HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
📖
공부한 책
/
Good Code, Bad Code
Good Code, Bad Code
/
4. 오류

4. 오류

오류

복구 가능성

  • 복구 가능한 오류 : 많은 소프트웨어 오류는 치명적이지 않으며, 오류가 발생하더라도 사용자는 알아채지 못하도록 적절하게 처리한다면 작동을 계속할 수 있는 합리적인 방법이 있음
  • 복구할 수 없는 오류 : 오류가 발생하고 시스템이 오류를 복구할 수 있는 합리적인 방법이 없을 때가 있음
    • 코드와 함께 추가되어야 하는 리소스가 없다
    • 어떤 코드가 다른 코드를 잘못 사용
      • 잘못된 입력 인수로 호출
      • 일부 필요한 상태를 사전에 초기화하지 않음
호출하는 쪽에서만 오류 복구 가능 여부를 알 때가 많다
 

견고성 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) } }
  • 반환되는 값을 널이 아닌 값으로 타입 변환을 할 수 있으며, 이것은 여전히 적극적인 결정이다. 타입 변환 시 값이 널인 경우를 처리할 수 밖에 없다.