반응형
Clean Code
by. Robert C. Martin
03. 함수
어떻게 함수를 읽기 쉽고 이해하기 쉽게 만들 수 있을까?
의도를 분명히 표현하는 함수를 어떻게 구현할 수 있을까?
함수에 어떤 속성을 부여해야 처음 읽는 사람이 프로그램 내부를 직관적으로 파악할까?
작게 만들어라!
함수를 만드는 규칙
- 작게!
- 더 작게!
각 함수는 명백하게 하나의 이야기를 표현해야 한다.
블록과 들여쓰기
if / else / while 문 등에 들어가는 블록은 한 줄이어야 한다.
대체로 블록 안에서 함수를 호출한다.
이때 함수 이름을 적절하게 짓는다면, 코드를 이해하기도 쉽다.
중첩 구조가 생길만큼 함수가 커져서는 안된다.
즉, 함수에서의 들여쓰기 수준은 1단이나 2단을 넘어서면 안된다.
한 가지만 해라!
- 함수는 한 가지를 해야한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
- 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행해야 한다.
- 함수는 큰 개념을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해 만든다.
- 만약, 의미 있는 이름으로 다른 함수를 추출한다면, 함수는 여러 작업을 하는셈이다.
함수 당 추상화 수준은 하나로!
함수 내 모든 문장의 추상화 수준이 동일해야 한다.
위에서 아래로 코드 읽기: 내려가기 규칙
- 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
- 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
- 물론 추상화 수준이 하나인 함수 구현은 어렵다.
- 그렇지만 매우 중요한 규칙이다.
Switch 문
Switch 문은 작게 만들기 어렵다.
본질적으로 '한 가지'만 하는 switch문도 어렵다. switch문은 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);
}
}
위 함수는 몇 가지 문제가 있다.
- 함수가 길다.
- '한 가지' 작업만 수행하지 않는다.
- SRP를 위반한다.
- 한 클래스는 하나의 책임만을 가져야 한다.
- OCP를 위반한다.
- 확장은 가능하지만, 변경은 불가능 해야한다.
public abstract class Employee {
public abstract Money calculatePay();
}
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 SalariedEmployee(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}
switch 문을 추상 팩토리에 숨긴다.
팩토리는 switch 문을 사용해 Employee의 파생 클래스의 인스턴스를 생성하고, 함수들은 인스턴트를 거쳐 실행된다.
서술적인 이름을 사용하라!
- 함수가 하는 일을 좀 더 잘 표현하는 이름
- 함수가 작고 단순할수록 서술적인 이름을 짓기 더 쉽다.
- 길고 서술적인 이름 > 짧고 어려운 이름
- 이름을 붙일 때는 일관성이 있어야한다.
함수 인수
함수에서 가장 이상적인 인수는 0개(무항).
- 그 다음은 1개(단항), 다음은 2개(이항)
- 그 이상은 가능한한 피하는 편이 좋다.
- 4개 이상(다항)은 특별한 이유가 필요하다. 하지만 사용하면 안된다.
코드를 읽는 사람이 세부사항을 알아야하는 경우가 생기기 때문에
많이 쓰는 단항 형식
- 인수에 질문을 던지는 경우
- 인수를 변환해 결과를 반환하는 경우
- 이벤트
- 입력만 있고, 출력은 없다.
위 경우가 아니라면 단항 함수는 피한다.
플래그 인수
플래그 인수는 추하고 끔찍하다.
함수가 여러가지를 행하는 것을 의미
인수 객체
- 인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성이 높다.
- 하지만 이미 개념을 표현하게 된다
동사와 키워드
좋은 함수 이름은 함수와 인수가 동사와 명사 쌍을 이뤄야 한다.
부수 효과를 일으키지 마라!
- 부수 효과는 거짓말
- 함수는 한 가지를 하겠다고 하고, 다른 일을 하는 것
- 예상치 못하게 클래스 변수를 수정하거나, 전역 변수를 수정한다.
- 시간적인 결합이나 순서 종속성을 초래한다.
명령과 조회를 분리하라!
- 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다.
- 객체를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나
오류 코드 보다 예외를 사용하라!
- 명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리를 위반한다.
- 오류를 반환하게 되면, 호출자는 오류 코드를 처리해야 한다.
- 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔.
try-catch 블록 뽑아내기
- 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 섞는다.
- 별도의 함수로 뽑아내자
- 정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉬어진다.
오류 처리도 한 가지 작업이다.
- 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
- 함수에 try 키워드가 있다면, 함수는 try로 시작해 catch/finally로 끝나야 한다.
반복하지 마라!
- 중복은 코드 길이가 늘어날 뿐 아니라, 알고리즘이 변하면 중복된 곳 모두를 손봐야 한다.
- 중복을 줄이면 모듈의 가독성이 늘어난다.
- 객체지향에서는 코드를 부모 클래스로 몰아 중복을 없앤다.
- 구조적 프로그래밍, AOP, COP 모두 중복제거 전략이다.
구조적 프로그래밍
- 모든 함수와 함수 내 블록에 입구와 출구는 하나만 존재해야 한다.
- 함수는 return이 하나여야 한다.
- break나 continue를 사용해선 안되며, goto는 절대로 안된다.
- 함수가 작게 만든다면 간혹 return, break, continue를 사용해도 괜찮다.
함수를 어떻게 짜죠?
- 처음엔 길고 복잡하다.
- 많은 들여쓰기 단계
- 많은 중복된 루프
- 긴 인수 목록
- 즉흥적인 이름
- 중복된 코드
- 코드를 다듬는다.
- 함수를 만든다.
- 이름을 바꾼다.
- 중복을 제거한다.
- 메서드를 줄인다.
- 순서를 바꾼다.
반응형
'Book Record' 카테고리의 다른 글
[Objects] Chapter1. 객체, 설계 (0) | 2021.04.20 |
---|---|
[Clean Code] Chapter5. 형식 맞추기 (0) | 2021.04.20 |
[Clean Code] Chapter4. 주석 (0) | 2021.04.20 |
[Clean Code] Chapter2. 의미 있는 이름 (0) | 2021.04.20 |
[Clean Code] Chapter1. 깨끗한 코드 (0) | 2021.04.20 |
댓글