티스토리 뷰

JAVA/Clean Code

3장 함수

소농배 2018. 4. 25. 21:57

작게 만들어라!


* 블록 (if , else, while ) 은 한 줄이어야 한다. 따라서 함수내의 들여쓰기는 1단이나 2단을 넘어가면 안된다.


한가지만 해라!


* 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지작업만 한다.

* 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.


Switch 문


* Switch 문은 한가지 작업만 하도록 만들기 어렵다. 즉 Switch 문은 N가지 일을 처리한다. 다형성을 이용하면 저 차원 클래스에 숨기고 절대로 반복하지 않을 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
public Money calculatePay(Employee e) throws InvalidEmployeeType {
    switch (e.type) {
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calcuateHorlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
        default:
            throw new InvalidEmployeetype(e.type);
    }
}
cs


위 함수는 여러가지 문제점이 있다.


1. 함수가 길다.

2. 한 가지 작업만 수행하지 않는다.

3. SRP (Single Responsibility Principle) 를 위반한다.

4. OCP (Open Closed Principle) 를 위반한다.

- 새로운 직원 유형을 추가할 대 마다 코드를 변경하기 때문이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 SalariedEmployee(e);
            default:
                throw new InvalidEmployeetype(e.type);
        }
    }
}
 
cs


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


* 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다.


* 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.


함수 인수


* 함수에서 이상적인 인수 개수는 0개이다. 그 다음은 1개 다음은 2개, 3개는 가능한 피하는 편이 좋다.


* 함수에 인수 1개를 넘기는 이유로 가장 흔한 두가지.

1. 인수에 질문을 던지는 경우 ex) boolean fileExists("MyFile")

2. 인수를 뭔가 변환해 결과를 반환한다. ex) InputStream fileOpen("MyFile")


단항 형식의 함수를 만들때는 위 두가지를 확실히 구분하여 이름을 짓는다.


* 플래그 인수는 추하다

1. 플래그를 인수로 받는 함수는 대놓고 플래그에 따라서 다른 처리를 하겠다고 공표한다.


* 이항 함수

1. Point P = new Point(0, 0) 가 좋은 예다. 

-직교 좌표계 점은 일반적으로 2개의 인수를 취해야 한다. 하나의 인수를 취하는 편이 오히려 어색하다.

2. 아주 당연하게 여겨지는 이항 함수 assertEquals(expected, actual) 에도 문제가 있다.

- expected 와 actual 의 위치를 인위적으로 기억해야 한다. 

3. 따라서 이항 함수는 가능한 단항 함수로 변경한다.


* 삼항 함수

1. 인수가 3개인 함수는 2개인 함수보다 훨씬 더 이해하기 어렵다.

2. 순서, 주춤, 무시로 야기되는 문제가 두 배 이상 늘어난다.


* 인수 객체

1. 인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다.

Circle makeCircle(double x, double y, double radius) => 

Circle makeCircle(Point center, double radius)


* 인수 목록

1.  때로는 인수 개수가 가변적인 함수도 필요하다.

String.format("%s worked %.2f hours.", name, hours);

가변 인수를 모두 List 로 취급한다면 하나의 인수로 취급할 수 있다. 실제로 그렇게 하고있다.

public String format(String format, Object... args)


* 동사와 키워드

1. 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.

write(name) : '이름' 이 무엇이든 '쓴다' => writeField(name) : '이름'이 '필드'


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


* 부수 효과는 거짓말이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserValidator {
    private Cryptographer crytographer;
    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;
    }
}
 
cs


* 여기서 함수가 일으키는 부수 효과는 Session.initialize() 호출이다.

1. checkPassword 함수는 이름 그대로 암호를 확인한다.

2. 이름만 봐서는 세션을 초기화 하는지 알 수 없다.

3. 따라서 함수 이름만 보고 함수를 호출하는 사용자는 사용자를 인증하면서 기존 세션 정보를 지워버릴 수 있다.

4. 개선한다면 checkPasswordAndInitializeSession 물론  한 가지만 한다는 규칙을 위반하지만....


* 출력 인수

appendFooter(s);

-  위 함수는 이름만 보고는 어떤 일을 하는지 정확하게 알 수 없다. s 를 Footer 에 붙이는지, s에 Footer에 붙이는지...


public void appendFooter(StringBuffer report)

- 선언부를 보면 분명하진다.


report.appendFooter() 와 같이 호출하는게 명확하다.


명령과 조회를 분리하라!


* 함수는 무언가를 수행하거나 무언가를 답하거나 둘 중 하나만 해야 한다.

public boolean set(String attribute, String value);

이 함수는 attribute인 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환한다.

그로 인해 다음과 같은 괴상한 코드가 생성된다.

if (set("username", "unclebob"))...


아래와 같이 수정하면 명확하다.


if (attributeExists("username")) {

setAttribute("username", "unclebob");

....

}



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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:
}
cs


1
2
3
4
5
6
7
8
9
 
try {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
catch (Exception e) {
    logger.log(e.getMessage());
}
 
cs



반복하지 마라!


* 중복은 코드 길이가 늘어날 뿐 아니라 알고리즘이 변하면 반복되는 곳 모두 수정해야 한다.




'JAVA > Clean Code' 카테고리의 다른 글

4장 주석  (0) 2018.05.02
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함