Clean Code 3장. 함수

HYl·2022년 4월 26일
0

Clean Code

목록 보기
3/9

DAY 3

오늘 읽은 범위 : 3장, 함수


책에서 기억하고 싶은 내용을 써보세요.

작게 만들어라!

// 목록 3-3 HtmlUtil.java (re-refactored)
public static String renderPageWithSetupsAndTeardowns( 
	PageData pageData, boolean isSuite) throws Exception {
    	if (isTestPage(pageData))
			includeSetupAndTeardownPages(pageData, isSuite);
        return pageData.getHtml();
}
  • if 문/else 문/while 문 등에 들어가는 블록은 한 줄이어야 한다는 의미이다.
    이 말은 중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻이다. 그러므로 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다. 당연한 말이지만, 그래야 함수는 읽고 이해하기 쉬워진다.

한 가지만 해라!

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.

함수가 ‘한 가지’만 하는지 판단하는 방법이 하나 더 있다. 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다

함수 당 추상화 수준은 하나로!

함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.

getHtml()은 추상화 수준이 아주 높다. 반면, String pagePathName = PathParser.render(pagepath);는 추상화 수준이 중간이다. 그리고 .append(“\n”)와 같은 코드는 추상화 수준이 아주 낮다.

근본 개념과 세부사항을 뒤섞기 시작하면, 깨어진 창문처럼 사람들이 함수에 세부사항을 점점 더 추가한다.

  • 위에서 아래로 코드 읽기: 내려가기 규칙
    • 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

Switch 문

switch 문을 저차원 클래 스에 숨기고 절대로 반복하지 않는 방법은 있다. 물론 다형성(polymorphism)을 이용 한다.

switch 문을 추상 팩토리에 꽁꽁 숨긴다. 아무에게도 보여주지 않는다.
calculatePay, isPayday, deliverPay 등과 같은 함수는 Employee 인터페이스를 거쳐 호출된다. 그러면 다형성으로 인해 실제 파생 클래스의 함수가 실행된다.

서술적인 이름을 사용하라!

이름이 길어도 괜찮다. 겁먹을 필요없다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다. 함수 이름을 정할 때는 여러 단어가 쉽게 읽히는 명명법을 사용한다. 그런 다음, 여러 단어를 사용해 함수 기능을 잘 표현하는 이름을 선택한다.

이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다. includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage, includeSetupPage 등이 좋은 예다.

함수 인수

함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개 (삼항)는 가능한 피하는 편이 좋다. 4개 이상 (다항)은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다.

함수 이름에 키워드를 추가하는 형식이다. 즉, 함수 이름에 인수 이름을 넣는다. 예를 들어, assertEquals보다 assertExpectedEqualsActual (expected, actual)이 더 좋다. 그러면 인수 순서를 기억할 필요가 없어진다.

부수 효과를 일으키지 마라!

public class UserValidator {
	private Cryptographer cryptographer;
    
	public boolean checkPassword(String userName, String password) {
    	User user = UserGateway.findByName(userName);
		if (user != User.NULL) {
        	String codedPhrase = user.getPhraseEncodedByPassword(); 
        	String phrase = cryptographer.decrypt(codedPhrase, password); 
        	if ("Valid Password".equals(phrase)) {
				Session.initialize();
				return true; 
        	}
		}
		return false; 
    }
}

여기서, 함수가 일으키는 부수 효과는 Session.initialize() 호출이다. checkPass- word 함수는 이름 그대로 암호를 확인한다. 이름만 봐서는 세션을 초기화한다는 사실이 드러나지 않는다.

만약 시간적인 결합이 필요하다면 함수 이름에 분명히 명시한다. checkPasswordAndInitializeSession이라는 이름이 훨씬 좋다. 물론 함수가 ‘한 가지’만 한다는 규칙을 위반하지만.

명령과 조회를 분리하라!

함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안 된다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. 둘 다 하면 혼란을 초래한다.

오류 코드보다 예외를 사용하라!

if (deletePage(page) == E_OK) {
  if (registry.deleteReference(page.name) == E_OK) {
    if (configKeys.deleteKey(page.name.makeKey()) == E_OK){ 
      logger.log("page deleted");
    } else {
       logger.log("configKey not deleted");
    }
  } else {
    logger.log("deleteReference from registry failed"); 
  }
} else {
  logger.log("delete failed"); 
  return E_ERROR;
}

오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.

try {
  deletePage(page); 
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
  logger.log(e.getMessage()); 
}

함수를 어떻게 짜죠?

처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
그런 다음 나는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다.


오늘 읽은 소감은?

마지막 주제였던 함수를 어떻게 짜죠? 에서 인상이 깊었다.
코드를 짤 때 항상 완벽하게 짜야 한다는 강박이 은연중에 있어서 코드를 작성하는 것이 힘들 때가 많았다. 책에서 한 번에 완벽하게 짜는 사람은 없다는 문장을 보니 그간 자책을 했던 마음들이 위안이 된 것 같다.

예외 처리를 하는 것이 나의 단점이라고 늘 생각하고 있었다. 처리를 하긴 하는데 뭔가 어설픈 느낌이랄까. 공부해야지 늘 마음만 먹고 있었는데 비교된 코드를 바라보니, 예외 처리를 숙달해야겠다고 다시 한번 다짐을 하게 된다.

profile
꾸준히 새로운 것을 알아가는 것을 좋아합니다.

0개의 댓글