- if / else / while 문에 블록은 한줄이어야 한다. → 감싸는 함수(enclosing function)가 작아진다.
- 거기서 함수를 호출하도록 한다. → 블록안의 함수 이름을 적절하게 짓는다면, 코드를 이해하기 쉬워진다.
- 함수내 들여쓰기는 1~2단 내로 제한한다.
- 추상화 수준이 하나이면, 한가지만 하는 것이다.
→ 함수를 만드는 이유는 결국 큰 개념을 다음 추상화 수준에서 여러단계로 나누어 하기 위함이다.- 함수내에 의미 있는 다른 함수로 추출이 가능하다면, 여러 작업을 하고 있는 것이다.
- 한가지만 하기 위해서는 함수내 추상화 수준이 하나여야한다.
- 한 함수내 추상화 수준이 여러개이면, 읽기가 어렵다.
→ 근본 개념 + 세부사항이 뒤섞이기 시작하면, 깨진창문 처럼 세부사항이 증가한다.
위에서 아래로 코드 읽기 : 내려가기 규칙- 한 단씩 함수내 추상화 수준을 낮춰야 한다.
- switch문은 작게 만들기 어렵다.
- 한가지 작업만 하는 switch문을 만들기도 어렵다.
switch (e.type) {
case COMMITSSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
위 코드의 문제점
switch문을 허용하는 하나의 케이스. 다형적 객체를 생성하는 코드.
상속관계로 숨긴후 다른코드에 들어내지 않는다.
public interface EmployeeFactory {
public Employee makeEmployee(EmployeeRecorder r) throws InvalidEmployType;
}
---------------------------------------
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecorder r) throws InvalidEmployType{
// switch문
}
}
- 주석을 쓰는거 보다 서술적 이름을 쓰는것이 좋다.
- 서술적인 이름은 개발자 머릿속 설계에도 도움이 된다.
- 일관성 있는 이름 → 짐작하는 대로 함수를 찾음
- 인수는 이해를 어렵게 만든다.
- 0개(무항) : 이상적인 인수
- 1개(단항) / 2개(이항)
- 3개(삼항) : 가능한 피한다. → 유효한 조합으로 테스트가 부담스러워짐.
- 4개이상(다항) : 사용시 특별한 이유가 필요
- 출력인수는 입력인수보다 이해하기 어렵다.
< 많이 쓰는 단항 형식 >
< 피해야할 단항 형식 >
< 플래그 인수 >
< 이항 함수 >
이항함수가 적절한 경우
< 삼항함수 >
< 인수객체 >
< 인수목록 >
< 동사와 키워드 >
부수효과는 거짓말이다.
함수에서 한가지를 하겠다 해놓고, 남몰래 다른것도 하는 것이니.
- 시간적인 결합(temporal coupling) + 순서 종속성(order dependency) 를 발생
- 차라리 한가지만 하지 못할꺼면, 함수명에 부수효과 까지 작성 해라.
< 출력인수 >
출력인수는 피한다.
함수에서 상태를 변경한다면, 함수가 속한 객체 상태를 변경하는 방식을 택한다.
함수는 객체의 상태를 변경 or 정보를 반환. 둘다하면 안됨.
// ASIS - 의도가 모호하다. set하는 함수인가? username이 unclebob인지 확인하는 함수인가?
if(set("username", "unclebob")...
// TOBE
if(attributeExists("username")) {
setAttribute("username", "unclebob");
}
명령함수에서 오류코드를 반환하는것은, 명령/조회 분리 규칙에 어긋난다.
- if(deletePage(page) == E_OK)
- 명령을 표현식 같이 사용하게 된다.
- 중첩되는 코드를 야기한다.
- 오류 코드를 반환하면, 호출자는 오류코드를 곧바로 처리해야 한다는 문제점에 부딪힘.
< Try/Catch블록 뽑아내기 >
try/catch문은 추하다. try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
public void delete(Page page){
try{
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
// try블록 별도 함수로 추출
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name); // 실제동작함수
configKeys.deleteKey(page.name.makeKey());
}
// catch블록 별도 함수로 추출
private void logError(Exception e){
logger.log(e.getMessage());
}
< 오류 처리도 한 가지 작업이다. >
< Error.java 의존성 자석(magnet) >
중복은 모든 악의 근원이다.
AOP, COP는 어떤면에서 중복을 제거하기 위한 전략이다.
모든 함수의 입구와 출구는 하나만 존재. 즉 return문이 하나.
함수를 작게 만든다면 return, break, continue를 여러번 사용가능하나, goto문은 함수의 크기와 관계없이 지양하는 것이 좋다.
여느 글짓기와 비슷하다. 생각을 기록 → 읽기 좋게 다듬는다.
- 처음에는 길고, 복잡 + 중복 + 인수테스트 목록도 길고 + 테스트 케이스 또한 세세하다.
- 함수를 만들고 + 이름을 바꾸고 + 중복을 제거 + 메서드를 줄이고, 순서를 바꾼다. + 전체 클래스를 쪼개기도한다.
이 과정에서 테스트 케이스는 항상 통과 된다.
*FitNesse(오픈소스 테스트 도구)