Clean Code 3장 - 함수
작게 만들어라
- 저자의 경험으로 많게는 3000줄에서 적게는 20~30줄 정도의 함수를 만들었다고 하는데 작을수록 좋다고 확신한다고 한다.
- 필자 또한 함수가 길어지면 길어질수록 가독성이 떨어지고 기능적으로 한가지 기능이 아닌 여러 기능을 수행하고 있다고 생각하기에 이는 리팩토링 대상이라고 생각한다.
- 블록과 들여쓰기
- 들여쓰기는 2회를 넘기지 않는다.
- 중첩 구조가 생길 시 내부 블록을 함수로 분리해낸다.
- 함수를 감싸는 외부 함수의 크기가 작아지며 가독성이 좋아진다.
- 중첩 구조가 생기면 함수가 너무 커진다.
한 가지만 해라
- 함수는 한 가지만 하고 그 한 가지를 잘해야 한다.
- 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
- 의미있는 이름으로 함수를 추출할 수 있다면 이는 함수가 여러 기능을 수행하는 것이다.
함수당 추상화 수준은 하나로
- 한 함수 내의 추상화 수준은 동일해야 한다.
- 그렇지 않은 경우 독자가 근본 개념인지 세부 사항인지 파악하기 힘들다.
내려가기 규칙 - 위에서 아래로 코드 읽기
- 위에서 아래로 읽혀야 좋다.
- 함수 추상화 수준이 한 단계씩 낮아진다.
- 내려가기 규칙을 잘 지킨다면 추상화 수준을 동일하게 가져가기 쉽다.
Switch 문
- switch문 애초에 여러 가지를 처리하기 위하여 존재하기에 줄이기 어렵다.
- 하지만 상속/구현 구조를 통한 다형성을 이용하여 확장성을 증가시키고, switch를 다른 코드에 노출하지 않도록 개선할 수 있다.
서술적인 이름을 사용하라
- 함수가 하는 일을 더 잘 표현할 수 있는 서술적인 이름을 사용하자.
- 서술적 이름을 사용하면 설계가 뚜렷해져 개선하기 쉬워질 수 있다. - 시도해봐야겠다.
- 이름에는 일관성이 있어야 한다.
- 모듈 내에 함수 이름은 같은 문구, 명사, 동사를 사용한다.
함수 인수
- 함수의 이상적인 인수 개수는 0개이다.
- 3개 이상의 인수를 갖는다면 함수 분리 고려 대상이다.
- 인수는 코드를 읽는 사람이 그 의미를 매번 파악해야 한다.
- 테스트를 할 경우 모든 인자를 유효한 값으로 세팅해주어야 하기에 복잡해진다.
- 단항 함수
- 인수에 질문을 던지는 경우
- boolean fileExists(“MyFile”);
- 인수를 뭔가로 반환하는 경우
- InputStream fileOpen(“MyFile”);
- 시스템 상태를 바꾸는 이벤트 함수
- void passwordAttemptFailedNTimes(int attempt); 등에 적합하다.
- 인수에 질문을 던지는 경우
- 플래그 인수
- 플래그 인수는 해당 함수가 여러가지 기능을 수행한다는 증거이다.
- 이항 함수
- 인수가 2개인 함수는 단항 함수보다 이해가 어렵다.
- 인수 2개가 한 값을 나타내거나 자연적인 순서가 있는 경우 적합하다.
- new Point(0, 0);
- assertEquals(expected, actual)은 두 인수가 한 값을 나타내는 것도 아니며, 개발자가 순서를 외워야만 한다.
- 이항함수는 불가피한 상황이 아니라면 단항 함수로 바꾸도록 노력해야 한다.
- 삼항 함수
- 상함 함수는 이항 함수보다 이해하는 것이 더욱 어렵기에 신중이 고려하여 만들어야 한다.
- 인수 객체
- 인수가 2~3개 이상인 경우 독자적인 클래스로 선언한다.
- 개념을 갖는 이름의 클래스로 만드는 것은 절대 무의미한 행동이 아니다.
- 인수 목록
- 인수 개수가 가변적인 함수도 필요하다.
- String.format은 가변인수를 사용한 좋은 예이다.
- 가변 인수를 취하는 모든 함수에 같은 원리가 적용된다.
- 가변 인수 전부를 동등하게 처리할 경우 하나의 인수로 취급할 수 있다.
- 동사와 키워드
- 함수의 의도나 인수의 순서와 의도를 제대로 표현하기 위해서 좋은 함수 이름이 필수다.
- 단항 함수는 함수와 인수가 동사/명사 쌍을 이루는게 좋다.
- write(name) - 이름을 쓰다.
- 함수 이름에 인수 이름을 넣으면 다항 함수의 인수 순서를 기억하지 않아도 된다.
부수 효과를 일으키지 마라
- 부수 효과는 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓을 하는 것이다.
- 예상치 못하게 클래스 변수나 인수 혹은 시스템 변수를 변경하는 것을 말한다.
- 이는 시간적인 결합, 순서 종속성을 초래한다.
- 특정한 일이 부수 효과로 숨겨진 경우 혼란이 커질 수 있다.
- 부수 효과가 있는 경우 함수 이름에 포함시킨다.
출력 인수
- 일반적으로 출력 인수는 피하는 것이 좋다.
- 함수에서 상태를 변경해야 한다면 함수가 속한 객체를 변경하는 방식을 사용한다.
명령과 조회를 분리하라
- 함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야한다.
- 둘 다 하는 것은 혼란을 초래한다.
- ex) boolean set(String key, String value)
- if (set(“name”, “hjhng”))와 같은 코드가 생기면 읽는 사람에게 혼란을 줄 수 있다.
오류 코드보다 예외를 사용하라
- 명령 함수에서 오류 코드를 반환하는 것은 명령/조회 원칙을 위반한다.
- 오류 코드를 받은 호출자는 바로 처리해야 한다는 문제에 부딪히게 됨.
- 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리된다.
- try-catch 블록은 정상 동작과 오류 처리 동작을 뒤섞기에 별도 함수로 뽑아내는 편이 코드를 이해하고 수정하기 쉽다.
- 정상 동작을 하는 함수를 분리하고 해당 함수는 발생할 여지가 있는 예외를 호출자에게 던진다.
- 호출자는 해당 예외를 처리한다.
- 예외 처리도 한가지의 작업이다.
- try가 시작된다면 함수는 catch/finally로 끝나야 한다.
- 예외 처리 외의 다른 기능은 없어야 한다.
반복하지 마라
- 반복은 코드의 길이를 증가시킬 뿐 아니라 관리 포인트를 여러 곳으로 만들게 된다.
- 따라서 특정 기능의 수정이 필요한 경우 모든 관리 포인트에 수정이 이루어져야 한다.
- 많은 기법과 원칙이 코드의 중복을 줄이기 위해 나왔다.
- 상속, AOP, COP 등등..
구조적 프로그래밍
- 모든 함수와 함수 내의 블록의 입구와 출구는 하나여야만 한다는 이론
- 함수는 return이 하나이고, loop안에서 continue, break 사용을 금하며 goto는 절대 안된다.
- 함수가 작다면 큰 이점이 없고 큰 함수에서 상당한 이익을 제공한다.
함수를 어떻게 짜죠?
- 첫 술에 배부를 수 없다.
- 특정 기능을 구현하면 길고 복잡할 수 있다.
- 이 코드의 단위 테스트를 하다보면 함수의 기능을 다른 함수로 나누거나 클래스 단위로 쪼갤 수 있다.
- 위 과정에서 중복되는 코드를 제거할 수 있으며 자연스레 사라지기도 한다.
댓글남기기