Architecture

[SW Design] - Defensive Programming

0cool 2023. 2. 10. 12:32

 Defensive Programming, 말 그대로 방어적으로 프로그래밍하는 설계 방식이다.

 

 사실 해당 설계 방식은 다른 산업군에서 활용하고 있는 Defensive design을 소프트웨어 버전으로 차용해서 가져온 것이다. 여기서 말하는 defesnsive design을 통해 설계된 제품들은 기대하지 않은 이슈 또는 외부 환경으로 부터 장치나 사용자를 보호하기 위해 나름의 '안전장치'를 설계하여 탑재하고 있다.

 

 그러면 해당 디자인을 차용한 Defensive Programming에서의 안전장치는 어떤 것이고 또 해당 안전장치의 설계를 위해 수행하는 방어적 프로그래밍은 어떤 것을 의미하는 것일까?

 

먼저 방어적 프로그래밍이 뭔지부터 알아보자

 

 방어적 프로그래밍은 함수 또는 메서드의 모든 부분을 유효하지 않은 모든 것으로부터 코드 스스로가 방어 가능하도록 하는 것을 말한다.

 

유효하지 않은 것으로부터 어떻게 코드를 방어할까?

 

 답은 의외로 간단하다. '유효하지 않은 모든 상황에 대해 철저히 Error Handling을 설계하여 최대한 모든 이슈 케이스에 대비하는 것'이다. 여기서 이 'Error Handling'이 바로 SW의 '안전장치' 역할을 수행하게 된다.

 

방어적 프로그래밍의 핵심은 바로 SW의 안전장치인 Error Handling을 어떻게 설계하느냐 있다.

 

 에러를 핸들링하는 방식에는 여러 가지가 있지만 크게 2가지 방식을 많이 활용한다. 하나는 유효하지 않은 값을 유효한 값으로 대치하는 값 대체 방식, 또 하나는 발생한 에러를 핸들링할 Procedure를 설계하여 해당 Procedure에 에러를 넘겨주는 예외 처리 방식이다.

 

먼저 값 대체 방식에 대해 자세히 알아보도록 하자

 

 값 대체 방식은 SW에 치명적인 이슈를 생성할 수 있는 값의 유발성에 대해 해당 값을 안전한 다른 값으로 교체하는 방식을 일컫는다. 즉, 문제가 될만한 값들을 안전한 값으로 교체하여 SW 전체에 미치는 영향을 상쇄시키는 방식이다.

 

 하지만 해당방식은 필연적으로 SW의 정확성과 견고성 사이의 trade-off를 발생시킨다.(정확성↓ 견고성↑) 함수나 메서드의 흐름에 따라 생성된 값을 인위적으로 교체하는 방식이니 필연적으로 생기는 단점이 아닐 수 없다. 따라서 정말 신중하게 생각하고 판단하여 해당 trade-off를 감내할 만한 상황에만 조심스럽게 사용해야 하는 안전장치이다.

 

 조금 더 보편적이면서 안전하게 해당 방식을 적용하는 case가 있는데 우리가 흔히 접하는 함수 파라미터의 기본값 설정이 그 대표적인 예이다. 즉, 제공되지 않은 데이터에 한해 기본값을 설정하여 SW의 예기치 못한 오작동을 방지하는 방법이다.

def connect(connector, host: str = "0cool.co.kr", port: int = 1234):
    try:
        return connector.connect(host, port)
    except ConnectionError as e:
    	raise e

 위 함수는 커넥터와 접속정보를 전달하고, 전달받은 정보를 기반으로 connect를 수행하는 단순한 함수이다. 파라미터를 잘 살펴보면 주소정보와 포트정보에 기본값을 할당해 전달한다.

 

 만약 호출자 부분에서 접속정보 파라미터를 누락시킨다 하더라도 명시된 기본 주소정보를 통해 정상적으로 connect를 요청하는 것이 가능해진다. (connect 실패로 인한 SW 오작동을 막을 수 있는 것이다.)

 

다음 방법으로 예외처리에 대해 알아보도록 하자

 

 예외처리 방식은 함수나 메서드에 발생한 심각한 오류에 대해서 명확하고 분명하게 리포트하고 적절하게 해당 오류가 해결될 수 있도록 처리하는 일련의 procedure를 설계하는 것이다.

 

이러한 예외처리를 수행하는 procedure에 대해 설계하고 작성할 때 조심해야 하는 몇 가지 주의사항이 있다.

 

1. 예외를 처리하는 procedure에 비즈니스 로직을 수행하는 행위는 지양해야 한다.

 

 예외처리 procedure 말 그대로 예외만을 처리하기 위해 작성된 코드이다. 특정 이슈에서만 작동하는 prodcedure에 비즈니스 로직을 수행하는 코드를 넣게 되면 전체적인 SW흐름에 좋지 않은 영향을 끼치게 된다. (이로 인해 goto 구문이 남발하는 수가 있다..)

 

2. 예외처리는 캡슐화를 약화시키기 때문에 신중하게 사용해야 한다.

 

 함수에 예외처리 procedure가 많을수록 함수 호출자는 해당 함수에 대해 더 많은 것을 알고 해당 함수를 호출해야 한다. 그리고 이는 문맥에서 자유롭지 못함을 의미한다. 호출할 때마다 발생 가능한 예외를 염두에 두고 문맥을 유지해야 한다면 전체적인 문맥상의 흐름으로 봤을 때 이는 분명 큰 부담으로 다가오게 된다. (만약 하나의 함수에 여러 개의 예외 처리 구문이 있다면, 이는 해당 함수를 분리해야 한다는 신호라고 볼 수 있다.)

 

https://gist.github.com/shr0048/e4cfceccb3b1752e6f5e37e9c34da8ae

 

error_handling_ex

error_handling_ex. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 위 gist는 python으로 작성한 좋은 Error handling과 나쁜 Error handling을 비교하는 코드이다. 해당 Gist를 통해 함수의 책임 분산과 문맥의 흐름을 위한 Error Handling의 분리가 어떻게 수행돼야 하는지 살펴볼 수 있다.

'Architecture' 카테고리의 다른 글

[SW Design] - Design by Contract  (0) 2023.02.08