11. 기본(The Basic) - 에러처리와 역설, 전제조건(Error Handling, Assertions & Preconditions)

이경은·2024년 1월 10일
0

요약

1. 에러처리(Error Handling)
- 목적 : 예외적인 상황 또는 오류가 발생했을 때, 프로그램이 정상적으로 계속 실행될 수 있도록 하는 메커니즘.
- 키워드 및 함수 : throws, do, catch, try
- 사용 시점 : 예외적인 상황이 예측 가능하며, 프로그램이 계속 실행될 수 있는 대안적인 조치를 취할 수 있는 경우에 사용.


2. 역설을 통한 디버깅(Debugging with Assertions)
- 목적 : 디버깅 중에만 활성화되며, 개발자가 가정한 조건이 만족되지 않으면 프로그램을 종료시켜 예상치 못한 문제를 조기에 발견하는데 사용되는 메커니즘.
- 키워드 및 함수 : assert, assertionFailure
- 사용 시점 : 개발자가 프로그램의 로직에 대한 가정을 확인하고 싶거나, 디버깅 중에 잘못된 가정이 발견되면 프로그램이 즉시 종료되야 하는 경우에 사용.


3. 강제 전제조건(Enforcing Preconditions)
- 목적 : 프로그램 실행 전, 반드시 만족해야 하는 조건을 검사하여, 실행 중에 발생할 수 있는 예상치 못한 문제를 방지하는데 사용되는 메커니즘.
- 키워드 및 함수 : precondition, preconditionFailure
- 사용 시점 : 프로그램이 실행되기 전에 반드시 충족되어야 하는 조건이 있을 때 사용. 예를 들면 함수에 전달되는 매개변수의 유효성 검사 등에 사용됨.




에러처리(Error Handling)

프로그램이 실행되는 동안 에러가 발생할 때, 이를 처리하기 위해 에러처리(Error Handling)를 사용합니다.

값의 존재 유무를 사용하여 함수의 성공 또는 실패를 전달할 수 있는 옵셔널과 달리 에러처리를 사용하면 에러 원인을 판별하고 필요한 경우 에러를 프로그램의 다른 부분으로 전파할 수 있습니다.

함수에 에러조건이 갖춰지면 에러가 발생합니다. 해당 함수의 호출자는 에러를 포착하고 적절하게 응답할 수 있습니다.

func canThrowAnError() throws {
	// 이 함수는 발생한 에러를 throw하거나 throw하지 않을 수 있습니다.
}

함수는 선언에 throws키워드를 포함시켜 에러가 발생할 수 있음을 나타냅니다. 에러를 발생할 수 있는 함수를 호출할 때는 표현식 앞에 try키워드를 붙여야 합니다.

Swift는 catch구문에 의해 처리될 때까지 현재 범위에서 에러를 자동으로 전파합니다.

do {
	try canThrowAnError()
	// 에러가 던져지지 않았을 때    
} catch {
	// 에러가 던져졌을 때
}

do구문은 에러를 하나 이상의 catch구문으로 전파할 수 있는 새로운 범위를 만듭니다.
다음은 에러처리를 사용하여 다양한 에러 조건에 응답하는 방법의 예입니다.

enum SandwichError: Error {		// 샌드위치에 발생할 수 있는 예외 상황을 처리할 Custom 에러타입을 정의
	case outOfCleanDishes
    case missingIngredients(ingredients: [String])
}

// 각 조건에 대응할 '샌드위치 먹기' 함수, '그릇 씻기' 함수, '재료 사기' 함수를 정의
func eatASandwich() {print("Enjoying the sandwich!")}
func washDishes() {print("Washing dishes.")}
func buyGroceries(ingredients: [String]) {print("Buying groceries: \(ingredients.joined(separator: ", "))")}

// 샌드위치 만들기 함수 정의. 
// cleanDishesAvailable가 참이고, ingredientsAvailable 배열이 "bread", "cheese", "ham"을 포함한다면 "Sandwich is ready!"를 출력.
func makeASandwich() Throws {
	let cleanDishesAvailable = true
    let ingredientsAvailable = ["bread", "cheese", "ham"]
    
    // cleanDishesAvailable이 false일 때, outOfCleanDishes를 throw한다.
    guard cleanDishesAvailable else {
    	throw SandwichError.outOfCleanDishes
    }
    
    // ingredientsAvailable 배열에 "bread", "cheese", "ham" 중 하나라도 없다면, missingIngredients(ingredients: ["bread", "cheese", "ham"]를 throw한다.
    guard ingredientsAvailable.contains("bread") && ingredientsAvailable.contains("cheese") && ingredientsAvailable.contains("ham") else {
    	throw SandwichError.missingIngredients(ingredients: ["bread", "cheese", "ham"])
    }
    
    // Custom 에러 타입에 해당되는 것이 없다면 실행할 구문.
    print("Sandwich is ready!")
}

// 만약 에러가 발생할 수 있는 makeASandwich 함수에서 에러가 throw되었다면, 아래 do-catch구문에서 처리됩니다.
func processSandwich() {
	do {
      try makeASandwich()						// 에러가 발생할 수 있는 함수는 try구문에 래핑
      eatASandwich()							// 해당 함수가 에러를 발생시키지 않았다면 실행
  } catch SandwichError.outOfCleanDishes {
      washDishes()								// outOfCleanDishes 커스텀 에러가 throw되었을때 실행
  } catch SandwichError.missingIngredients(let ingredients) {
      buyGroceries(ingredients: ingredients)	// missingIngredients 커스텀 에러가 throw되었을때 실행
  } catch {
  	print("An unknown error occured.")			// 커스텀 에러에서 명시하지 않은 예외상황에서 실행
  }
}

processSandwich()								// 함수 호출

위 예시에서 makeASandwich()함수는 깨끗한 그릇의 유무나 재료의 유무에 따라 SandwichError가 발생할 것입니다. 이처럼 에러를 발생시킬 수 있는 함수는 try표현식으로 래핑됩니다. 함수 호출을 do구문으로 래핑하면 어떠한 에러도 catch절로 전파됩니다.

에러가 발생하지 않으면 eatASandwich()함수가 호출됩니다.
SandwichError.outOfCleanDishes에러가 발생하면 washDishes()함수가 호출됩니다.
SandwichError.missingIngredients에러가 발생하면 catch패턴에 의해 캡쳐된 [String]값과 함께 buyGroceries(_:)함수가 호출됩니다.
마지막 catch절은 코드의 안정성을 높이기 위해 사용되었습니다. SandwichError에서 명시하지 않은 예외상황이 발생한 경우"An unknown error occured."를 출력하고, 프로그래머가 이에 대응할 수 있도록 하는 역할을 합니다.

에러발생, 포착, 전파는 에러처리(Error Handling)에서 자세하게 다룰예정입니다.




역설과 전제조건(Assertions and Preconditions)

역설과 전제조건(Assertions and Preconditions)은 런타임시 발생하는 조건입니다. 추가 코드를 실행하기 전에 이를 사용하여 필수조건이 충족되는지 확인할 수 있습니다. 역설 또는 전제조건의 부울 조건이 true이면 코드는 평소와 같이 진행됩니다. 조건이 false로 판단되면 프로그램의 현재 상태는 유효하지 않아 코드 실행은 종료되고 앱은 종료됩니다.

역설과 전제조건은 가정과 기대치를 표현하므로 코드의 일부로 포함할 수 있습니다. 역설은 개발과정에서 실수와 잘못된 가정을 찾는데 도움이 되고 전제조건은 프로적션 문제를 감지하는데 도움이 됩니다.

런타임 시 기대치를 확인하는 것 이외에 역설과 전제조건은 또한 코드 내에서 유용한 문서 형식이 됩니다. 위의 에러 처리(Error Handling)와 다르게 역설과 전제조건은 복구 가능하거나 예상되는 에러에 사용되지 않습니다. 실패한 역설 또는 전제조건은 유효하지 않은 프로그램 상태를 나타내기 때문에 실패한 상태를 잡을 방법은 없습니다. 유효하지 않은 상태에서 복구하는 것은 불가능합니다. 역설이 실패하면 프로그램의 데이터 중 하나가 유효하지 않다는 의미입니다. 그러나 그것이 왜 유효하지 않은지 추가로 다른 상태도 유효하지 않은지 알 수 없습니다.

역설과 전제조건을 사용하는 것은 유효하지 않는 조건이 발생하지 않게 코드를 디자인하기 위함입니다. 그러나 유효한 데이터 및 상태를 적용하기 위해 이를 사용하면 유효하지 않은 상태가 발생하면 앱이 종료되기 때문에 더 쉽게 문제에 대해 디버깅 할 수 있습니다. 가정을 확인하지 않으면, 다른 코드가 실패하기 시작하고, 사용자 데이터가 손상된 후에야 이런 종류의 문제를 알 수 있습니다. 유효하지 않은 상태가 감지되는 즉시 실행을 중지하면 해당 유효하지 않은 상태로 인한 피해를 제한하는데 도움이 됩니다.

역설과 전제조건의 차이점은 언제 체크되는지에 있습니다. 역설은 오직 디버그 빌드에서 체크되지만, 전제조건은 디버그와 프로적션 빌드에서 체크됩니다. 프로덕션 빌드일 때, 역설 내부의 조건은 실행되지 않습니다. 이 의미는 프로덕션에서 성능의 영향이 없이 개발단계에서 많은 양의 역설을 사용할 수 있다는 뜻입니다.




역설을 통한 디버깅(Debugging with Assertions)

Swift 표준 라이브러리에 assert(_:_:file:line:)함수로 역설을 작성할 수 있습니다. 이 함수에 true 또는 false로 판단될 표현식과 조건이 false일 경우 출력될 메세지를 전달합니다.

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// 이 역설은 조건이 false 이므로 실패하며, "A person's age can't be less than zero."를 출력하고 종료됩니다. 

위 예시에서 age가 0보다 크거나 같을 때, 조건인 age >= 0true이므로 프로그램은 이어서 실행됩니다. age가 0보다 작을 때, 조건인 age >= 0false이므로 역설은 실패, 프로그램은 종료됩니다.

예를 들어 평범하게 조건만 반복될 때 메세지를 생략할 수 있습니다.

assert(age >= 0)

코드가 이미 조건이 체크되었다면, 역설이 실패되었는지를 알 수 있는 assertionFailure(_:file:line:)함수를 사용합니다.

if age > 10 {
	print("You can ride the roller-coaster or the ferris wheel.")
} else if age >= 0 {
	print("You can ride the ferris wheel.")
} else {
	assertionFailure("A person's age can't be less than zero.")
}



강제 전제조건(Enforcing Preconditions)

조건이 거짓일 가능성이 있을 때마다 전제조건을 사용하지만 코드가 순차적으로 실행되려면 확실하게 참이어야 합니다. 예를 들어 어떤 값들이 범위를 벗어나는지 또는 함수에 유효한 값이 전달되는지 체크하기 위해 전제조건을 사용합니다.

precondition(_:_:file:line)함수로 전제조건을 작성할 수 있습니다. 이 함수에 true또는 false로 판단될 표현식과 조건이 false일 경우 출력될 메세지를 전달합니다. 예를 들면

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

preconditionFailure(_:_:file:line:)함수를 호출하여 실패가 발생했음을 알릴 수 있습니다. 예를 들어 유효한 데이터는 스위치의 기본 케이스가 아닌 다른 케이스에서 처리되어야 합니다.

체크하지 않는 모드(-Ounchecked)로 컴파일하면 전제조건은 체크하지 않습니다. 컴파일러는 전제조건은 항상 참이라고 가정하고 코드에 알맞게 최적화 합니다. 그러나 fatalError(_:file:line:)함수는 최적화 설정과 무관하게 항상 중지를 실행합니다.
프로토타입과 초기 개발단계에서 아직 구현되지 않은 기능에서 fatalError(_:file:line:)을 사용할 수 있으며 fatalError("Unimplemented")와 같이 작성할 수 있습니다. 역설 또는 전제조건과 다르게 치명적인 에러는 절대 최적화 되지 않기 때문에 이 구현을 만나면 항상 중지됩니다.

0개의 댓글