3장 - 함수
함수는 어떤 프로그램이든 가장 기본적인 단위가 된다. 함수를 읽기 쉽고 이해하기 쉽게 구현하는 방법에 대해 알아본다.
1. 작게 만들어라!
함수를 만드는 첫번째 규칙은 작게이다. 두번째 규칙은 더 작게 이다.
- 함수는 가로 150 글자를 넘어서는 안된다.
- 세로로 100줄을 넘어서는 안된다.
- 사실 20줄의 함수도 길다.
- if, if/else, while 문 등에 들어가는 블록은 한줄이어야 한다.
- 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서는 안된다.
2. 한가지만 해라!
함수는 한가지를 해야한다. 그 한가지를 잘 해야한다. 그 한 가지만을 해야한다.
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한가지 작업만 한다고 한다.
의미있는 이름으로 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.
3. 함수 당 추상화 수준은 하나로!
함수가 확실히 한가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
내려가기 규칙
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.
위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한번에 한 단계씩 낮아진다. 이것을 내려가기 규칙이라 부른다.
switch 문
switch문은 작게 만들기 힘들다. 같은 맥락으로 if/else가 여럿 이어지는 구조 역시 작게 만들기 힘들다.
또한, 한가지 작업만 하는 switch문도 만들기 어렵다.
swith문을 완전히 피해갈 방법은 존재하지 않는다.
- switch문을 추상 팩토리에 숨긴다.
4. 서술적인 이름을 사용하라!
- 좋은 이름이 주는 가치는 아무리 강조해도 지나치지 않는다.
- 이름이 길어도 괜찮다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
- 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
5. 함수 인수
이상적인 인수의 갯수는 0개이다.
그 다음은 1개이고, 다음은 2개이다.
3개는 가능하면 피하는 편이 좋다.
단항 형식
함수에 인수 1개를 넘기는 경우는 인수에 질문을 던지는 경우 와 인수를 뭔가로 변환해 결과를 반환하는 경우 이다.
- 변환 함수에서 출력 인수를 사용하면 혼란을 일으킨다.
- 입력 인수를 변환하는 함수라면 변환 결과는 반환값으로 돌려준다.
플래그 인수
함수로 부울 값을 넘기는 관례는 끔찍하다. 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 보여주는 셈이다.
이항 함수
인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어렵다.
프로그램을 짜다보면 불가피하게 인수가 2개인 함수를 만드는 경우가 생긴다.
그만큼 위험이 따른다는 사실을 이해하고 가능하면 단항 함수로 바꾸도록 애써야한다.
삼항 함수
인수가 3개인 함수는 순서, 주춤, 무시로 야기되는 문제가 빈번히 발생한다.
삼항 함수를 만들 때는 늘 신중히 고려해야한다.
인수 객체
인수가 2 ~ 3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 살펴본다.
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Pointer center, double radius);
인수 목록
인수 갯수가 가변적인 함수가 필요할 때도 있다.
가변 인수를 취하는 함수는 단항, 이항, 삼항 함수로 취급할 수 있다.
하지만 이를 넘어서는 인수를 사용할 경우에는 문제가 있다.
동사와 키워드
함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수이다.
단항 함수는 함수와 인수의 동사 / 명사가 쌍을 이루어야 한다.
writeField(name);
함수의 이름에 키워드를 추가하는 방법도 있다.
assertEquals(expected, actual); // 보다
assertExpectedEqualsActual(expected, actual); // 인수 순서를 기억할 필요가 없어진다.
6. 부수 효과를 일으키지 마라
부수 효과는 거짓말이다. 함수에서 한가지를 하겠다고 해놓고 남몰래 다른 짓도 하기 때문이다.
부수 효과는 시간적인 결합을 초래하게 된다.
함수가 한가지만 한다는 원칙을 위반하게 되지만 부수 효과가 생긴다면 함수 이름에 분명히 명시한다.
출력 인수
우리는 인수를 함수 입력으로 해석한다.
일반적으로 출력 인수는 피한다.
함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
7. 명령과 조회를 분리하라!
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야한다.
함수가 둘 다 할 경우에는 혼란을 초개하게 된다.
8. 오류 코드보다 예외를 사용하라!
명령 함수에서 오류 코드를 반환하는 방식은 명령 / 조회 분리 규칙을 미묘하게 위반한다.
오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제에 부딪힌다.
try / catch 블록 뽑아내기
try / catch 문은 코드 구조에 혼란을 이르키며, 정상 동작과 오류 처리 동작을 뒤섞는다.
ㄴtry / catch 블록을 별도 함수로 뽑아내는 편이 좋다.
오류처리도 한가지 작업인다.
함수는 한 가지 작업만 해야 한다. 오류 처리도 한 가지 작업 에 속한다.
Error.java
오류 코드를 반환한다는 이야기는 어딘가에서 오류 코드를 정의한다는 뜻이다.
public enum Error {
OK,
LOCKED,
WAITING_FOR_EVENT
}
위와 같은 클래스를 의존성 자석 이라고한다. 다른 클래스에서 위의 클래스를 import해야하기 때문이다.
만양 Error enum을 변경한다면 Error enum을 사용하는 클래스를 전부 변경하고 컴파일 해야한다.
오류 코드 대신 예외를 사용하면 재컴파일 / 재배치 없이도 새 예외 클래스를 추가할 수 있다.
9. 반복하지 마라!
- 다른 코드와 섞이면서 모양새가 조금 달라진 탓에 중복이 금방 드러나지 않지만 중복된 코드는 중복된 코드이다.
- 중복은 소프트웨어에서 모든 악의 근원이다.
10. 구조적 프로그래밍
구조적 프로그래밍 원칙에서는 return문이 하나여야하고, 루프 안에서 break나 continue를 사용해서는 안되며, goto는 절대로 안된다.
하지만 위의 규칙은 함수가 작다면 큰 이익을 제공하지 못한다.
함수를 작게 만든다면 return, break, continue를 여러차례 사용해도 괜찮다.
단, goto 문은 피해야한다.
11. 함수를 어떻게 짜죠?
소프트웨어를 짜는 행위는 글짓기와 비슷하다.
초안은 대게 서투르고 어수선하지만 원하는 대로 읽힐 때까지 다듬고 문장을 고치고 문단을 정리한다.
함수도 마찬가지이다.
- 처음에는 길고 복잡하고, 들여쓰기 단계도 많고 중복된 루프도 많은 함수를 짜게된다.
- 인수 목록도 아주 길다.
- 이름은 즉흥적이도 코드는 중복된다.
- 하지만 해당 함수에 대한 단위 테스트 케이스도 만든다.
- 그 다음 코드를 다음고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다.
- 메서드를 줄이고 순서를 바꾼다.
- 때로는 전체 클래스를 쪼개기도 한다.
- 이 와중에도 코드는 항상 단위 테스트를 통과한다.
- 최종적으로는 3장에서 설명한 규칙을 따르는 함수가 얻어진다.
처음부터 탁 짜내는 사람은 없다.
결론
프로그래밍의 기술은 언제나 언어 설계의 기술이다.
대가 프로그래머는 시스템을 구현할 프로그램이 아닌 풀어갈 이야기로 여긴다.
이 장에서 설명한 규칙대로 함수를 짠다면 길이가 짧고, 이름이 좋고, 체계가 잡힌 함수가 나올 것이다.
하지만 진짜 목표는 시스템이라는 이야기를 풀어가는데 있다.
우리가 작성한 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아 떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억하자.
'개발 공부 > etc' 카테고리의 다른 글
클린코드 5장 - 형식 맞추기 (0) | 2021.11.17 |
---|---|
클린코드 4장 - 주석 (0) | 2021.11.12 |
클린코드 2장 - 의미있는 이름 (0) | 2021.10.25 |
개발자 포트폴리오 만들기 - Notion (0) | 2020.10.29 |
이력서만들기 - GitHub Page (3) | 2020.04.12 |
댓글