Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Clean Code(클린 코드) - 12. 냄새와 휴리스틱 (3) (이름, 테스트) 본문

Clean Code (클린 코드)

[iOS - swift] Clean Code(클린 코드) - 12. 냄새와 휴리스틱 (3) (이름, 테스트)

jake-kim 2021. 11. 29. 23:58

* 냄새와 휴리스틱: 파틴 파울러의 "Refactoring"에서 "코드 냄새"를 표현하여 주의해야하는 코드 사항들에 대해 나열한 것에 더하여 저자 로버트 C.마틴의 생각을 더한 것

이름

  • 서술적인 이름을 사용할 것
    • 소프트웨어가 진화할 경우 의미도 변하므로 선택한 이름이 적합한지, 성급하지 않고 신중하게 고를 것
    • 로버트 C마틴에 의하면, 소프트웨어 가독성의 90%는 이름이 결정
    • 주석을 통해 추가 설명을 하려고 하기전에, 먼저 서술적인 이름으로 코드를 구성하는 방향으로 구현하면 다 읽지 않아도 유추하기가 쉬워져서, 코드 생산성에도 긍정적인 영향을 부여

RIGHT

- 서술적인 이름으로 인해 함수의 이름만 보고도 내용을 예측할수 있는 형태

private let ballCountToStrike = 10

private isStrike(frame: Int) -> Bool {
    return rolls[frmae] == ballCountToStrike
}

  • 적절한 추상화 수준에서 이름을 선택할 것
    • 추상화 수준이 너무 낮은 변수 이름은 확장 가능성을 제한하므로 발견하면 추상화 수준을 높게 바꾸는 것으로 지향

WRONG

- phoneNumber라는 추상화 수준이 낮은 변수 이름을 사용하여, 전화번호가 아닌 포트 번호로 통신하는 경우 확장성에 제한되는 코드

protocol {
    func dial(phoneNumber: String) -> Bool
    func disconnect() -> Bool
    func send(message: String) -> Bool
    func recv() -> String
    func getConnectedPhoneNumber() -> String
}

RIGHT

- phoneNumber 이름을 변경하여 연결 대상의 이름을 전화번호로 제한하지 않고, 다른 연결 방식에도 확장 가능하도록 수정

protocol {
    func connect(connectionLocator: String) -> Bool
    func disconnect() -> Bool
    func send(message: String) -> Bool
    func recv() -> String
    func getConnectedLocator() -> String
}

  • 함수, 변수 이름이 길지라도 서술적이고 명확한 이름이 된다면 변경할 것

WRONG

- doRename이라는 이름은 명확하지 않고 아주 광범위하고 모호한 형태

private func doRename() -> String {
    if refactorReferences {
        renameReferences()
    }
    renamePage()
    
    pathToRename.removeNameFromEnd()
    pathToRename.addNameToEnd(newName)
    return PathParser.render(pathToRename)
}

RIGHT

- 함수 이름을 doRename에서 renamePageAndOptionallyReferences로 변경 (길더라도 서술적이고 명확한 형태)

private func renamePageAndOptionallyReferences() -> String {
    if refactorReferences {
        renameReferences()
    }
    renamePage()
    
    pathToRename.removeNameFromEnd()
    pathToRename.addNameToEnd(newName)
    return PathParser.render(pathToRename)
}

  • 코드 rows의 길이가 짧은 함수에는, 함수 안의 변수들의 이름을 짧게 지어도 괜찮지만, rows길이가 길면 긴 이름을 사용할 것

RIGHT

- rows가 5줄밖에 안되므로, i와 j같은 짧은 변수 이름을 사용해도 무방

private func rollMany(n: Int, pins: Int) {
    for i in 0..<n {
        g.roll(pins)
    }
}

  • 함수 블록에서 발생하는 side effect를 함수 이름만 보고도 유추할 수 있도록 함수 이름에 side effect의 내용이 들어가도록 할 것

WRONG

- 함수 블록 내부에서, 단순히oos만 가져오지 않고 값이 없는 경우에는 oos를 생성하는 함수지만, 함수이름에는 단순히 getOos이고 oos를 생성하는 side effect내용을 알 수 없는 상태

func getOos() -> ObjectOutputStream {
    if let oos = oos {
        return oos
    } else {
        return ObjetOuputStream(socket.getOutputStream())
    }
}

RIGHT

- 함수 이름을 getOos에서 createOrReturnOos로 변경하여, 함수 이름만 봐도 side effect을 알 수 있는 형태

func createOrReturnOos() -> ObjectOutputStream {
    if let oos = oos {
        return oos
    } else {
        return ObjetOuputStream(socket.getOutputStream())
    }
}

테스트

  • 유닛 테스트 코드를 충분히 만들 것
    • 대부분의 개발자들이 "이 정도면 충분하지 않을까"라는 척도를 사용하지만, 테스트 케이스는 잠재적으로 깨질만한 부분을 모두 테스트해야 안전한 코드 확립 가능
  • 사소한 테스트도 건너뛰지 말고 진행할 것
    • 테스트 코드는 단순히 테스트, 변경에 유연한 코드일 뿐만이 아니라, 문서적 가치를 제공하므로 구현에 드는 비용보다 값진 것
  • 특정 함수에서 버그가 하나 나왔다면, 다른 버그도 나올 가능성이 높으므로 테스트 코드의 수를 늘릴 것
  • 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛰어야 하므로, 테스트 코드 구현 시 빠르게 돌아가게끔 할것

cf) 테스트 개념: Clean Code(클린 코드) - 8. Unit Test (단위 테스트)

 

* 참고: Clean Code (로버트 C. 마틴)

Comments