작게 만들어라!
- 블록과 들여쓰기
- if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어야 한다.
- 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
한 가지만 해라!
- 함수는 한가지를 해야 하고 그 한 가지를 잘 해야 하며 그 한 가지만을 해야 한다.
- 지정된 함수 이름 아래에서 추상화 수준이 하낭니 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
- 한 가지 작업만 하는 함수는 섹션으로 나누기 어렵다.
함수 당 추상화 수준은 하나로!
- 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
- getHtml() - 추상화 수준 높음
- .append("\n") - 추상화 수준 낮음
- 위에서 아래로 코드 읽기: 내려가기 규칙
- 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
- 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.
Switch 문
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
- switch 문을 완전히 피할 방법은 없다.
- 하지만 다형성을 이용해서 각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법은 있다.
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
public interface EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployee(r) ;
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmploye(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}
- switch 문을 추상 팩토리에 꽁꽁 숨긴다. 노출 절대 X
- 팩토리는 switch 문을 사용해 적절한 Employee 파생 클래스의 인스턴스를 생성한다.
- 다형적 객체를 생성하는 코드 안에서는 switch 문을 사용하자.
서술적인 이름을 사용하라!
- 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 된다.
함수 인수
함수에서 이상적인 인수의 개수는 0개이고, 차선은 1개뿐인 경우다.
많이 쓰는 단항 형식
- 인수에 질문을 던지는 경우
boolean fileExists(“MyFile”);
- 인수를 뭔가로 변환해 결과를 반환하는 경우
InputStream fileOpen(“MyFile”);
- 이벤트 함수일 경우
- 이벤트라는 사실이 코드에 명확히 드러나야 한다.
플래그 인수
- 사용하지 말자(함수 내에서 여러 가지를 처리하기 때문)
Bad: render(true)
Good: renderForSuite()
, renderForSingleTest()
이항 함수
- 단항 함수보다 이해하기 어렵다.
- 하지만
new Point(x, y);
처럼 적절한 경우도 간혹 있다.
삼항 함수
- 이항 함수보다 더 이해하기 어렵다. 신중히 고려해서 사용하자.
인수 객체
- 인수가 2~3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어보자.
인수 목록
String.format
처럼 인수 개수가 가변적인 함수도 필요하다.
동사와 키워드
- 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
writeField(name)
- 함수 이름에 키워드를 추가하면 인수 순서를 기억할 필요가 없어진다.
- Bad:
assertEquals(expected, actual)
- Good:
assertExpectedEqualsActual(expected, actual)
- Bad:
부수 효과를 일으키지 마라!
- 하나의 함수에서는 한 가지 일만 해야 한다.
- 주의해야 할 것
- 예상치 못하게 클래스 변수를 수정하는 것
- 함수로 넘어온 인수나 시스템 전역 변수를 수정하는 것
출력 인수
- 출력 인수는 피해야 한다.
- 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
명령과 조회를 분리하라!
- 함수는 객체 상태를 변경하거나 객체 정보를 반환하거나 둘 중 하나다.
- 명령과 조회를 분리해 혼란을 애초에 뿌리뽑자.
Good
if (attributeExists("username")) {
setAttribute("username", "unclebob");
}
오류 코드보다 예외를 사용하라!
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;
}
- 오류 코드 대신 예외를 사용해보자.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
} catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
- 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.
- try/catch 블록은 별도 함수로 뽑아내는 편이 좋다.
오류 처리도 한 가지 작업이다
- 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
- try문으로 시작해 catch/finally 문으로 끝나야 한다.
Error.java 의존성 자석
public enum Error {
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES,
WAITING_FOR_EVENT;
}
- 위와 같은 클래스는 의존성 자석이다.
- 오류 코드 대신 예외를 사용하자. 그러면 새 예외는 Exception 클래스에서 파생된다.
반복하지 마라!
- 중복은 소프트웨어에서 몯느 악의 근원이다.
구조적 프로그래밍
- 함수를 작게 만든다면 return, break, continue를 여러 차례 사용해도 괜찮다.
- 작은 함수에서는 goto 문을 피해야만 한다.
함수를 어떻게 짜죠?
- 소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다.
- 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만든다.
- 그런 다음 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다.
- 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스는 쪼개기도 한다.
- 이 와중에도 코드는 항상 단위 테스트를 통과해야 한다.
결론
- 시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여겨보자.
- 내가 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억해야 한다.
'Clean Code' 카테고리의 다른 글
[Clean Code] 경계 (0) | 2020.09.03 |
---|---|
[Clean Code] 오류 처리 (0) | 2020.09.03 |
[Clean Code] 객체와 자료구조 (0) | 2020.09.03 |
[Clean Code] 형식 맞추기 (0) | 2020.09.03 |
[Clean Code] 의미 있는 이름 (0) | 2020.09.03 |