클린코드 - 3.함수
2022-02-27
함수
- 작게만들어라!
- 함수를 작게 만들어 명백하게 만든다.
- if 문/ else 문/ while 문 등에 들어가는 블록은 한 줄이어야 한다. 대게 여기서 함수를 호출한다. 그러면 바깥을 감싸는 함수가 작아질 뿐 아니라, 블록 안에서 호출하는 함수 이름을 적절히 짓는다면, 코드를 이해하기도 쉬워진다.
- 중첩 구조가 생길만큼 함수가 커져서는 안 된다. 그러므로 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
- 한 가지만 해라!
- 함수는 한 가지를 해야 한다. 그 한가지를 잘해야 한다. 그 한 가지만을 해야한다.
- 함수 당 추상화 수준은 하나로!
- 함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
- 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다. 특정 표현이 근본 개념인지 아니면 세부사항인지 구분하기 어려운 탓이다.
- 근본 개념과 세부사항을 뒤섞기 시작하면, 깨어진 창문처럼 사람들이 함수에 세부사항을 점점 더 추가한다.
- 코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다. 이것을 내려가기 규칙이라 부른다.
- Switch 문
- switch 문을 추상 팩토리에 꽁꽁 숨겨 아무에게도 보여주지 않는다. 팩토리는 switch 문을 사용해 적절한 파생 클래스의 인스턴스를 생성한다.
- 서술적인 이름을 사용하라!
- 함수가 하는 일을 좀 더 잘 표현할 수 있다.
- 이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
- 함수 인수
- 함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다.
- 인수 객체를 이용하면 좋다.
- 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다. write(name)
- 함수 이름에 키워드를 추가하는 형식을 사용한다. 즉, 함수 이름에 인수 이름을 넣는다.
- 부수 효과를 일으키지 마라!
- 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓을 하면 안 된다.
- 부수 효과는 시간적인 결합을 초래한다. 시간적인 결합은 혼란을 일으킨다.
- 일반적으로 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
- 명령과 조회를 분리하라!
- 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안 된다. 객체 상태를 변경하거나 객체 정보를 반환하거나 둘 중 하나다.
- 오류 코드보다 예외를 사용하라!
- 명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.
- 반면 오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.
- try/catch 블록은 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
- 오류 처리도 ‘한 가지’ 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 한다. 함수에 키워드 try가 있다면 함수는 try 문으로 시작해 catch/finally 문으로 끝나야 한다.
- 오류 코드를 반환한다는 이야기는, 클래스든 열거형 변수든, 어디선가 오류 코드를 정의한다는 뜻이다. 클래스는 의존성 자석이여서 변경이 어려워진다. 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.
- 반복하지 마라!
- 중복은 소프트웨어에서 모든 악의 근원이다. 많은 원칙과 기법이 중복을 없애거나 제어할 목적으로 나왔다.
- 구조적 프로그래밍
- 모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다. 즉, 함수는 return 문이 하나여야 한다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로, 절대로 안 된다.
- 하지만 함수를 작게 만든다면 return, break, continue를 여러 차례 사용해도 괜찮다. 오히려 때로는 단일 입/풀구 규칙보다 의도를 표현하기 쉬워진다.
- 반면, goto 문은 큰 함수에서만 의미가 있으므로, 작은 함수에서는 피해야만 한다.
- 함수를 어떻게 짜죠?
- 처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다.
- 이 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
- 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다.
- 이 와중에도 코드는 항상 단위 테스트를 통과한다.
결론
모든 시스템은 특정 응용 분야 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어: Domain Specific Language, DSL로 만들어진다. 함수는 그 언어에서 동사며, 클래스는 명사다. 요구사항 문서에 나오는 명사와 동사를 클래스와 함수 후보로 고려한다는 끔찍한 옛 규칙으로 역행하자는 것이 아니다. 프로그래밍의 기술은 언제나 언어 설계의 기술이다.
시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여겨야 한다. 프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 좀 더 표현력이 강한 언어를 만들어 이야기를 풀어간다. 시스템에서 발생하는 모든 동작을 설명하는 함수 계층이 바로 그 언어에 속한다. 재귀라는 기교로 각 동작은 바로 그 도메인에 특화된 언어를 사용해 자신만의 이야기를 풀어간다.
함수를 잘 만들면 길이가 짧고, 이름이 좋고, 체계가 잡힌 함수가 나오지만 진짜 목표는 시스템이라는 이야기를 풀어가는 데 있다. 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다.