본문 바로가기
Architecture

[Clean Code 정리] 함수

by 테리는당근을좋아해 2022. 5. 28.

Clean Code - 함수

 

1. 함수

- 어떤 프로그램이든 기본 단위는 함수다

- 길고, 중복되며, 난해한 문자열과 낯설고 모호한 타입의 코드로 이루어진 함수는 이해하기 어렵다.

- 읽기 쉽고 이해하기 쉬운 함수를 작성해야 한다.

 

 

 

2. 작게 만들어라

- 함수를 만드는 규칙의 첫번째, 두번째도 작게 만드는 것이다.

 

 

 

3. 한 가지만 해라

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

- 함수를 만드는 이유는 큰 개념을 추상화 수준에서 여러 단계로 나눠 수행하기 위함이다.

- 함수를 여러 섹션으로 나눌 수 있다면 그 함수는 여러 작업을 하는 것이다.

 

 

 

4. 함수 당 추상화 수준은 하나로

- 함수가 한 가지 작업을 하기 위해서는 함수 내 모든 문장의 추상화 수준이 동일해야 한다.

- 한 함수 내에서 추상화 수준을 섞으면 코드를 읽기 어려워 진다.

 

1) 위에서 아래로 코드 읽기

- 코드는 위에서 아래로 이야기하듯이 읽혀야 한다.

- 함수 추상화 부분이 한 번에 한 단계씩 낮아지는 것이 가장 이상적이다.

 

 

 

5. Switch문

- 본질적으로 n가지 일을 하는 switch문을 작게 만드는 것은 어렵다.

- 팩터리, 전략 패턴 등을 이용해 switch문을 제거하자

 

 

 

6. 서술적인 이름을 사용하라

- 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행할 때 깨끗한 코드라 부를 수 있다.

- 작은 함수는 그 기능이 명확 하므로 이름을 붙이기가 더 쉬워진다.

- 일관성있는 서술형 이름을 사용한다면 코드를 순차적으로 이해하기도 쉬워진다.

- 이름을 정하는데 시간을 들여도 좋다.

 

 

 

7. 함수 인수

- 함수에서 이상적인 인자의 개수는 0개이며, 차선은 1개, 다음은 2개이다.

- 3개 이상은 가능하면 피하는 것이 좋다.

 

1) 출력 인수

- 함수의 반환 값이 입력 인수로 결과를 받는 경우 이해하기 어렵기 때문에 가능하면 사용하지 않는 것이 좋다.

- 출력 인수가 많을 경우 클래스/pair/tuple 등을 고려하자

 

 

 

2) 단항 함수

// 많이 쓰는 단항 형식
// 1. 인수에 질문을 던지는 경우 : bool fileExist("filename");
// 2. 인수를 변환해 결과를 반환하는 경우 : InputStream fileOpen("filename");
// 3. 이벤트 함수일 경우

// 안 좋은 예
// 변환 함수에서 출력 인수를 사용하면 프로그래머에게 혼란을 줌
void includeSetupPageInfo(String& pageText);


// 좋은 예
// 입력 인수와 출력 인수가 명확
// 입력 인수를 그대로 돌려주는 함수라도 변환 함수 형식을 따르는 것이 좋다.
String transform(final String in);

 

 

3) 이항 함수

- 단항 함수보다 이해하기 어렵지만 setPoint(x, y); 처럼 적절히 사용해야하는 경우가 존재한다.

- 이항 함수를 사용할 때는 인자의 순서를 기억해야하는 불편함이 존재한다.

 

 

 

4) 인수 객체(IntroduceParameterObject)

- 인수가 2 ~ 3개 필요하다면 일부를 클래스 변수로 대체한다.

- 함께 사용되는 파라미터 그룹이 있다면 그것을 객체로 변경한다.

- 긴 파라미터 리스트는 가독성을 떨어뜨리고 중복된 코드를 발생시킨다.

 

# 안 좋은 예
def analysis_wafer(lot_id, wafer_id, swap_time, expose_start, expose_end):
    ...


# 좋은 예
class Wafer():
    def __init__(self):
        self.__lot_id = None
        self.__wafer_id = None
        self.__swap_time = None
        self.__expose_start = None
        self.__expose_end = None

    ...

def analysis_wafer(wafer):
    ...

 

 

 

 

8. 동사와 키워드

- 함수명과 인수명이 동사/명사 쌍을 이뤄야 한다.

- 예 : void write(String name), assertExpectedEqualsActual(expected, actual);

 

 

 

9. 부수 효과를 일으키지 마라.

- 부수 효과란 함수에서 한 가지를 하겠다고 약속해놓고 다른 기능을 함께 수행하는 것이다.

- 한 함수에서는 딱 한가지만을 수행한다.

 

 

 

10. 출력 인수

- 일반적으로 출력 인수는 피해야 한다.

- 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택하라

 

 

 

11. 명령과 조회를 분리하라

- 함수는 "객체 상태를 변경"하거나, "정보를 반환"하거나 둘 중 하나만 수행해야 한다.

 

# 안 좋은 예
def set_lot_overhead(lot, overhead):
    if lot.id == None:
        return False
    lot.overhead = overhead
    return True

...
if (set_lot_overhead(current_lot, overhead)):



# 좋은 예
if is_valid_lot(lot):
    set_lot_overhead(lot, overhead)

 

 

 

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

- try/catch문을 사용하면 오류 처리가 원래 코드에서 분리되기 때문에 코드가 더 깔끔해 진다.

 

# 좋은 예
if is_valid_lot(lot):
    set_lot_overhead(lot, overhead)

def analysis(self, wafer):
    try:
        calculate_wafer_swap_time(wafer)
        ...
    except:
        # 오류 처리도 한 가지 작업이기 때문에 별도의 함수로 뽑아내는 것이 좋다
        self.logger.exception("analysis error")

 

 

 

 

13. 반복하지 마라

- 중복은 악의 근원이기 때문에 늘 중복을 없애도록 노력해야 한다.

 

 

 

14. 구조적 프로그래밍

- Dijkstra의 구조적 프로그래밍 원칙을 따르면 모든 함수와 블록의 입구와 출구는 하나여야 한다.

- 함수는 return문이 하나여야하고, 루프 안에서 break, continue를 사용하면 안되며, goto는 절대 사용해서는 안된다.

- 구조적 프로그래밍은 함수가 클 경우에만 많은 이점을 제공하기 때문에, 함수가 작다면 위 규칙은 많은 효과를 보기 어렵다.

 

 

 

15. 함수를 어떻게 짜야할까?

- 처음 함수는 길고 복잡하고, 중복된 루프가 많거나 인수 목록이 길수도 있다.

- 단위 테스트 케이스를 만들고, 코드를 다듬고, 함수를 추출하고, 이름을 바구고, 중복을 제거한다.

- 코드는 항상 단위 테스트를 통과해야 한다.

댓글