클린 코드 3장 - 함수

French Marigold·2023년 11월 10일
0

클린코드

목록 보기
3/13

함수를 작게 만들어라 (42p)

  • 얼마나 작게 만들어야 하냐면 20줄 이하로 만들어야 한다. 20줄도 길다.
  • 2, 3, 4줄 정도의 함수가 가장 좋다.
  • if else, while문 안에 들어가는 블록은 한 줄이어야 한다.
    • 즉, 중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻이다.

한 가지만 해라 (44p)

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

함수 당 추상화 수준은 하나로 (45p)

Switch문을 함수 내에 사용하면 발생하는 문제점들 (47p)

  1. 함수가 길어진다. Switch문에 case를 추가하면 함수가 더 길어진다.
  2. 함수가 ‘한 가지’ 작업만 수행하지 않는다.
  3. 함수가 SRP(단일 책임 원칙)을 위반한다.
  4. 함수가 OCP(개방 폐쇄의 원칙)을 위반한다. 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 하는데 새 case를 추가할 때마다 코드를 변경하기 때문이다.
  • Switch 문을 함수 내에서 사용하려 한다면 추상 팩토리 패턴을 이용해 필요할 때만 선택적으로 switch 문을 사용하게끔 하고 나머지 때에는 꽁꽁 숨긴다.

서술적인 이름을 사용하라 (49p)

  • 길고 서술적인 함수 이름이 짧고 어려운 이름보다 좋다.
  • 길고 서술적인 함수 이름이 길고 서술적인 주석보다 좋다.
  • 서술적인 이름을 사용하면 머릿 속에서 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
  • 모둘 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
    • includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage 등으로 만들면 된다. (include 동사로 맞췄음)

함수 파라미터 (50p)

  • 함수에서 이상적인 파라미터 개수는 0개다. 1개까지도 괜찮다.
  • 3개부터는 가능한 피하는 편이 좋다.
  • 4개 이상은 특별한 이유가 있어도 사용하면 안 된다.
  • 함수 파라미터가 없으면 테스트 케이스를 작성하기 훨씬 쉽다.
  • 많이 사용하는 단항 형식은 다음과 같다.
    • 파라미터에 질문을 던지는 경우

      func fileExists(_ fileName: String) -> Bool {
      		// 코드 내부 
      		if FileManager.default.fileExists(at path: fileName) {
      				return true
      		} else {
      				return false
      		}
      }
      
      fileExists("MyFile") // 파일이 존재하면 true, 파일이 존재하지 않으면 false
    • 파라미터를 뭔가로 변환해 결과로 반환하는 경우

      func makeIntToString(_ number: Int) -> String {
      		let intToString = "\(number)"
      
      		return intToString
      }
      
      makeIntToString(3) // Int 타입 3을 String 타입 "3"으로 변환
    • 이벤트 함수를 사용하는 경우 (입력 파라미터만 있고 리턴 파라미터는 없는 함수)

      func passwordAttemptFailedNtimes(attempts: Int) {
      		
      }
  • 플래그 파라미터
    • 파라미터에 Bool 값을 넘기는 건 최악이다. 왜냐하면 함수가 여러 가지를 처리할 것이라고 공표하는 것이기 때문이다. true일 경우와 false 일 경우 처리할 코드가 다르다.

    • 플래그 파라미터는 코드의 가독성을 저하시키고, 함수의 복잡성을 증가시킬 수 있으므로, 사용에 주의해야 한다. 특히, 하나의 함수가 여러 개의 플래그 파라미터를 가질 경우, 함수가 너무 많은 책임을 지게 되어 코드의 유지 보수가 어려워질 수 있다.. 이런 경우, 함수를 분리하거나 다른 방식으로 리팩토링을 고려하는 것이 좋다.

      func processDocument(_ document: Document, shouldBackup: Bool) {
          if shouldBackup {
              backup(document)
          } else {
      				print("백업 못함")
      		}
          // document 처리 코드...
      }
  • 이항 함수

    • 이항 함수는 단항 함수보다 이해하기 어렵다. 그래서 오류를 일으킬 가능성이 높다.
    • 이항 함수가 적절한 경우는 x, y 좌표 같은 것을 표현할 때이다.
      • 여기서 인수 두 개는 한 값을 표현하는 두 요소이다.
      • 그러나 writeField(outputStream, name)같은 함수는 한 값을 표현하는 함수가 아니다. 서로 다른 별 개의 값을 인수로 사용하기 때문에 부적절하다.
    • 가능하면 이항 함수에서 단항 함수로 바꾸려고 애써야 한다.
      • 예를 들어, writeField(outputStream, name) 함수일 경우, writeField 메소드를 outputStream 클래스 구성원으로 만들어 outputStream.writeField(name)으로 호출한다.
  • 삼항 함수

    • 이항 함수보다 훨씬 이해하기 어려우므로, 신중하게 사용하는 것을 추천한다.
  • 동사와 키워드

    • 단항 함수는 함수와 파라미터가 동사(함수)/ 명사(파라미터) 쌍을 이루어야 한다.
      • write(name), writeField(name)

부수 효과를 일으키지 마라 (54p)

  • 예를 들어, 한 가지만 하겠다는 함수가 다른 클래스의 변수를 수정한다던지, 함수로 넘어온 전역 변수를 수정한다던지 등의 행동을 해서는 안 된다. 함수는 그냥 한 가지 일만 하는 게 좋다.
  • 만일 부수 효과를 일으키는 함수라면 반드시 함수 이름에도 그 부수 효과를 기재해야 한다.
    • 아래 코드와 같은 경우 checkPassword가 아니라 checkPasswordAndInitializeSession 으로 바꿔줘야 한다. 물론 함수가 한 가지만 한다는 규칙을 위반하지만.
import Foundation

class UserValidator {
    private var cryptographer: Cryptographer

    init(cryptographer: Cryptographer) {
        self.cryptographer = cryptographer
    }
		
		// 단순히 password를 체크하는 함수로만 보이지만 
    func checkPassword(userName: String, password: String) -> Bool {
        guard let user = UserGateway.findByName(userName), user != User.null else {
            return false
        }

        let codedPhrase = user.getPhraseEncodedByPassword()
        let phrase = cryptographer.decrypt(codedPhrase: codedPhrase, password: password)

        if phrase == "Valid Password" {
						// 세션 정보를 초기화하는 코드가 숨어있다! 이런 부수 효과를 일으키면 안 된다. ⭐️⭐️
						// password를 체크하는 역할만 해야지, 세션 정보를 초기화하는 부수 효과를 일으켜서는 안 된다. 
            Session.initialize() 
            return true
        }

        return false
    }
}

명령과 조회를 분리하라 (56p)

  • 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안 된다.
  • 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. 둘 다 하면 혼란을 초래한다.
func set(_ attribute: String, _ value: String) -> Bool {
		// 코드 로직 구현 
}

// 무엇을 말하고자 하는지 한 번에 이해하기 힘들다. 
if set("username", "uncleBob") {
		
}

==================================================================================

// 잘 만든 코드는 다음과 같이 객체를 변경하는 함수와 객체를 반환하는 함수를 분리한다. 
func attributeExists(_ attribute: String) -> Bool {

}

func setAttribute(_ attribute: String, _ value: String) {

}

if attributeExists("userName") {
		setAttribute("userName", "uncleBob")
}

오류 코드보다 예외를 사용하라 (57p)

  • 오류를 일일이 처리하는 코드 대신 do try catch 구문을 사용하면 코드가 훨씬 깔끔해진다.
  • do try catch 구문을 정상 동작과 오류 동작을 뒤섞으므로 추한 코드이긴 하다. 그러므로 do try catch 블록을 별도 함수를 뽑아내는 편이 좋다.
    • 오류를 처리하는 함수는 오류만 처리해야 한다.
func delete(page: Page) {
		do {
				try deletePageAndAllReferences(page)
		} catch (let error) {
				print("에러가 발생했습니다. \(error)")
		}
}

반복하지 마라 (60p)

  • 코드가 중복되면 안 된다.

결론 (62p)

  • 대가 프로그래머는 시스템을 구현할 프로그램이 아니라 풀어나갈 이야기로 여긴다.
  • 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억하자!
profile
꽃말 == 반드시 오고야 말 행복

0개의 댓글