[목차]
[출처]
좋은 소프트웨어란 무엇인가? 이런 추상적인 주제는 관점이나 환경에 따라서 정답이 다를 수 있겠지만, 제품의 관점에서 봤을 때 일반적으로 더 나은 성능을 내고 사용자의 니즈를 충족시키는 것을 좋은 소프트웨어라 할 수 있을 것이다.
좋은 소프트웨어는 좋은 디자인으로부터 나온다. 세부 코드도 중요하지만 구조적인 디자인은 제품으로써 코드의 본질을 나타내기 때문이다. 아무리 효율적인 코드를 작성해도 그것이 다른 모듈들과 유기적으로 동작하지 못한다면 아무 의미 없지 않을까? 반대로 input/output 측면에서 모호한 코드를 작성하면 일관성 없는 불안정한 디자인이 될 것이다. 이렇듯 세부 코드는 좋은 디자인을 상세하게 보여주는 하나의 부분이라는 측면에서, 구조적으로 일관성 있고 독립적이며 결함이 적은 클린 코드 만드는 법에 대해 생각해 보자.
관점에 따라 좋은 디자인은 여러 방식이 존재하기 때문에, 이 글에서는 다음의 목표를 세우고 다양하게 접근해 본다.
(와.. 모두 굉장히 중요해보인다..)
a.k.a Design by Contract - DbC
클래스, 메서드, 함수를 만들 때 컴포넌트의 기능을 숨겨 캡슐화하고 사용자에게 API 를 제공하여
일련의 계약(Contract)적인 Input/Output
관계를 갖도록 디자인하는 것을 계약에 의한 디자인이라고 한다.
이에 따라 사용자는 약속된 입력값을 넣었을 때 특정한 값을 얻도록 기대할 수 있다.
여기서 중요한 것은 약속이 철저히 지켜져야 한다는 것이며, 기대되는 값이 들어오지 않거나 예외적인 상황이 발생하면 그것을 절대 조용히 넘기지 않아야 한다.
사전조건과 사후조건은 코드 레벨에서 반드시 구현되어야 하는 것이고, 나머지 두 개는 문서화 상에서 기록되어야 한다. 이렇게 하면 문제 발생 시 책임소재를 구체적이고 분명히 파악하여 어디를 고쳐야 할지, 가정이 잘못된 것은 아닌지 빠르게 파악 가능하다.
a. 사전조건
함수나 메서드가 제대로 동작하기 위해 보장해야 하는 모든 것을 말한다. 특히 파이썬은 동적으로 타이핑되므로, 전달된 데이터를 사전에 필히 검증해야 한다.
Black, mypy와 같은 코드 포매팅 도구도 존재하지만, 이는 외부 모듈에 의한 단순 타입 체킹에 지나지 않는 경우가 많아, 컴포넌트 자체적으로 직접적인 유효성 검사를 해야만 한다.
그렇다면 사전조건 체크를 어느 부분에서 해야 할까? 함수 호출 전에? 아니면 함수 내부에서 자체적으로? 전자는 관용적(tolerant), 후자는 까다로운(demanding) 접근 방법으로, 후자가 업계에서 안정적인 방식으로 널리 쓰인다. 또한 중복 제거 원칙에 따라 둘중 한 부분에서만 체크가 이루어져야 한다.
b. 사후조건
메서드 또는 함수가 반환된 후의 상태를 강제하는 계약의 일부이다. 컴포넌트의 반환값은 특정 속성이 항상 보장되어야만 한다.
c. 파이썬스러운 계약
파이썬스러운 코딩을 위해 RuntimeError 예외 또는 ValueError 예외 제어 메커니즘을 적용해보자. 일반적인 예외보다는 내가 작성하는 애플리케이션에 종속되는 보다 구체적인 사용자정의 예외를 만드는 것도 좋은 방법이다. (파이썬으로 예외처리하기)
또한 컴포넌트의 사전조건, 사후조건, 핵심 기능들을 구분하여 가능한 한 격리된 상태로 유지하는 것이 좋다. 이를 위해 데코레이터를 사용하는 것도 좋다.
d. 결론
정의된 계약으로부터 문제에 대한 빠른 원인파악이 가능하다. 이는 더욱 견고한 코드를 만들게 된다는 의미로 이어진다. 많은 유지보수 와중에도 불변식이 항상 불변하다면, 해당 컴포넌트는 정상 동작할 것이라고 기대할 수 있다.
조금 귀찮고 시간이 들더라도 계약을 따르도록 하는 여러 검증기능들을 추가한다면, 장기적인 관점에서 더 나은 품질로 보상될 것이다.
a.k.a Defensive programming
방어적 프로그래밍은 예외를 발생시키고 유효하지 않을 시 실패하게 하는 DbC와는 다르게 각 컴포넌트의 모든 부분들을 유효하지 않은 것으로부터 스스로 보호하도록 하는 것이다.
디자인의 관점이 다르다는 것은 서로 보완적인 관계에 있을 수 있다는 것이며, 실제로 이 관점은 다른 디자인 원칙과 결합될 때 특히 유용하다.
a. 에러 핸들링
이것의 주요 목적은 예상되는 에러에 대해서 실행을 계속할 수 있을지 아니면 극복할 수 없는 오류여서 프로그램을 중단할지를 결정하는 것이다. 에러 처리 방법에는 값 대체(substitution), 에러 로깅, 예외 처리가 있다.
잘못 생성된 결과를 정합성을 깨지 않는 다른 값으로 대체하는 방법이 값 대체인데, 일반적인 대체값은 기본 값, 잘 알려진 상수, 초기 값이 있다.
Written on August 5th, 2020 by namu