Search

파이썬 방어적(Defensive) 프로그래밍

생성일
2023/06/13 06:53
태그
python
날짜
2023/06/15
3 more properties
코드의 모든 부분을 유효하지 않은 것으로부터 스스로 보호할 수 있게 하는 것
DbC
계약에 의거하여 예외를 발생기킴
실패하는 모든 조건을 기술
Defensive Programming
코드(객체, 함수, 메서드 등)가 스스로를 유효하지 않은 것으로부터 보호
방어적 프로그래밍의 주요 주제
1.
예상할 수 있는 시나이로의 오류를 처리하는 방법
→ Error Handling Procedure
2.
발생하지 않아야 하는 오류를 처리하는 방법
→ Assertion

Error Handling

에러에 대해 실행을 계속 할 수 있을 지, 그렇지 않아서 프로그램을 종료할 지 결정
값 대체 (value substitution)
예외 처리 (exception handling)
에러 로깅 (error logging)

값 대체

잘못된 값을 정합성을 깨지 않는 다른 값(기본 값, 초기 값)으로 대체하는 것
아래 예시에서 실제로 방어적 프로그래밍이 어떻게 구현되어있는 지 확인할 수 있다.
# dictionary d = {'name': 'Yang'} print(d.get('name')) # Yang print(d.get('age')) # None print(d.get('age', 1)) # 1
Python
복사
# os environment import os print(os.getenv('PATH')) # /usr/local/bin:/usr/bin:/bin print(os.getenv('DBHOST')) # None print(os.getenv('DBHOST', 'localhost')) # localhost
Python
복사

예외 처리

예외 처리 메커니즘의 핵심은 상황을 명확히 설명하고 원래의 비즈니스 로직에 따라 흐름을 유지하는 것이다.
예외처리는 호출자에게 잘못을 알려주는 것이다. 하지만 이러한 예외는 캡슐화를 약화시키기 때문에 신중해야한다. 함수에 예외가 많을수록 호출자는 함수에 대해 많은 것을 알아야한다. 또한, 예외가 많아지면 함수가 너무 많은 책임을 지고 있는 것일 수도 있다. 이런 경우 여러개의 작은 기능들로의 분리를 고려해야한다.

추상화 단계에서의 예외 처리

예외는 오직 한 가지 일을 하는 함수의 한 부분이어야 한다. 명확하게 관심사를 분리하자.
class DataTransport: ... def deliver_event(self, event: Event): try: self.connect() data = event.decode() self.send(data) except ConnectionError as e: logger.info(f"connection error : {e}") raise except ValueError as e: logger.error(f"{event} took wrong value : {e}") raise ... def connect(self): ...
Python
복사
위의 경우 deliver_event 메서드에서 ConnectionErrorValueError를 모두 처리하고 있다. 하지만 ConnectionError에 대한 책임은 connect 메서드에 전가하는 것이 합리적이다.

엔드 유저에게 Traceback 노출 금지

보안을 고려하여 상황에 따라 정보 노출을 방지해야한다. 이 방법은 HTTP 오류가 발생했을 때 사용하는 기법이다.

비어있는 except 블록 지양

에러는 조용히 전달되어서는 안 된다. 파이썬스럽지도 않다.
try: process_data() except: pass
Python
복사
단순히 pass를 사용해버리는 것이 나쁜 이유는 코드가 의미하는 바를 알 수 없기 때문이다. 굳이 이러한 상황이 있다면 다음 사항을 고려해야한다.
AttributeError, KeyError 등 구체적인 예외를 사용한다.
명시적으로 특정 오류를 무시하려면 contextlib.suppress 함수를 사용한다.
import contextlib with contextlib.supress(KeyError): process_data()
Python
복사

예외 처리 중 발생한 예외

raise <e> from <original> 구문을 사용해서 기존 예외 정보를 포함시키자.
class InternalDataError(Exception): ... def process(data_dict, record_id): try: return data_dictionary[record_id] except KeyError as e: raise InternalDataError("no data") from e
Python
복사

파이썬 어설션 (assertion)

assertion은 절대 일어나서는 안 되는 상황을 정의한다 (소프트웨어 결함)
try: assert condition.holds(), "조건에 맞지 않음" except AssertionError: alternative_procedure()
Python
복사
위의 코드에서 alternative_procedure()은 절대 조용히 넘어가서는 안 된다. AssertionError를 받는 이유는 종료 전 처리해야할 것들이 있을 수 있기 때문이다. 예외처리와 다르게 AssertionError가 발생했다는 것은, 프로그램에 큰 문제가 있다는 것으로 간주해야한다.