Clean Code - 3장 - 함수

Whale·2023년 3월 16일
0

CleanCode

목록 보기
3/6

어떤 프로그램이든 가장 기본적인 단위는 함수. 의도를 분명히 표현하는 함수를 구하기위한 가이드.

작게 만들어라

함수를 만드는 첫째 규칙은 작게. 두번째 규칙은 더 작게! 그렇다면 얼마나 짧아야 좋을까? 저자는 최대한 짧게. 각 함수가 이야기 하나를 표현해야한다고 말한다.

블록과 들여쓰기

  • 중첩구조가 생길만큼 함수가 커져서는 안된다.
  • 함수에서 들여쓰기의 수준은 1단이나 2단을 넘어서는 안된다.

그래야만 함수는 읽고 이해하기가 쉬워진다.

한가지만 해라!

함수는 한가지를 해야 한다. 그 한가지를 잘 해야 한다. 한가지만을 해야한다.

함수가 한가지만 판단하는 방법.

  • 단순히 다른 표현이 아니라 의미있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러작업을 하는 셈이다...?

함수당 추상화 수준은 하나로!

함수가 확실히 한가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야한다.

한 함수 내에서 추상화 수준을 섞으면 코드를 읽는 사람이 햇깔린다. 특정 표현이 근본 개념인지, 아니면 세부사항인지 구분하기 어려운 탓이다.

여기서 더 심각한 문제는 근본개념과 세부사항이 뒤섞였을때, 다음 함수는 점점 더 세부사항이 추가된다.

위에서 아래로 코드읽기: 내려가기 규칙

내려가기 규칙
코드를 작성할때 한 함수 다음에는 추상화 수준이 한단계 낮은 함수를 배치한다. 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 하나씩 내려가도록.

하지만 추상화 수준이 하나인 함수를 구현하기는 쉽지 않은 일! 핵심은 결국 한가지만 하는 함수를 만드는것.

Switch 문

Switch 문이 함수에 포함되게 되면, 함수당 하나의 기능을 담당하도록 할 수 없다. Switch 문이 본질적으로 N가지 일을 처리하기 때문에 하나의 작업만 하도록 구성할 수 없다.

(다형성...? 이게 뭘 말하는지 잘 모르겠다. 추상화 레벨을 높여서 이런 문제를 해결할 수 있다곤 하는데... 이게 의미 있는 일인가...?)

서술적인 이름을 사용하라!

testableHtml -> SetupTeardownIncluderReader

함수가 하는일을 좀 더 잘 표현함으로 훨씬 좋은 이름! 귀찮아서 짧게 쓰지 말고 좀 더 서술적으로. 읽는 사람이 의미를 파악할 수 있도록 명명한다.

(물론... 나는 영어가 힘들어서 서술적인 이름도 너무나 힘들다. 요즘 ChatGPT에게 이 함수이름 어때? 라고 물어보는 빈도가 늘었다...)

함수 이름은 길어도 괜찮다. 좋은 이름은 주석을 대신하기도 한다. 쉽게 읽히도록 생각하며 짤것.

이름을 붙일떄는 일관성있게. 모듈 내의 함수 이름은 같은 문구, 명사, 동사를 사용한다.

함수 인수

함수 인수에서 가장 이상적인 개수는 0개! 최대한 적을수록 좋다. 3개는 피하는게 좋고, 4개는 특별한 이유가 없으면 지양한다. 인수가 많아질수록 함수는 한번에 이해하기 어렵다.

테스트 관점에서도 인수가 많으면 각 인수의 조합으로 테스트를 만들어야하니 훨씬 어렵고 복잡하다.

(웁스... 그...렇...군...요...?)

많이 쓰는 단항 형식

함수에서 인수 1개를 넘기는 이유중 가장 흔한 2가지 경우

  • 인수를 통해 질문을 던지는 경우
    • func isVisible(view: UIView) -> Bool
  • 인수를 통해 뭔가로 변환된 결과를 반환하는 경우.
    • func makeUrl(base: String) -> URL
  • 혹은 입력만 있고 출력은 없는 이벤트형식
    • func setStatus(isSelected: Bool)

(2개라더니... 왜 3개인가요 선생님...?)

마지막의 입력만 있고 출력은 없는 이벤트 함수는 사용에 조심할것. 이벤트라는것이 코드에 명확히 드러나야 한다...?

플래그 인수

함수가 여러가지 일을 한다고 대놓고 말하는 셈이기 때문에 함수에 플래그(Bool) 값을 넘기는 관례는 끔찍하다.

(?!)

이항 함수

인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어렵다. 이항함수가 무조건 나쁘다는 소리는 아니지만, 그만큼 위험이 따른다는 사실을 이해하고, 가능하면 단항함수로 바꾸도록 노력해야한다.

삼항 함수

더더더욱 이해하기 어렵다. 가능하면 쓰지말아라.

인수 객체

인수가 2~N 개가 필요하다면 일부를 독자적인 클래스 변수로 선언할수도 있다. 객체를 생성해 인수를 줄이는 방법은 눈속임이라 여겨질지도 모르지만, 객체의 이름이 결국 개념을 표현하게 된다.

인수 목록

String(format: "%@, %@", A, B) 와 같이 인수를 리스트형으로 처리할수도 있다. 이는 실제론 다항 함수로 본다. 다만, 위에서 말한것처럼 최대 4가지를 넘지 않도록 유의할것

(모든 인수는 적을수록 좋다...)

동사와 키워드

함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수.

  • func write(name: String)
    • 이름(name) 을 쓴다(write)
  • func writeField(name: String)
    • 이름필드를 쓴다

부수적인 효과를 일으키지 마라!

함수가 동작할때 목적한 함수의 역할 이외의 다른 효과를 넣지 말자. 남몰래 다른짓을 하지 않도록...

아무리 함수 이름에 신경쓰고, 인수에 신경을 쓰더라도 그 구현에서 다른 효과를 일으킨다면 그것부터가 문제다. 함수의 작성은 언제나 최소단위로 쪼개고 기능하도록 구성할것.

명령과 조회를 분리하라!

함수는 뭔가를 수행하거나, 뭔가에 답하거나 둘중 하나만 해야한다. 둘다 하면 안된다.

  • func set(key: String, value: Any) -> Bool

set 에 성공하면 Bool 값을 반환. 이렇게 되면 설정을 위한 코드인지, 그 결과를 보기위한 코드인지 논란여지가 생긴다.

(함수가 하나의 기능만 하지 않는셈...! 하지만 Swift 에는 discardable 같은것도 있지...)

오류 코드보다 예외를 사용하라!

명령함수에서 오류코드를 반환하는 방식은 많이 사용하지만, 명령/조회 분리 규칙에서 미묘하게 어긋난다.

  • if deletePage(0) == Error

이럴경우엔 차라리 try / catch 를 이용해서 오류코드를 처리할 수 있도록 하자.

try {
  deletePage(0)
} catch error {
  print(error)
}

오류 처리도 한가지 작업이다.

함수는 한가지 작업만 해야하고, 오류처리도 이 한가지 적업에 속한다.

Error 의존성

열거형과 같은 형태로 Error 을 정의하게되면, 다른 클래스등에서도 이 에러에 의존하게 됨으로 의존성이 여기저기 엮이게 된다. (!!)

enum CustomError {
  case aError
  case bError
}

공통적으로 쓰게되는 에러클래스는 변경/재배치 또한 어려워지니 차라리 예외를 사용하는것이 좋다. 예외는 exception 클래스에서 파생되고 다른 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.

(잘 이해는 안가는데... 에러enum 에 의존성이 커지는건 큰 공감...! 사용법을 고민해볼 가치가 있다.)

반복하지 마라

중복은 소프트웨어에서 악의 근원. 수정이 발생하면 모든곳을 다 변경해야하고, 변경을 빠트리게 되면 버그가 된다. 중복을 제거하기 위해 끊임없이 노력할것.

구조적 프로그래밍

에츠히르 데이크스트라 의 구조적 프로그래밍 원칙
모든 함수와 함수 내 모든 블록에 입구와 출구는 하나만 존재해야한다.
루프 안에서 break 나 continue 를 사용하지 말고, goTo 와 같은것도 절대로 쓰지 말것.

(... 진짜...?)

함수를 어떻게 짜죠?

처음에는 길고, 복잡하고, 들여쓰기도 많고, 중복도 많고, 인수도 길지만... 이걸 점점 변경하고 테스트하며 코드를 다듬는다. 한번에 만들어지길 바라지 말자.

마무리

뭐 이렇게 찔리는게 많아...?!

profile
그저 오래된 iOS 개발자.

0개의 댓글