- 코드가 디자인이고, 디자인이 코드
- 클린코드의 궁극적인 목표는 코드를 가능한 견고하고 결함을 최소화하고 완전히 자명하도록 하는 것
- 견고한 소프트웨어의 개념 이해
- 작업 중 잘못된 데이터를 다루는 방법
- 새로운 요구 사항을 쉽게 받아들이고 확장할 수 있는 유지보수가 쉬운 소프트웨어 설계
- 재사용 가능한 소프트웨어 설계
- 개발팀의 생산성을 높이는 효율적인 코드 작성
-
소프트웨어는 사용자가 직접 호출하거나 코드의 다른 부분에서 호출
-
컴포넌트는 기능을 숨겨 캠슐화하고 함수를 사용할 고객에게는
API를 노출해야 함
- API를 디자인할 때 예상되는 입력, 출력, 부작용을 문서화해야 함
-
그러나 문서화가 런타임 시의 소프트웨어 동작까지 강제할 수는 없음
만약 호출자가 모듈(루틴)의 모든 선행조건을 충족한다면, 해당 모듈은 종료시 모든 후행조건과 불변식이 참이 될 것을 보증해야 한다.
- 관계자가 기대하는 바를 암묵적으로 코드에 삽입하는 대신 양측이 동의하는 계약을 먼저 한 다음, 계약을 어겼을 경우는 명시적으로 왜 계속할 수 없는지 예외를 발생시키는 것
- 이 책에서 말하는 계약은 소프트웨어 컴포넌트 간의 통신 중에 반드시 지켜져야 할 몇 가지 규칙을 강제하는 것
- 계약은 주로 사전조건과 사후조건을 명시하지만 때로는 불변식과 부작용을 기술함
- 사전조건(precondition)
- 코드가 실행되기 전에 체크해야 하는 것들
- module을 호출하기 위해 참이어야 하는 것들
- 사후조건(postcondition)
- 사전조건과 반대로 여기서는 함수 반환 값의 유효성 검사가 수행됨
- module이 자기가 할 것이라고 보장하는 것
- 불변식(invariant)
- 함수가 실행되는 동안에 일정하게 유지되는 것
- 함수의 문제가 없는지 확인하기 위한 것
- 호출자의 입장에서 이 조건이 언제나 참이라고 보장하는 것
- 부작용(side-effect)
- pep-316(link) - 연기(defered) 상태
- 일반적인 디자인 원칙하에 파이썬으로 구현할 수 있는 방법(저자)
- 메서드, 함수 및 클래스에
RuntimeError예외 또는ValueError예외를 발생시키는 제어 메커니즘을 추가하는 것 - 문제를 특정하기 어려울 경우, 사용자 정의 예외를 만드는 것이 바람직
- 데코레이터나 더 작은 함수를 생성하여 사전/사후조건에 대한 검사 부분과 핵심 기능 부분의 코드를 격리된 상태로 유지하는 것이 좋음
- 메서드, 함수 및 클래스에
- 디자인 원칙의 주된 가치는 문제가 있는 부분을 효과적으로 식별할 수 있음
- 코드가 더욱 견고해짐
- 각 컴포넌트는 자체적으로 제약 조건과 불변식을 관리
- 이러한 불변식이 유지되는 한 프로그램이 정상 동작함
- 무엇을 검증할 것인지 신중히 검토해야 함
-
DbC 는 테스트 주도 개발이나 단위테스트를 보완하는 방법
-
내부 코드 상태를 검사 할 때 내부적으로 일관성을 유지하도록 하며 상대적으로 저비용 접근 방식
-
legacy code 나 잘못 설계된 API와 같이 UT/TDD의 cost가 높은 상황에서 유용
- DbC: 계약에서 예외를 발생시키고 실패하게 되는 모든 조건을 기술
- 방어적 프로그래밍: 객체, 함수 또는 메서드와 같은 코드의 모든 부분을 유효하지 않은 것으로부터 스스로 보호할 수 있게 하는 것
- 방어적 프로그래밍은 여러 측면을 고려한 기술
- 다른 디자인 원칙과 결합된 경우 특히 유용(보완 관계 가능)
- 예상되는 에러에 대해서 실행을 계속할 수 있을지 아니면 극복할 수 없는 오류여서 프로그램을 중단할지를 결정하는 것
- 오류가 발생하기 쉬운 상황에서 사용, 일반적으로 데이터 입력 확인 시 자주 사용
- 에러 처리 방법
- 값 대체
- 결과 값을 안전한 다른 값으로 대체
- 견고성과 정확성 간의 trade-off
- 예외 처리
- 예외적인 상황을 명확하게 알려주고 원래의 비즈니스 로직에 따라 흐름을 유지하는 것
- 프로그램이 꼭 처리해야 하는 정말 예외적인 비즈니스 로직을
except블록과 혼합하여 사용하면 상황이 더욱 악화될 수 있음 - 호출자가 알아야만 하는 실질적인 문제가 있을 경우에는 예외를 발생시켜야 함
함수가 너무 많은 예외가 있다
-
응집력이 약함(캡슐화가 안되어 있다)
-
많은 책임을 가지게 됨
-
분할화 해야 함(작은 단위의 함수)
- 올바른 수준의 추상화 단계에서 예외 처리
- 예외는 오직 한 가지 일을 하는 함수의 한 부분이어야 함
- 함수가 처리하는(발생시키는) 예외는 캡슐화된 로직과 일치해야 함
- 예제
- Traceback 노출 금지
- 보안을 위한 고려 사항
- 예외를 처리할 때 오류가 너무 중요하다면 전파해도 됨
- 파이썬에서 traceback은 매우 유용하고 많은 디버깅 정보를 포함
[주의] 그와 동시에 이 정보는 악의적인 사용자에게도 중요 정보 유출 발생 위험
- 비어있는 except 블록 지양
- 너무 방어적이여서 아무것도 하지 않은 채로 조용히 지나쳐버리는 비어있는 except 블록
- 파이썬의 안티패턴(realpython blog)
대안
- 구체적인 예외 사용
- except 블록에서 실제 오류 처리
- 가장 좋은 방법은 위의 두 항목 동시 적용
- 원본 예외 포함
- 오류 처리시 메시지를 변경할 경우, 원래 예외를 포함하는 것이 좋음
raise <e> from <original_exception>구문 사용
- 절대로 일어나지 않아야 하는 상황에서 사용
- assert 문에 사용된 표현식은 불가능한 조건을 의미
- 이 상태가 된다는 건 소프트웨어에 결함이 있음을 의미
- 잘못된 시나리오에 도달할 경우 프로그램이 더 큰 피해를 입지 않도록 하는 것
- 비즈니스 로직과 섞거나 소프트웨어의 제어 흐름 메커니즘으로 사용해서는 안된다.
**AssertionError 에외를 처리하지 않는다**
result= condition.holds()
assert result > 0, "에러 {0}".format(result)- 참고링크(blog)
- SRP
- 책임이 다르면 컴포넌트, 계층, 모듈로 분리되어야 함
- 프로그램의 각 부분은 기능의 일부분(관심사)에 대해서만 책임을 지며 다른 부분에 대해서는 알 필요 없음
- 목표 : 파급 효과를 최소화하여 유지보수성을 향상
파급(ripple) 효과: 어느 지점에서의 변화가 전체로 전파되는 것
저자) 잘 정의된 소프트웨어는 높은 응집력과 낮은 결합력을 갖는다.
- 응집력
- 객체가 자고 잘 정의된 목적을 가져야 하며 가능하면 작아야 한다는 것을 의미
- 응집력이 높을수록 더 유용하고 재사용성이 높아진다.
- 결합력
- 두 개 이상의 객체간의 의존성
- 낮은 재사용성: 다른 상황에서 이 함수를 사용하기 어려움
- 파급 효과: 하나를 변경하면 다른 부분에도 영향이 감
- 낮은 수준의 추상화: 너무 가까운 두 함수는 서로 다른 추상화 레벨에서 문제 해결이 어려움
-
DRY(Do not Repeat Yourself)
-
OAOO(Once and Only Once)
-
중복을 반드시 피해야 한다.
-
즉, 코드에 있는 지식은 단 한번, 단 한곳에 정의되어야 함
-
코드 중복은 유지보수에 직접적인 영향을 미치는 문제임
- 오류 발생이 쉬움
- 비용이 비쌈
- 신뢰성이 떨어짐
- 과잉 엔지니어링을 하지 않기
- 유지보수가 가능한 소프트웨어를 만드는 것은 미래의 요구 사항을 예측하는 것이 아니다.
- 현재의 요구사항을 잘 해결하기 위한 소프트웨어를 작성하고 가능한 나중에 수정하기 쉽도록 작성하는 것
- 디자인이 단순할 수록 유지 관리가 쉽다.(simple is best)
- EAFP(Easier to Ask Forgiveness than permission)
- 일단 코드를 실행하고 실제 동작하지 않을 경ㅇ우에 대응
- LBYL(Look Before You Leap)
- 사전에 미리 확인해라
파이썬은 EAFP 방식으로 만들어졌으며, 그렇게 할 것을 권장한다. (암묵적인 것보다 명시적인 것이 좋다.)
EAFP vs LBYL(reference)
- 코드를 재사용하는 올바른 방법은 여러 상황에서 동작 가능하고 쉽게 조합할 수 있는 응집력 높은 객체를 사용하는 것
- 동시에 결합력을 최소한으로 줄이는 것
- 부모 클래스를 쉽게 전수 받을 수 있는 장점이 있지만 모든 것을 새로운 클래스로 가져왔기 때문에 새로운 정의에 너무 많은 기능을 추가하게 되는 단점도 있다.
- 상속이 좋지 못한 경우
- 상위 클래스는 막역한 정의와 너무 많은 책임을 가짐
- 하위 클래스는 확장하려고 하는 상위 클래스의 적절한 세분화가 아님
- 상속이 좋은 예
- 부모 클래스의 기능을 물려받으면서 추가 기능을 더하려는 경우(extension) or 특정 기능 수정하는 경우(overriding)
- 인터페이스의 정의, ISP
- 예외 (Exception)
- 도메인 문제를 해결하기 위해 적절한 데이터 구조를 만든 다음에 이 데이터 구조를 사용하는 객체를 만들지 않고 데이터 구조 자체를 객체로 만드는 경우
- 올바르게 구현되지 않으면 문제가 커짐
- 믹스인을 활용한 애플리케이션
메서드 결정 순서(MRO)
- 다중 상속 구조
믹스인(minin)
- 코드를 재사용하기 위해 일반적인 행동을 캡슐화해놓은 기본 클래스
- 그 자체로는 유용하지 않으며 대부분이 클래스에 정의된 메서드나 속성에 의존함
- 다른 클래스와 함께 믹스인 클래스를 다중 상속하여 믹스인에 있는 메서드나 속성을 사용
파이썬 함수의 인자 전달 메커니즘과 SW 엔지니어링에서의 모범 사례에서 발견되는 일반적인 원칙을 살펴본다.
- 함수의 서명(signature) : 매개변수와 반환형을 총칭
인자는 함수에 어떻게 복사되는가
- 파이썬의 첫 번째 규칙은 모든 인자가 값에 의해 전달(passed by a value)된다는 것
- 변형(mutable) 객체를 전달하고 함수 내부에서 값을 변경하면 결과에서 실제 값이 변경될 수 있음
- 함수 인자를 변경하지 않아야 함
가변인자
-
별표(*)를 사용해 인자를 패킹하여 함수에 전달해서 사용
-
반복은 변수 언패킹의 가장 좋은 예
- 함수나메서드가 너무 많은 인자를 사용하면 나쁜 디자인
- 구체화(reification)
- 전달하는 모든 인자를 포함하는 새로운 객체를 만듬
- 가변인자나 키워드 인자를 사용하여 동적 서명을 가진 함수를 만듬
- 매우 동적이어서 유지보수하기가 어려워 남용하면 안됨
함수 인자와 결합력
- 함수 서명의 인수가 많을수록 호출자 함수와 밀접하게 결합될 가능성이 커짐
많은 인자를 취하는 작은 함수의 서명
- 공통 객체에 파라미터 대부분이 포함되어 있다면 리팩토링을 통해 코드를 향상시킬 수 있음
- 변경 불가능한 객체를 사용하여 부작용을 최소화
- 직교 : 두 요소가 독립적
- 하나의 변경이 외부 세계의 영향을 최소화해야함
- 관심사의 분리, 응집력, 컴포넌트 격리 등
- 여러 정의(클래스, 함수, 상수 등)가 들어있는 큰 파일을 만드는 것은 지양한다.
- 유사한 컴포넌트끼리 정리하여 구조화
__init__.py파일을 가진 새 디렉토리를 만들어서 파일 분할을 쉽게 할 수 있음- 모듈을
import할 때 구문을 분석하고 메모리에 로드할 객체가 줄어듬 - 의존성이 줄었기 때문에 더 적은 모듈만 가져오면 됨
- 모듈을