4.1 복구 가능성
복구 가능한 오류
- 치명적이지 않음
- 오류가 발생하더라도 사용자는 알아채지 못하도록 적절하게 처리 → 작동 계속 진행
- 예) 사용자가 유효하지 않은 전화번호 입력 시 오류 메시지 제공하고 올바른 번호를 입력 요청
- 다른 예) 네트워크 오류, 통계 기록하는 부분에서 오류
복구할 수 없는 오류
- 프로그래밍 오류
- 개발자가 어느 부분을 망쳐놓은 경우
- 예) 잘못된 입력 인수로 호출, 일부 필요항 상태를 사전에 초기화하지 않음
- 신속한 실패, 요란한 실패 → 피해를 최소화하고 개발자가 문제를 발견하고 해결할 가능성을 최대화 해야 함
호출하는 쪽에서만 오류 복구 가능 여부를 알 때가 많다
- 일반적으로 코드의 잠재적 호출자에 대한 가정을 가능한 하지 않는 것이 좋음
- 함수가 어디서 호출될지 그리고 호출 시 제공되는 값이 어디서 올지 정확한 지식이 없다
호출하는 쪽에서 복구하고자 하는 오류에 대해 인지하도록 하라

- PhoneNumber.parse() 함수의 작성자는 이 함수에서 오류가 발생할 수 있다는 가능성을 호출하는 쪽에서 확실하게 인지하도록 해야 한다
4.2 견고성 vs 실패
신속하게 실패하라
- 가능한 한 문제의 실제 발생 지점으로부터 가까운 곳에서 오류를 나타내는 것
- 그렇지 않으면 문제를 추적하고 해결하기 위해 상당한 노력이 필요
요란하게 실패하라
- 오류가 발생하는데도 불구하고 아무도 모르는 상황을 막고자 하는 것
- 가장 명백한 방법은 예외를 발생해 프로그램이 중단되게 하는 것
- 다른 방법은 오류 메시지 기록 (요란하지 않음)
- 이 경우 그냥 지나갈 가능성이 있음
오류를 숨기지 않음
- 오류를 숨기는 것은 복구할 수 없는 오류와 있는 오류 모두에 문제를 일으킴
- 기본값 반환, 널 객체 반환, 아무것도 하지 않음 모두 오류를 숨기는 예
4.3 오류 전달 방법
- 명시적 방법: 코드를 직접 호출한 쪽에서 오류가 발생할 수 있음을 인지할 수밖에 없도록 함
- 암시적 방법: 코드를 호출하는 쪽에 오류를 알리지만, 호출하는 쪽에서 그 오류를 신경 쓰지 않아도 됨

예외
- 코드에서 오류나 예외적인 상황이 발생한 경우 이를 전달하기 위한 방법으로 고안
- 예외가 발생할 때 콜 스택을 거슬러 올라가는 데 예외를 처리하는 코드를 만나거나, 더 이상 올라갈 콜 스택이 없을 때까지 그렇게 함. 더 이상 올라갈 콜 스택이 없는 경우에는 오류 메시지를 출력하고 프로그램이 종료됨.
- 자바는 검사 예외, 비검사 예외 개념을 모두 가지고 있음
명시적 방법: 검사 예외
- 컴파일러가 검사 예외에 대해 호출하는 쪽에서 예외를 인지하도록 강제적으로 조치
- 호출하는 쪽에서는 예외 처리를 위한 코드를 작성하거나 자신의 함수 시그니처에 해당 예외 발생을 선언해야 함

- getSquareRoot() 함수를 호출하는 코드는 NegativeNumberException 예외를 처리하거나 함수 시그니처에 이 예외를 발생시킬 수 있음을 표시해야 함


암시적 방법: 비검사 예외
- 비검사 예외를 사용하면 다른 개발자들은 코드가 이 예외를 발생시킬 수 있다는 사실을 전혀 모를 수 있음
- 함수에서 어떤 예외를 발생시키는지 문서화하는 것이 바람직하나 개발자가 문서화하는 것을 잊어버릴 때가 있음


- 중요한 것은 getSquareRoot()를 호출하는 함수가 예외를 확인하고 처리하지 않아도 된다는 점
- 컴파일도 문제없음
명시적 방법: 널값이 가능한 반환 유형
- 널 반환 → 특정값을 계산하거나 얻는 것이 불가능함을 나타내기 위한 방법
- 널 값이 반환될 수 있다는 것을 호출하는 쪽에서 강제적으로 인지하고, 그에 따라 처리


명시적 방법: result 반환 유형
- 널값이나 옵셔널 타입을 반환할 때 문제 중 하나는 오류 정보를 전달할 수 없다는 것
- 언어에서 지원하지 않으면 다음과 같이 만들어서 사용해야 함

- 다른 개발자가 result 유형을 사용하는 방법에 얼마나 익숙해지느냐가 문제
- getValue()를 호출하기 전에 hasError() 함수를 통해 오류를 확인하지 않는다면 무용지물


- 처리는 오류가 발생했는지 알기 위해 먼저 hasError() 호출, 오류 없으면 getValue() 호출하여 결과값 얻음
- 오류가 발생한 경우라면 getError() 호출해서 세부정보 얻을 수 있음

명시적 방법: 아웃컴 반환 유형
- 호출하는 쪽에서 반환값을 강제적으로 확인해야 함


- 좀 더 복잡한 시나리오를 구현해야 한다면 더 정교한 아웃컴 유형을 사용
- enum, 캡슐화를 위한 전체 클래스 정의
- 호출하는 쪽에서 반환값을 무시하거나 함수가 값을 반환한다는 사실조차 인식 못할 수 있음
- 일부 언어에서는 호출하는 쪽에서 함수의 반환값을 무시하면 컴파일러가 경고를 생성하도록 함수를 표시할 수 있음
- 자바 CheckReturnValue 애너테이션
- C# MustUseReturnValue 애너테이션
- C++ [[nodiscard]]
- 일부 언어에서는 호출하는 쪽에서 함수의 반환값을 무시하면 컴파일러가 경고를 생성하도록 함수를 표시할 수 있음
암시적 방법: 프로미스 또는 퓨처
- 비동기적으로 실행하는 코드


- 오류가 발생하고 프로미스가 거부될 수 있음을 알려면 세부 구현 사항을 확인해야 함
- 호출하는 쪽에서는 잠재적인 오류 시나리오를 완전히 알지 못함
- 명시적으로 만드려면 result 유형의 프로미스를 반환하는 방법이 있음 (조금 복잡)

암시적 방법: 매직값 반환
- 오류를 알리기 위해 -1 반환
- 좋은 방법은 아님

4.4 복구할 수 없는 오류의 전달
- 신속하고 요란하게 실패하라
- 비검사 예외 발생
- 프로그램이 패닉이 되도록 (언어에서 지원하는 경우)
- 체크나 어서션 사용
- 암시적인 기술 사용하는 게 합리적, 왜냐면 할 수 있는 게 없음
4.5 호출하는 쪽에서 복구하기를 원할 수도 있는 오류의 전달
- 비검사 예외와 명시적 오류 전달 기법 중 어느 것을 사용해야 하는지에 대한 논쟁
- 비검사 예외 주장
- 코드 구조 개선: 오류 처리 한 곳에서 가능
- 개발자들이 무엇을 할 것인지에 대해서 실용적이어야 함: 너무 많은 명시적 오류 때문에 결국 무시하게 됨
- 명시적 기법 주장
- 매끄러운 오류 처리: 오류가 상위로 전파된다면 사용자 친화적이지 않은 오류 메시지를 UI에 표시할 수도 있음
- 실수로 오류를 무시할 수 없음
- 개발자들이 무엇을 할 것이지에 대해서 실용적이여야 함: 처리해야 할 예외가 명시되어 있어서 그냥 하면 됨
- 필자는 명시적 방식이 낫다고 봄
요약
- 오류에는 크게 두 종류
- 시스템이 복구할 수 있는 오류
- 시스템이 복구할 수 없는 오류
- 해당 코드에 의해 생성된 오류로부터 복구할 수 있는지 여부를 해당 코드를 호출하는 쪽에서만 알 수 있는 경우 많음
- 에러가 발생하면 신속하게 실패, 에러를 복구할 수 없는 경우 요란하게 실패
- 오류는 숨기지 말고 신호를 보내야 함
- 오류 전달 기법은 두 가지 범주
- 명시적 방법: 코드 계약의 명확한 부분. 호출하는 쪽에서는 오류가 발생할 수 있음을 인지한다.
- 암시적 방법: 코드 계약의 세부 조항을 통해 오류에 대한 설명이 제공되거나 전혀 설명이 없을 수도 있다. 오류가 발생할 수 있다는 것을 호출하는 쪽에서 반드시 인지하는 것은 아니다.
- 복구할 수 없는 오류에 대해서는 암시적 오류 전달 기법 사용
- 잠재적으로 복구할 수 있는 오류에 대해서는
- 명시적 기법이 좋음
- 컴파일러 경고에 주의를 기울이라
'TIL > 좋은 코드, 나쁜 코드' 카테고리의 다른 글
| 3. 다른 개발자와 코드 계약 (0) | 2025.03.30 |
|---|---|
| 2. 추상화 계층 (0) | 2025.03.23 |
| 1. 코드 품질 (0) | 2025.03.16 |










