[클린코드: Clean Code] 3장 함수

devssun 2019. 8. 5. 23:16
728x90
반응형

3장 함수

어떤 프로그램이든 가장 기본적인 단위가 함수다

 

함수를 만드는 규칙

1. 작게 만들어라!

예전에는 함수가 한 화면을 넘어가면 안된다고 했다. 

- 블록과 들여쓰기

if / else / while / guard 등에 들어가는 블록은 한 줄이어야 한다

중첩 구조가 생길만큼 함수가 커져서는 안된다

 

2. 한 가지만 해라!

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

 

함수가 '한 가지'만 하는지 확인하는 방법?

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

 

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

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

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

문단을 읽어내려 가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다.

 

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

함수가 작고 단순할 수록 서술적인 이름을 고르기도 쉬워진다

이름이 길어도 괜찮다

함수 이름을 정할 때는 여러 단어가 쉽게 읽히는 명명법을 사용한다

 

5. 함수 인수

함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개, 2개다. 3개는 가능한 피하는 편이 좋다. 

4개 이상(다항)은 특별한 이유가 필요하다. 있어도 사용하면 안된다.

 

- 단항 (1개)

함수에 인수 1개를 넘기는 이유 두 가지

1. 인수에 질문을 던지는 경우

func fileExists(filename: String) -> Bool

-> 파일이 있니?

 

2. 인수를 뭔가로 변환해 결과를 반환하는 경우

func fileOpen(filename: String) -> InputStream

-> String 형의 파일 이름을 InputStream 으로 변환

 

다소 드물게 사용하지만 아주 유용한 단항 함수 형식이 이벤트다. 이벤트 함수는 입력 인수만 있다

프로그램은 함수 호출을 이벤트로 해석해 입력 인수로 시스템 상태를 바꾼다

func passwordAttemptFailedNtimes(attempts: Int)

이벤트 함수는 조심해서 사용한다. 이벤트라는 사실이 코드에 명확히 드러나야 한다

 

위에 설명한 경우가 아니라면 단항 함수는 가급적 피한다

입력 인수를 변환하는 함수라면 변환 결과는 반환값으로 돌려준다

 

- 플래그 인수

플래그 인수는 추하다 

함수로 부울 값을 넘기는 관례는 끔찍하다 -> 왜? 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 알리는 셈이다

 

- 이항 함수

이항 함수가 적절한 경우

var p = Point(0, 0)

직교 좌표계 점은 일반적으로 인수 2개를 취한다. 여기서 인수 2개는 한 값을 표현하는 두 요소다

 

아주 당연하게 여겨지는 이항함수에도 문제가 있다

expression1 인수에 expression2 값을 집어넣는 실수가 많다. 두 인수는 자연적인 순서가 없어 expression1 다음에 expression2 가 온다는 순서를 인위적으로 기억해야 한다

XCTAssertEqual(expression1, expression2)

 

- 삼항 함수

인수가 3개인 함수는 인수가 2개인 함수보다 훨씬 더 이해하기 어렵다

- 인수 객체

인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어 본다

func makeCircle(x: Double, y: Double, radius: Double) -> Circle
func makeCircle(center: Point, radius: Double) -> Circle

x와 y를 묶어 Point로 표현하여 결국 개념을 표현하게 된다

- 동사와 키워드

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

단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다

 

6. 부수 효과를 일으키지 마라!

함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 것을 하지마라

 

7. 명령과 조회를 분리하라!

함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야 한다.

객체 상태를 변경하거나 객체 정보를 반환한다

 

아래의 set 함수는 이름이 attribute인 속성을 찾아 값을 value로 설정한 후 성공하면 true를 실패하면 false를 반환한다

If 문부터 보게되면 어떤 함수의 역할인지 알 수가 없다

func set(sttribute: String, value: String) -> Bool


if set(sttribute: "username", value: "unclebob") ...

 

이렇게 수정할 수 있다. 명령과 조회를 분리한다

한 눈에 봐도 username이라는 attribute를 찾은 후 unclebob으로 변경하라는 뜻으로 보인다

if attributeExists("username") {
    setAttribute("username", "unclebob")
}

 

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

명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다

자칫하면 If 문에서 명령을 표현식으로 사용하기 쉬운 탓이다

if deletePage(page) == E_OK

오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제에 부딪힌다

반면 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다

 

Try/Catch 블록 뽑아내기

try/catch 블록은 원래 추하다. 블록을 별도 함수로 뽑아내는 편이 좋다

func delete(page: Page) {
    do {
        deletePageAndAllReferences(page)
    }catch let error = error {
        logError(error)
    }
}

// --- 코드 분리 후

func deletePageAndAllReferences(page: Page) throws {
    deletePage(page)
    registry.deleteReference(page.name)
    configKeys.deleteKey(page.name.makeKey())
}

func logError(_ error: Error) {
    print(error.localizedDescription)
}

정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉬워진다

 

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

오류 처리도 '한 가지' 작업에 속한다

그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다

 

Error.java 의존성 자석(magnet)

오류 코드를 반환한다는 이야기는 클래스든 열거형이든 어디선가 오류 코드를 정의한다는 뜻이다

Error enum이 변한다면 해당 enum을 사용하는 클래스 전부를 다시 컴파일하고 다시 배치해야 한다

오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다

재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다

 

9. 반복하지 마라!

중복코드를 줄이는 노력을 하자

 

10. 구조적 프로그래밍

테이크스트라는 모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다고 말했다

함수는 return문이 하나여야 한다는 말이다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로 안된다

 

하지만 위 규칙은 함수가 아주 클 때만 상당한 이익을 제공한다

return, break, continue는 함수가 작을 때 여러 차례 사용해도 괜찮다. 

goto문은 큰 함수에서만 의미가 있으므로, 작은 함수에서는 피한다

 

11. 함수를 어떻게 짜죠?

소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다

 

먼저 생각을 기록한 후 읽기 좋게 다듬는다

초안은 대개 서투르고 어수선하므로 원하는 대로 말을 다듬고 문장을 고치고 문단을 정리한다

 

함수를 짤 때도 마찬가지다

일단 동작하는 코드를 작성한 후 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다.

물론 이때도 코드는 항상 단위 테스트를 통과한다

처음부터 정리된 코드가 만들어지진 않는다


클린코드 함수를 처음 읽은 날은 졸려서 정리를 하다가 자러 갔다..

가끔 코드 작성할 때 더 효율적으로 짜고싶어 으아!!!! 하다가 시간을 보낸적이 있는데

그러다 결국 일단 짜고 담에 고쳐~~~ 로 한다 ㅋㅋㅋ

아무튼 간에.. 이 방법이 맞는거고 리팩토링은 끝이 없이 꾸준히 해야하는 것!

반응형