지우쓰 개발일기
[Clean Code] 3장 | 함수 본문
3장 | 함수
💻 작게 만들어라
"함수를 만드는 첫째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다." (p.42)
- 조건문에 들어가는 블록은 한 줄이어야 한다.
💻 한 가지만 해라
"함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다." (p.44)
- 저장된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행해야 한다.
- 의미 있는 다른 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하고 있는 것이다.
💻 함수 당 추상화 수준은 하나로
- 추상화 수준을 섞으면 표현이 근본 개념인지 세부사항인지 구분하기 어려워 헷갈리게 된다.
내려가기 규칙
- 위에서 아래로 이야기처럼 읽혀야 좋다.
- 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
💻 Switch 문
- 장황한 switch 문의 반복은 추상 팩토리 & 다형성 객체 생성 코드로 개선할 수 있다.
예) 목록 3-5. Employee and Factory
💻 서술적인 이름을 사용하라
"코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다." (p.49)
- 함수가 작고 단순해야 하고, 하는 일을 이름에 잘 표현해야 한다.
💻 함수 인수
"이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다." (p.50)
- 인수는 개념을 이해하기 어렵게 만든다.
- 별로 중요하지 않은 세부사항임에도 불구하고, 발견할 때마다 의미를 해석해야 하기 때문이다.
- 인수가 생기면 모든 경우의 수를 검증하는 테스트 케이스를 작성하기 복잡해진다.
인수를 없애는/줄이는 방법
- 인수를 현재 클래스의 static 변수로 만들어 인수로 넘기지 않는다.
- 별도의 클래스를 작성하여 인수를 생성자로 받아 그 클래스의 static 변수로 만들고, 그 클래스 내에서 메소드를 구현한다.
- 인수 클래스에 메소드를 추가하고 기존 클래스에서 객체.메소드 형태로 호출한다.
- 인수가 2-3개 필요하다면, 인수를 독자적인 클래스 변수로 선언한다.
단항 함수가 적절한 경우
- 인수에 질문을 던지는 경우
예) boolean fileExists("MyFile") - 인수를 뭔가로 변환해 결과로 반환하는 경우
예) InputStream fileOpen("MyFile") - 이벤트 (입력 인수로 시스템 상태를 바꾸는 경우)
예) void passwordAttemptFailedNtimes(int attempts)
이항 함수가 적절한 경우
- 인수 2개가 한 값을 표현하고, 자연적인 순서가 있는 경우
좋은 예) Point p = new Point(0,0)
나쁜 예) assertEquals(expected, actual)
삼항 함수가 적절한 경우
- 부동소수점 비교
예) assertEquals(1.0, amount, .001)
플래그 인수는 금지
- 각 플래그 값에 따라 별도의 함수를 작성하라.
예) render(boolean isSuite) → renderForSuite() & renderForSingleTest()
가변 인수
- 인수 개수가 가변적인 경우
예) String.format("%s worked %.2f hours.", name, hours);
동사와 키워드
- 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
예) write(name) - 함수 이름에 인수에 관한 키워드를 추가하라.
예) write(name) → writeField(name)
예) assertEquals() → assertExpectedEqualsActual(expected, actual)
💻 부수 효과를 일으키지 마라
- 예상치 못한 부수 효과는 시간적인 결합, 순서 종속성을 초래하게 된다.
- 함수는 한 가지 기능만 하도록 작성하라.
💻 명령과 조회를 분리하라
- 함수는 뭔가를 수행하거나, 뭔가에 답하거나 둘 중 하나만 해야 한다.
- 나쁜 예
if (set("username", "myname")) ...
- 좋은 예
if (attributeExists("username")) { setAttribute("username", "myname"); ... }
💻 오류 코드보다 예외를 사용하라
- 오류 코드를 반환하는 방식은 여러 단계로 중첩되는 코드를 야기한다.
- 오류 코드를 반환하는 방식은 오류 코드를 곧바로 처리해야 한다는 문제가 발생한다.
- 오류 코드를 정의하는 의존성 자석(magnet)은 재컴파일/재배치를 요구하기 때문에 번거로워진다.
Try/Catch 블록
- Try/Catch 블록을 별도 함수로 뽑아내고, try문 안에서 실제 '작업' 메소드를 호출한다.
- 실제 '작업'을 하는 코드에서는 thorws Exception하여 모든 예외 처리를 한 곳에서 처리한다.
💻 반복하지 마라
"어쩌면 중복은 소프트웨어에서 모든 악의 근원이다." (p.60)
- 코드 길이가 늘어난다.
- 알고리즘이 변하면 중복된 코드 모두 수정해야 한다.
- 오류가 발생할 확률도 몇 배로 높다.
💻 구조적 프로그래밍
- 모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다.
- 함수가 아주 클 때 구조적 프로그래밍의 목표와 규율이 효과적이다.
📝 느낀점
바람직한 함수 코드의 예로 저자가 제시한 목록 3-7 코드를 읽었을 때, 코드 흐름만 따라가면 거의 다 바로바로 이해가 돼서 신기했다. 변수가 무슨 뜻인지, 메소드가 무슨 일을 하는 지 고민할 필요 없이 바로 흐름과 관계를 파악할 수 있었다. 이렇게 의문이 들지 않는, 잘 써진 글과 같은 코드를 만들기 위해 고민을 많이 했다는 흔적이 느껴졌다.
이번 챕터에서는 소제목 반복하지 마라의 내용이 특히 공감이 되었다. 소스 코드에서 중복을 제거하려는 노력이 있었기에 지금의 프로그래밍 기법이 나올 수 있었다는 것이다. 최근 프로젝트에서 코드를 작성하면서, 조금씩 다르지만 사실은 같은 기능을 하는 메소드를 여러개 중복해서 작성한 적이 있다. 작성하면서도 스스로 이 중복으로 점칠된 더러운 코드가 나의 최선이냐며 자책했던 것이 기억이 난다. 여태는 중복된 부분을 고치고 싶어도 시간도 부족하고 올바른 방법을 모르니 일단 방치하고 넘어갔었다. 이제는 함수를 잘 작성하는 가이드를 읽고 배웠으니, 적극 참고해서 코드를 개선할 수 있을 것 같다.
또 한편으로, 나는 이렇게 깔끔한 코드를 쓴 경험이 없어서 걱정도 조금 됐다. 이렇게 좋은 코드를 쓰려면 얼마나 많은 시간을 투자해야 하는 건지, 한 모듈만 이렇게 리팩토링하는 것도 상당한 시간이 필요할 텐데 착착 해내는 능력이 없다면 현실적으로 직장에서 일을 하면서 이런 코드를 쓸 수 없는 게 아닐지 의문이 들었기 때문이다. 실제로 지인들의 인턴 경험을 들어 봤을 때, 이미 작성되어 있던 코드가 워낙 지저분해서 그거 이해하고 고치다가 몇 달이 다 가버렸다는 분들이 꽤 있었다. 현실적으로 고퀄리티의 결과물을 낼 수 있는 환경과 자원이 주어졌으면 좋겠다는 생각이 들었다.
저자는 이런 코드는 한 번에 뚝딱 만들어지는 것이 아니라, 다듬고 바꾸고 고치는 과정을 지나야 얻어지는 것이라고 한다. 클린 코드라는 책을 쓴 저자도 이런 말을 하는 것을 보니, 좋은 코드를 쓰기 위한 능력은 쉽게 얻어지는 것은 아닐 것 같다. 책만 읽고 그냥 넘어갈 것이 아니라, 시간을 많이 투자해서 내 코드들을 더 좋은 코드로 개선시키는 노력을 해야 겠다.
'기술독서 > Clean Code' 카테고리의 다른 글
[Clean Code] 7장 | 오류 처리 (0) | 2020.09.20 |
---|---|
[Clean Code] 6장 | 객체와 자료 구조 (0) | 2020.09.06 |
[Clean Code] 5장 | 형식 맞추기 (0) | 2020.09.06 |
[Clean Code] 2장 | 의미 있는 이름 (0) | 2020.09.06 |
[Clean Code] 1장 | 깨끗한 코드 (0) | 2020.09.06 |