4. 오류
오류에 대해서, 특히 어떻게 알리고 대처해야 하는지는 논의해봐야 골치만 아픈 복잡한 문제다. 코드가 오류를 어떻게 전달하고 처리해야 하는지에 관해 많은 소프트웨어 엔지니어들과 프로그래밍 언어 설계자들이 의견을 달리한다(때로는 자신들의 의견을 강하게 주장하기도 한다).
4.1. 복구 가능성
4.1.1. 복구 가능한 오류
전체 시스템의 작동이 멈추면 이상한 오류를 뜻한다.
- 잘못된 사용자 입력
- 네트워크 오류
- 중요하지 않은 작업 오류 (ex. 통계 기록)
이러한 오류는 일어날 것을 예상하여 처리 코드를 작성해야 한다.
4.1.2. 복구할 수 없는 오류
코드를 변경하지 않는이상 정상 동작을 기대 할 수 없는 오류를 뜻한다.
- 코드와 함께 추가되어야 하는 리소스가 없다.
- 코드 사용 방법이 잘못 된 경우
- 잘못된 입력 인수로 호출
- 일부 필요한 상태를 사전에 초기화하지 않음
이러한 오류는 최대한 신속히 그리고 최대한 요란하게 개발자에게 알려야한다.
4.1.3. 호출하는 쪽에서만 오류 복구 가능 여부를 알 때가 많다
작성한 코드가 미래에 어떤 상황에서 어떻게 사용 될지 모르기 때문에
4.1.4. 호출하는 쪽에서 복구하고자 하는 오류에 대해 인지하도록 하라
코드를 사용하는 쪽에서는 그 코드의 세부사항에 대한 지식이 없을 가능성이 높으므로, 오용 할 확률이 높다. 그렇기에 호출하는 쪽에 오류를 확실히 인지 시켜야 한다.
4.2. 견고성 vs 실패
견고성보다는 실패가 많은 경우에 있어 최선
4.2.1. 신속하게 실패하라
가능한 한 문제의 실제 발생 지점으로부터 가까운 곳에서 오류를 나타내는 것이다.
즉, 잘못된 입력으로 인한 오류라면, 입력을 사용할 때 오류를 발생시키지 말고, 입력을 받았을 때 바로 유효성을 확인하여 오류를 발생시키는 것이 바람직하다.
4.2.2. 요란하게 실패하라
오류가 발생하는데도 불구하고 아무도 모르는 상황을 막고자 하는 것이다. 이를 위한 가장 명백한(그리고 강압적인) 방법은 예외(또는 이와 유사한 것)를 발생해 프로그램이 중단되게 하는 것이다
4.2.3. 복구 가능성의 범위
소프트웨어의 견고성과 오류 가시성은 양립하기 힘들다. 이에 대한 해결책은 ‘모니터링’의 강화이다. (ex. 오류 발생률이 높은 부분은 알림 메시지 발송)
4.2.4. 오류를 숨기지 않음
어떤 때는 실수를 숨기고 아무 일도 없었던 것처럼 동작하도록 코드를 작성하고 싶은 마음이 생길 수 있다. 이렇게 하면 코드가 훨씬 더 단순해지고 번거로운 오류 처리를 피할 수 있지만, 좋은 생각은 아니다. 오류를 숨기는 것은 복구할 수 있는 오류와 복구할 수 없는 오류 모두에 문제를 일으킨다.
오류를 숨기는 방법
- 기본값 반환
- 참조형의 경우 null, 기본형인 경우 기본형의 기본값을 반환
- 널 객체 패턴
- 빈 목록이나, 빈 객체를 반환
- 아무것도 하지 않음
- 반환타입이 void일 경우, 아무런 동작을 하지 않음
- 예외 발생 시 catch 후 후처리를 하지 않음
4.3. 오류 전달 방법
- 명시적 방법: 호출한 쪽에게 오류 발생 가능성을 명확히 인지 시키는 방법
- 암시적 방법: 호출한 쪽이 오류 발생 가능성을 인지 할 수도, 인지 못 할수도 있는 방법
4.3.1. 요약: 예외
자바는 검사 예외(checked exception)와 비검사 예외(unchecked exception)의 개념을 모두 가지고 있다. 예외를 지원하는 대부분의 주요 언어는 비검사 예외만 가지고 있으므로 자바 이외의 거의 모든 언어에서 예외라는 용어는 일반적으로 비검사 예외를 의미한다.
4.3.2. 명시적 방법: 검사 예외
- 호출하는 쪽에서 예외 검사를 해야하도록 컴파일러가 강제하는 예외
- Java의
Exception
4.3.3. 암시적 방법: 비검사 예외
- 호출하는 쪽에서 예외 검사를 하지 않아도 되는 예외
- 인터페이스에 선언하여 사용하는 쪽에 인지 시킬 수 있으나, 검사가 강제되진 않음
- Java의
RuntimeException
4.3.4. 명시적 방법: 널값이 가능한 반환 유형
- 널값을 이용하여 오류가 발생 했음을 알리는 방법
- 널 안정성이 언어차원에서 제공되는 언어인 경우에는 명시적 방법이지만, 그렇지 않은 언어(ex. Java)에서는 암시적 방법임
4.3.5. 명시적 방법: 리절트 반환 유형
- 정상 값과 오류 정보를 모두 담을 수 있는 Result 타입을 반환하는 방법
- 호출 하는 쪽에서 error를 확인하지 않으면 소용이 없으므로 암시적 방법이라고 생각 함
- Result 타입으로 감싸기 때문에, 반환 타입이 복잡해 짐
4.3.6. 명시적 방법: 아웃컴 반환 유형
- 결과를 boolean으로 반환하는 방법
- 호출 하는 쪽에서 무시하면 소용이 없음
- 각 언어의 컴파일러 경고를 이용하여 개발자에게 알려줄 수 있으나 이것또한 강제적이지 않음
4.3.7. 암시적 방법: 프로미스 또는 퓨처
- 비동기코드를 작성 할 때 사용하는 방법
- 콜백 함수를 통해 성공 혹은 실패 시 실행되는 함수가 실행 됨
- Java의
CompletableFuture
4.3.8. 암시적 방법: 매직값 반환
- 특별한 의미를 가진 값을 반환하여 그 반환값으로 오류 유무를 판단하는 방법
- 사용하지 않는 것을 추천
4.4. 복구할 수 없는 오류의 전달
현실적으로 복구할 가능성이 없는 오류가 발생하면 신속하게 실패하고, 요란하게 실패하는 것이 최상의 방법
4.5. 호출하는 쪽에서 복구하기를 원할 수도 있는 오류의 전달
호출하는 쪽에서 복구하기를 원할 수도 있는 오류를 전달하고자 할 때, 이에 대한 모범 사례와 관련해 소프트웨어 엔지니어(그리고 프로그래밍 언어 설계자) 사이에서 일치된 의견이 없으므로 흥미로운 주제다.
개인의 의견보다 팀이 동의한 철학에 따라 오류 전달 방법을 사용하는 것이 중요!
4.5.1. 비검사 예외를 사용해야 한다는 주장
- 코드 구조 개선
- 각 계층이 오류에 대해 알 필요가 없음. 즉 오류 처리 계층으로 오류 처리 로직을 모을 수 있음
- 개발자들이 무엇을 할 것인지에 대해서 실용적이어야 함
- 검사 예외가 계속 있으면, 개발자가 로직에 집중하기 힘들 수 있음
- 심지어 예외를 무력화 시킬 수 있게 유도 함
4.5.2. 명시적 기법을 사용해야 한다는 주장
- 매끄러운 오류 처리
- 암시적 오류 처리의 경우, 현실적으로 모든 오류를 매끄럽게 처리할 수 있는 단일 계층을 갖기 어려움
- 실수로 오류를 무시할 수 없다
- 개발자가 오류 처리를 잊지 않도록 강제 할 수 있음
- 개발자들이 무엇을 할 것인지에 대해서 실용적이어야 함
- 암시적 예외는 문서화가 제대로 되지 않는이상, 어떤 예외가 발생 할 지 모름
- 심지어 모든 예외를 잡아내는 방식으로 예외를 처리하도록 유도 함
4.5.3. 필자의 의견: 명시적 방식을 사용하라
호출하는 쪽에서 복구하기를 원할 수도 있는 오류에 대해 비검사 예외를 사용하지 않는 것이 최상이라는 것이 필자의 의견
내 의견은 비즈니스 로직에서 발생하는 예외는 검사 예외를, 그 보다 추상화 계층이 낮은 곳에서 발생하는 예외는 비검사 예외를 사용하는 것이 좋을 것 같음
4.6. 컴파일러 경고를 무시하지 말라
컴파일러의 경고는 잠재적 버그를 알려 줄 가능성이 높음