오류
복구 가능성
- 복구 가능한 오류 : 많은 소프트웨어 오류는 치명적이지 않으며, 오류가 발생하더라도 사용자는 알아채지 못하도록 적절하게 처리한다면 작동을 계속할 수 있는 합리적인 방법이 있음
- 복구할 수 없는 오류 : 오류가 발생하고 시스템이 오류를 복구할 수 있는 합리적인 방법이 없을 때가 있음
- 코드와 함께 추가되어야 하는 리소스가 없다
- 어떤 코드가 다른 코드를 잘못 사용
- 잘못된 입력 인수로 호출
- 일부 필요한 상태를 사전에 초기화하지 않음
호출하는 쪽에서만 오류 복구 가능 여부를 알 때가 많다
견고성 vs 실패
신속하게 실패하라(failing fast)
- 가능한 한 문제의 실제 발생 지점으로부터 가까운 곳에서 오류를 나타내는 것
- 일반적인 예 : 잘못된 인수로 함수를 호출하는 경우. 실패의 신속한 표시는 그 함수가 잘못된 입력과 함께 호출되는 즉시 오류를 발생시키는 것을 의미.
요란하게 실패하라(failing loudly)
- 오류가 발생하는데도 불구하고 아무도 모르는 상황을 막고자 하는 것
- 가장 명백한 방법은 예외를 발생시켜 프로그램이 중단되게 하는 것
- 다른 방법은 오류 메시지를 기록하는 것인데 개발자가 얼마나 부지런히 로그를 확인하는지, 로그에 방해되는 다른 메시지가 얼마나 있는지에 따라 오류가 무시될 수 있음
복구 가능성의 범위
단일 요청을 처리할 때 서버에서 프로그래밍 오류가 발생할 수 있다. 요청은 독립 이벤트이므로 이 경우 전체 서버가 멈추지 않는 것이 최상이다. 이 오류는 단일 요청 범위 내에서는 복구할 수 없지만, 서버 차원에서는 전체적으로 복구할 수 있다.
오류를 숨기는 몇가지 방법(좋지 않음)
기본값 반환
class AccountManager { private AccountStore accountStore; Double getAccountBalanceUsd(Int customerId) { AccountResult result = accountStore.lookup(customerId); if (!result.success()) { return 0.0; } return result.getAccount().getBalanceUsd(); } }
- 코드에 기본값을 두는 것이 유용한 경우가 있을 수 있지만, 오류를 처리할 때는 대부분의 경우 적합하지 않다.
- 잘못된 데이터로 시스템이 제대로 작동하지 못하게 만들고 오류가 나중에 이상한 방식으로 나타날 수 있기 때문에
신속한 실패
와요란한 실패
의 원리를 위반하는 것
널객체 패턴
class InvoiceManager { private final InvoiceStore invoiceStore; List<Invoice> getUnpaidInvoices(Int customerId) { InvoiceResult result = invoiceStore.query(customerId); if (!result.success()){ { return []; } return result.getInvoices().filter(invoice -> !invoice.isPaid()); } }
- 널 객체 패턴을 사용하는 것이 꽤 유용한 경우가 있지만, 오류 처리에 사용하는 것은 바람직하지 않다.
- 아무일도 하지 않음 : 코드가 무언가를 반환하지 않고 단지 어떤 작업을 수행하는 경우, 문제가 발생할 때 가능한 한가지 옵션은 오류가 발생했다는 신호를 보내지 않는 것 → 바람직하지 않음
오류 전달 방법
- 오류가 발생하면 일반적으로 더 높은 계층으로 오류를 알려야 한다.
- 오류로부터 복구할 수 없는 경우 이는 일반적으로 프로그램의 훨씬 더 높은 계층에서 실행을 중지하고, 오류를 기록하거나 전체 프로그램의 실행을 종료하는 것을 의미함
- 오류로부터의 복구가 잠재적으로 가능한 경우, 일반적으로 즉시 호출하는 쪽(또는 호출 체인에서 한두 수준 위의 호출자)에 오류를 알려 정상적으로 처리할 수 있도록 해야 한다.
- 자바는 Checked Exception, Unchecked Exception 둘 다 갖고 있지만, 예외를 지원하는 대부분의 주요 언어는 비검사 예외만 갖고 있으므로 자바 이외의 거의 모든 언어에서 예외라는 용어는 일반적으로 비검사 예외를 말함
ㅤ | 명시적 오류 전달 기법 | 암시적 오류 전달 기법 |
코드 계약에서의 위치 | 명확한 부분 | 세부 조항 혹은 아예 없음 |
호출하는 쪽에서 오류 발생 가능성에 대해 아는가? | 그렇다. | 알 수도 있고 모를 수도 있다. |
기법의 예 | 검사 예외
널 반환 유형(널 안전성의 경우)
옵셔널 반환 유형
리절트 반환 유형
아웃컴 반환 유형(반환값 확인이 필수인 경우)
스위프트 오류 | 비검사 예외
매직값 반환(피해야 함)
프로미스 또는 퓨처
어서션
체크(구현에 따라 달라짐)
패닉 |
명시적 방법 : 널 반환 유형
Double? getSquareRoot(Double value) { if (value < 0.0) { return null; } return Math.sqrt(value); } void displaySquareRoot() { val squareRoot = getSquareRoot(ui.getInputNumber()) if (squareRoot == null) { ui.setError("Can't get square root of a negative number.") } else { ui.setOutput("Square root is : " + squareRoot) } }
- 반환되는 값을 널이 아닌 값으로 타입 변환을 할 수 있으며, 이것은 여전히 적극적인 결정이다. 타입 변환 시 값이 널인 경우를 처리할 수 밖에 없다.