[Clean Code] 3장 | 함수

jwkim·2020년 11월 13일
0

Clean Code 정독하기

목록 보기
3/13
post-thumbnail

💻 작게 만들어라

"함수를 만드는 첫째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다." (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 코드를 읽었을 때, 코드 흐름만 따라가면 거의 다 바로바로 이해가 돼서 신기했다. 변수가 무슨 뜻인지, 메소드가 무슨 일을 하는 지 고민할 필요 없이 바로 흐름과 관계를 파악할 수 있었다. 이렇게 의문이 들지 않는, 잘 써진 글과 같은 코드를 만들기 위해 고민을 많이 했다는 흔적이 느껴졌다.

이번 챕터에서는 소제목 반복하지 마라의 내용이 특히 공감이 되었다. 소스 코드에서 중복을 제거하려는 노력이 있었기에 지금의 프로그래밍 기법이 나올 수 있었다는 것이다. 최근 프로젝트에서 코드를 작성하면서, 조금씩 다르지만 사실은 같은 기능을 하는 메소드를 여러개 중복해서 작성한 적이 있다. 작성하면서도 스스로 이 중복으로 점칠된 더러운 코드가 나의 최선이냐며 자책했던 것이 기억이 난다. 여태는 중복된 부분을 고치고 싶어도 시간도 부족하고 올바른 방법을 모르니 일단 방치하고 넘어갔었다. 이제는 함수를 잘 작성하는 가이드를 읽고 배웠으니, 적극 참고해서 코드를 개선할 수 있을 것 같다.

또 한편으로, 나는 이렇게 깔끔한 코드를 쓴 경험이 없어서 걱정도 조금 됐다. 이렇게 좋은 코드를 쓰려면 얼마나 많은 시간을 투자해야 하는 건지, 한 모듈만 이렇게 리팩토링하는 것도 상당한 시간이 필요할 텐데 착착 해내는 능력이 없다면 현실적으로 직장에서 일을 하면서 이런 코드를 쓸 수 없는 게 아닐지 의문이 들었기 때문이다. 실제로 지인들의 인턴 경험을 들어 봤을 때, 이미 작성되어 있던 코드가 워낙 지저분해서 그거 이해하고 고치다가 몇 달이 다 가버렸다는 분들이 꽤 있었다. 현실적으로 고퀄리티의 결과물을 낼 수 있는 환경과 자원이 주어졌으면 좋겠다는 생각이 들었다.

저자는 이런 코드는 한 번에 뚝딱 만들어지는 것이 아니라, 다듬고 바꾸고 고치는 과정을 지나야 얻어지는 것이라고 한다. 클린 코드라는 책을 쓴 저자도 이런 말을 하는 것을 보니, 좋은 코드를 쓰기 위한 능력은 쉽게 얻어지는 것은 아닐 것 같다. 책만 읽고 그냥 넘어갈 것이 아니라, 시간을 많이 투자해서 내 코드들을 더 좋은 코드로 개선시키는 노력을 해야 겠다.

0개의 댓글