관리 메뉴

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

[iOS - swift] Clean Code(클린 코드) - 3. 함수 (2) 본문

Clean Code (클린 코드)

[iOS - swift] Clean Code(클린 코드) - 3. 함수 (2)

jake-kim 2021. 11. 13. 13:36

짧은 이름보다는 서술적인 이름을 선택

  • 함수를 구현할 때 항상 함수의 이름을 확인하면서, 함수의 이름에 부합하는 작업만 포함하고 있는지 확인할 것
  • 길지라도 서술적인 이름이 지향
    • 함수가 하는 일을 좀 더 잘 표현
    • 좋은코드는 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행하도록 짜는 코드이므로 서술적인 코드가 짐작하기가 용이
    • ex) SetupTeardownIncluder, isTestable(), includeSetupAndTeardownPages()

모듈 내에서의 일관성

  • 모듈 내에서함수 이름은 같은 문구, 명사, 동사를 사용
class IncludeModule {
	func includeSetupAndTeardownPages() {}
	func includeSetupPages() {}
	func includeSuiteSetupPage() {}
	func includeSetupPage() {}
}

함수의 인수

  • 함수에서 이상적인 인수 개수는 0개이므로 최대히 적게 사용하는것이 중요
  • 인수가 4개를 사용할땐 특별한 이유가 필요 
    • 인수는 개념을 이해하기 어렵기 만들기 때문에 지양
    • 인수가 생겨나면 본래의 함수 이름과 인수 사이의 추상화 수준이 달라지는 현상이 발생
인수가 있는 함수 인수가 없는 함수
includeSetupPageInfo(pageContent: PageContent) includeSetupPageInfo()
  • 인수가 있는 함수에서 코드를 입는 사람은 pageContent 인수를 발견 할때마다 의미를 해석해야한다는 점이 존재
  • testable 코드를 구성할 때 인수가 많아지면, 인수에 따른 변수가 여러가지가 더 생기므로 테스트 관점에서 안좋은 코드

인수가 1개인 함수

  • 아래 2가지 형태가 아니면 가급적 인수를 사용하지 말것

1) 인수에 질문을 던지는 형태

func isExistsFile(_ fileName: String) -> Bool {}

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

func transformToString(number: Int) -> String {}
  • 절대 피해야하는 형태 - 플래그 인수
    • 함수로 Boolean값을 넘긴다는 것은 함수 안에서 한꺼번에 여러 가지를 처리한다고 가정하는 것이므로 피해야되는 것을 주의

인수가 2개인 함수

  • 인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어려운 점
인수가 1개인 함수 호출 인수가 2개인 함수 호출
writeField(name) writeField(outputStream, name)
  • 인수가 2개로 사용될 수 밖에 없는 아래 케이스를 제외한 것들은 가능한 인수가 1개가 되도록 수정
let p = Point(0, 0)
  • 인수 2개 > 1개로 변환 방법: 해당 인수의 클래스에 메소드로 추가
    • 인수가 2개인 함수: writeField(outputStream, name)
    • 인수가 1개인 함수로 변환: outputStream.writeField(name)

여러개의 인수인 경우, 인수 객체로 변환

  • 인수가 3개 이상이 되면 이해하기가 어려워지므로 인수 객체를 만들어서 사용
인수가 4개인 경우 인수 객체를 이용한 방법
func makeCircle(x: Double, y: Double, radius: Double) -> Circle  func makeCircle(center: Point, radius: Double) -> Circle

함수에 Side Effect가 있는 경우

  • 함수안에서 다른것에 영향을 주는 Side Effect를 최대한 지양해야 하고, 어쩔수 없이 있는 경우에는 함수의 이름에 명시해주는것이 중요

ex) 유저의 패스워드를 찾아서 있다면 session을 초기화해주는 함수

Side Effect가 있는 함수 - checkPassword(userNmae:password:)는 이름만 봤을때 패스워드만 확인하는 것이지만 내부에서 Session.initialize()를 호출하게되어 session을 초기화하는 형태

class UserValidator {
    private let cryptographer: Cryptographer
    init (cryptographer: Cryptographer) { 
        self.cryptographer = cryptographer
    }
    
    func checkPassword(userName: String, password: String) -> Bool {
        let user = UserGateway.findByName(userName)
        guard let user = user else { return false }
        let codedPhase = user.getPhraseEncodedByPassword()
        let phrase = cryptographer.decrypt(codedPhase, password)
        if ("Valid Password".contains(phrase)) {
            Session.initalize()
            return true
        }
    }
}

> 함수 이름을 변경하여 Side Effect를 예상하도록 변경 "checkPasswordAndInitializeSession"

오류 코드보다 예외 코드를 지향

  • 오류 코드보다 try - catch 예외 처리를 사용해야 하는 이유
    • 오류를 처리하는 코드가 원래의 코드에서 분리할 수 있기 때문에 더욱 코드가 깔끔해지게 되기 때문

WRONG

- 오류를 처리하는 부분과 원래의 코드가 같이 있어서 읽기 어려운 형태

if deletePage(page) == E_OK {
    if registry.deleteReference(page.name) == E_OK {
        if configKeys.deleteKey(page.name.makeKey()) == E_OK {
            logger.log("page deleted")
        } else {
            logger.log("configKey not deleted")
        }
    } else {
        logger.log("deleteReference from registry failed")
    }
} else {
    logger.log("delete failed")
    return E_ERROR
}

RIGHT

- 오류를 처리하는 부분과, 원래의 코드가 분리된 형태

try {
    deletePage(page)
    registry.deleteReference(page.name)
    configKeys.deleteKey(page.name.makeKey())
} catch {
    logger.log(error)
}

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

저자인 로버트 C. 마틴이 함수를 짜는 방식

  • 작가들의 글짓기와 비슷하게, 처음에는 길고 복잡하게 들여쓰기 단계도 많고 중복도 많고, 인수 목록이 길고 이름도 대충 구현
  • 이런 함수들을 처음에 짤때 서투른 코드들에 대해서 unit test를 만드는 것은 빼먹지 않고 진행
  • 코드를 다듬고 함수를 만들고 이름을 바꾸는 작업을 진행, 테스트 코드가 통과하는지 계속 확인

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

Comments