effective-swift

item49. 매개변수가 유효한지 검사하라

메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건을 만족하기 바랍니다. 이런 제약들은 반드시 문서화해야 하며 메서드 몸체가 시작되기 전에 검사해야 합니다. 오류는 가능한 한 빨리(발생한 곳에서)잡아야 한다 는 일반 원칙의 한 사례이기도 합니다. 오류를 발생 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워집니다.

메서드 몸체가 실행되기 전에 매개변수를 확인해 잘못된 값이 넘어왔다면 즉각적으로 예외를 던지는 방식을 택할 수 있습니다. 만약 매개변수 검사를 제대로 하지 못하면 몇 가지 문제가 생길 수 있습니다. 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있고 더 나쁜 상황은 메서드는 문제없이 수행됐지만, 어떤 객체를 의도하지 않은 방향으로 만들어놔서 미래의 알 수 없는 시점에 이 메서드와 관련없는 에러를 내뱉을 때입니다.

매개변수 제약을 문서화한다면 그 제약을 어겼을 때 발생하는 예외도 함께 기술되어야 합니다. 이런 간단한 방법으로도 사용자가 제약을 지키도록 유도할 수 있습니다.

enum CaculateError: Error {
    case notDualNumber
}

enum ParameterError: Error {
  case invalidValue
}
/*
	- Parameters:
		- value: 계수(양수어야 함)
	- Throws: value가 0보다 작거나 같으면 에러 던짐
	- Returns: 현재 값 mod value
*/
func mod(value: Int) throws -> Int {
    guard value > 0 else { throw CaculateError.notDualNumber }

    // 계산 수행..
}

// 아래와같이 유효하지 않은 매개변수가 입력된 경우 즉각적으로 예외를 던질 수 있습니다
func mod(value: Int?) throws -> Int {
    guard let value = value else { throw ParameterError.invalidValue }

    // 계산 수행..
}

그리고 매개변수가 논옵셔널인 메서드에 옵셔널 매개변수를 넣고자 할 때는 빌드 경고 메시지가 떠 개발자가 유효한 매개변수를 입력할 수 있습니다.

메서드가 직접 사용하지는 않지만 나중에 쓰기 위해 저장하는 매개변수는 특히 더 신경써서 검사해야 합니다. 생성자의 경우에도 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는 데 꼭 필요합니다.

메서드가 호출되는 상황을 통제 할 수 있는 상황에서는 유효한 값만이 메서드에 인자로 넘겨지리라는 것을 보증할 수 있게 만들 수 있고 그렇게 해야만 합니다. 아래와같이 assert(_:) 를 사용해 매개변수 유효성을 검증할 수 있습니다.

private func sort(_ array: [Int], offset: Int, count: Int) {
    assert(!array.isEmpty)
    assert(offset >= 0 && offset <= array.count)
    assert(count >= 0 && count <= array.count - offset)
    // 계산 수행..
}

여기서 핵심은 이 단언문들은 자신이 단언한 조건이 무조건 참이라고 선언한다는 것입니다. 이 메서드가 선언된 객체를 사용하는 쪽에서 어떻게 사용하는가와는 상관이 없습니다. 이 assert(_:)는 디버깅모드에서만 작동하고 릴리즈모드에서는 제외됩니다. 그래서 이 메서드를 자주 사용해도 실제 프로덕션 앱의 성능에는 영향을 끼치지 않습니다. 그래서 프로그래머의 실수를 추적하는데 적합합니다. 주로 디버깅 중 조건의 검증을 위해 사용됩니다.

메서드 몸체 실행 전에 매개변수 유효성을 검사해야 한다는 규칙에도 예외는 있습니다. 유효성 검사 비용이 지나치게 높거나 실용적이지 않을 때, 혹은 계산 과정에서 암묵적으로 검사가 수행될 때입니다. 때로는 계산 과정에서 필요한 유효성 검사가 이뤄지지만 실패했을 때 잘못된 예외를 던지기도 합니다. 달리 말하면, 계산 중 잘못된 매개변수 값을 사용해 발생한 예외와 메서드 도움말에서 던지기로 한 예외가 다를 수 있다는 뜻입니다. 이런 경우에는 아이템 73에서 설명하는 예외 번역(exeption translate) 관용구를 사용해 메서드 도움말에 기재된 예외로 번역해주어야 합니다.

이번 아이템은 매개변수에 제약을 두는 게 좋다고 말하는 것이 아닙니다. 메서드는 최대한 범용적으로 설계해야 합니다. 메서드가 건네받은 값으로 제대로 된 일을 할 수 있다면 매개변수 제약은 적을수록 좋습니다. 하지만 구현하려는 개념 자체가 특정한 제약을 내재한 경우도 드물지 않습니다.

그러므로 메서드나 생성자를 작성할 때, 그 매개변수들에 어떤 제약이 있을지 생각해야 합니다. 그리고 그 제약들을 문서화하고 메서드 시작 부분에서 명시적으로 검사해야 합니다.