Skip to content

Latest commit

 

History

History
353 lines (203 loc) · 13.8 KB

ch3.md

File metadata and controls

353 lines (203 loc) · 13.8 KB

Ch3. 좋은 코드의 일반적인 특징

  • 코드가 디자인이고, 디자인이 코드
  • 클린코드의 궁극적인 목표는 코드를 가능한 견고하고 결함을 최소화하고 완전히 자명하도록 하는 것

학습목표

  • 견고한 소프트웨어의 개념 이해
  • 작업 중 잘못된 데이터를 다루는 방법
  • 새로운 요구 사항을 쉽게 받아들이고 확장할 수 있는 유지보수가 쉬운 소프트웨어 설계
  • 재사용 가능한 소프트웨어 설계
  • 개발팀의 생산성을 높이는 효율적인 코드 작성

1. 계약에 의한 디자인

  • 소프트웨어는 사용자가 직접 호출하거나 코드의 다른 부분에서 호출

  • 컴포넌트는 기능을 숨겨 캠슐화하고 함수를 사용할 고객에게는

    API를 노출해야 함

    • API를 디자인할 때 예상되는 입력, 출력, 부작용을 문서화해야 함
  • 그러나 문서화가 런타임 시의 소프트웨어 동작까지 강제할 수는 없음

1-1. 계약에 의한 디자인(Design by Contract)

만약 호출자가 모듈(루틴)의 모든 선행조건을 충족한다면, 해당 모듈은 종료시 모든 후행조건과 불변식이 참이 될 것을 보증해야 한다.

  • 관계자가 기대하는 바를 암묵적으로 코드에 삽입하는 대신 양측이 동의하는 계약을 먼저 한 다음, 계약을 어겼을 경우는 명시적으로 왜 계속할 수 없는지 예외를 발생시키는 것
  • 이 책에서 말하는 계약은 소프트웨어 컴포넌트 간의 통신 중에 반드시 지켜져야 할 몇 가지 규칙을 강제하는 것
  • 계약은 주로 사전조건과 사후조건을 명시하지만 때로는 불변식과 부작용을 기술함

  • 사전조건(precondition)
    • 코드가 실행되기 전에 체크해야 하는 것들
    • module을 호출하기 위해 참이어야 하는 것들
  • 사후조건(postcondition)
    • 사전조건과 반대로 여기서는 함수 반환 값의 유효성 검사가 수행됨
    • module이 자기가 할 것이라고 보장하는 것
  • 불변식(invariant)
    • 함수가 실행되는 동안에 일정하게 유지되는 것
    • 함수의 문제가 없는지 확인하기 위한 것
    • 호출자의 입장에서 이 조건이 언제나 참이라고 보장하는 것
  • 부작용(side-effect)

1-2. 파이썬스러운 계약

  • pep-316(link) - 연기(defered) 상태
  • 일반적인 디자인 원칙하에 파이썬으로 구현할 수 있는 방법(저자)
    1. 메서드, 함수 및 클래스에 RuntimeError 예외 또는 ValueError 예외를 발생시키는 제어 메커니즘을 추가하는 것
    2. 문제를 특정하기 어려울 경우, 사용자 정의 예외를 만드는 것이 바람직
    3. 데코레이터나 더 작은 함수를 생성하여 사전/사후조건에 대한 검사 부분과 핵심 기능 부분의 코드를 격리된 상태로 유지하는 것이 좋음

1-3. 계약에 의한 디자인(DbC) - 결론

  • 디자인 원칙의 주된 가치는 문제가 있는 부분을 효과적으로 식별할 수 있음
  • 코드가 더욱 견고해짐
    • 각 컴포넌트는 자체적으로 제약 조건과 불변식을 관리
    • 이러한 불변식이 유지되는 한 프로그램이 정상 동작함
  • 무엇을 검증할 것인지 신중히 검토해야 함

1-4. DbC vs TDD or UT

  • reference , reference2

  • DbC 는 테스트 주도 개발이나 단위테스트를 보완하는 방법

  • 내부 코드 상태를 검사 할 때 내부적으로 일관성을 유지하도록 하며 상대적으로 저비용 접근 방식

  • legacy code 나 잘못 설계된 API와 같이 UT/TDD의 cost가 높은 상황에서 유용

2. 방어적(Defensive) 프로그래밍

  • DbC: 계약에서 예외를 발생시키고 실패하게 되는 모든 조건을 기술
  • 방어적 프로그래밍: 객체, 함수 또는 메서드와 같은 코드의 모든 부분을 유효하지 않은 것으로부터 스스로 보호할 수 있게 하는 것
  • 방어적 프로그래밍은 여러 측면을 고려한 기술
  • 다른 디자인 원칙과 결합된 경우 특히 유용(보완 관계 가능)

2-1. 에러 핸들링(Error Handling)

  • 예상되는 에러에 대해서 실행을 계속할 수 있을지 아니면 극복할 수 없는 오류여서 프로그램을 중단할지를 결정하는 것
  • 오류가 발생하기 쉬운 상황에서 사용, 일반적으로 데이터 입력 확인 시 자주 사용
  • 에러 처리 방법
  1. 값 대체
  • 결과 값을 안전한 다른 값으로 대체
  • 견고성과 정확성 간의 trade-off
  1. 예외 처리
  • 예외적인 상황을 명확하게 알려주고 원래의 비즈니스 로직에 따라 흐름을 유지하는 것
  • 프로그램이 꼭 처리해야 하는 정말 예외적인 비즈니스 로직을 except 블록과 혼합하여 사용하면 상황이 더욱 악화될 수 있음
  • 호출자가 알아야만 하는 실질적인 문제가 있을 경우에는 예외를 발생시켜야 함

함수가 너무 많은 예외가 있다

  • 응집력이 약함(캡슐화가 안되어 있다)

  • 많은 책임을 가지게 됨

  • 분할화 해야 함(작은 단위의 함수)

2-2. 파이썬의 예외처리 권장사항

  1. 올바른 수준의 추상화 단계에서 예외 처리
    • 예외는 오직 한 가지 일을 하는 함수의 한 부분이어야 함
    • 함수가 처리하는(발생시키는) 예외는 캡슐화된 로직과 일치해야 함
    • 예제
  2. Traceback 노출 금지
  • 보안을 위한 고려 사항
  • 예외를 처리할 때 오류가 너무 중요하다면 전파해도 됨
  • 파이썬에서 traceback은 매우 유용하고 많은 디버깅 정보를 포함

[주의] 그와 동시에 이 정보는 악의적인 사용자에게도 중요 정보 유출 발생 위험

  1. 비어있는 except 블록 지양
  • 너무 방어적이여서 아무것도 하지 않은 채로 조용히 지나쳐버리는 비어있는 except 블록
  • 파이썬의 안티패턴(realpython blog)

대안

  • 구체적인 예외 사용
  • except 블록에서 실제 오류 처리
  • 가장 좋은 방법은 위의 두 항목 동시 적용
  1. 원본 예외 포함
  • 오류 처리시 메시지를 변경할 경우, 원래 예외를 포함하는 것이 좋음
  • raise <e> from <original_exception> 구문 사용

2-3. 어설션(Assertion) 사용하기

  • 절대로 일어나지 않아야 하는 상황에서 사용
  • assert 문에 사용된 표현식은 불가능한 조건을 의미
  • 이 상태가 된다는 건 소프트웨어에 결함이 있음을 의미
  • 잘못된 시나리오에 도달할 경우 프로그램이 더 큰 피해를 입지 않도록 하는 것
  • 비즈니스 로직과 섞거나 소프트웨어의 제어 흐름 메커니즘으로 사용해서는 안된다.
    • **AssertionError 에외를 처리하지 않는다**
result= condition.holds()
assert result > 0, "에러 {0}".format(result)

3. 관심사의 분리(Seperation of Concerns, SOC)

  • 참고링크(blog)
  • SRP
  • 책임이 다르면 컴포넌트, 계층, 모듈로 분리되어야 함
  • 프로그램의 각 부분은 기능의 일부분(관심사)에 대해서만 책임을 지며 다른 부분에 대해서는 알 필요 없음
  • 목표 : 파급 효과를 최소화하여 유지보수성을 향상

파급(ripple) 효과: 어느 지점에서의 변화가 전체로 전파되는 것

3-1. 응집력(cohesion)과 결합력(coupling)

저자) 잘 정의된 소프트웨어는 높은 응집력과 낮은 결합력을 갖는다.

  • 응집력
    • 객체가 자고 잘 정의된 목적을 가져야 하며 가능하면 작아야 한다는 것을 의미
    • 응집력이 높을수록 더 유용하고 재사용성이 높아진다.
  • 결합력
    • 두 개 이상의 객체간의 의존성
    • 낮은 재사용성: 다른 상황에서 이 함수를 사용하기 어려움
    • 파급 효과: 하나를 변경하면 다른 부분에도 영향이 감
    • 낮은 수준의 추상화: 너무 가까운 두 함수는 서로 다른 추상화 레벨에서 문제 해결이 어려움

4. 개발 지침 약어

4-1. DRY/OAOO

  • DRY(Do not Repeat Yourself)

  • OAOO(Once and Only Once)

  • 중복을 반드시 피해야 한다.

  • 즉, 코드에 있는 지식은 단 한번, 단 한곳에 정의되어야 함

  • 코드 중복은 유지보수에 직접적인 영향을 미치는 문제임

    • 오류 발생이 쉬움
    • 비용이 비쌈
    • 신뢰성이 떨어짐

4-2. YAGNI(You Ain't Need it)

  • 과잉 엔지니어링을 하지 않기
  • 유지보수가 가능한 소프트웨어를 만드는 것은 미래의 요구 사항을 예측하는 것이 아니다.
  • 현재의 요구사항을 잘 해결하기 위한 소프트웨어를 작성하고 가능한 나중에 수정하기 쉽도록 작성하는 것

4-3. KIS(Keep It Simple)

  • 디자인이 단순할 수록 유지 관리가 쉽다.(simple is best)

4-4. EAFP / LBYL

  • EAFP(Easier to Ask Forgiveness than permission)
    • 일단 코드를 실행하고 실제 동작하지 않을 경ㅇ우에 대응
  • LBYL(Look Before You Leap)
    • 사전에 미리 확인해라

파이썬은 EAFP 방식으로 만들어졌으며, 그렇게 할 것을 권장한다. (암묵적인 것보다 명시적인 것이 좋다.)

EAFP vs LBYL(reference)

5. 컴포지션과 상속

  • 코드를 재사용하는 올바른 방법은 여러 상황에서 동작 가능하고 쉽게 조합할 수 있는 응집력 높은 객체를 사용하는 것
  • 동시에 결합력을 최소한으로 줄이는 것

5-1. 상속이 좋은 선택인 이유

  • 부모 클래스를 쉽게 전수 받을 수 있는 장점이 있지만 모든 것을 새로운 클래스로 가져왔기 때문에 새로운 정의에 너무 많은 기능을 추가하게 되는 단점도 있다.
  • 상속이 좋지 못한 경우
    • 상위 클래스는 막역한 정의와 너무 많은 책임을 가짐
    • 하위 클래스는 확장하려고 하는 상위 클래스의 적절한 세분화가 아님
  • 상속이 좋은 예
    • 부모 클래스의 기능을 물려받으면서 추가 기능을 더하려는 경우(extension) or 특정 기능 수정하는 경우(overriding)
    • 인터페이스의 정의, ISP
    • 예외 (Exception)

5-2. 상속 안티패턴

  • 도메인 문제를 해결하기 위해 적절한 데이터 구조를 만든 다음에 이 데이터 구조를 사용하는 객체를 만들지 않고 데이터 구조 자체를 객체로 만드는 경우

5-3. 파이썬의 다중상속

  • 올바르게 구현되지 않으면 문제가 커짐
  • 믹스인을 활용한 애플리케이션

메서드 결정 순서(MRO)

  • 다중 상속 구조

3장.파이썬의다중상속

믹스인(minin)

  • 코드를 재사용하기 위해 일반적인 행동을 캡슐화해놓은 기본 클래스
  • 그 자체로는 유용하지 않으며 대부분이 클래스에 정의된 메서드나 속성에 의존함
  • 다른 클래스와 함께 믹스인 클래스를 다중 상속하여 믹스인에 있는 메서드나 속성을 사용

6. 함수와 메서드의 인자

파이썬 함수의 인자 전달 메커니즘과 SW 엔지니어링에서의 모범 사례에서 발견되는 일반적인 원칙을 살펴본다.

  • 함수의 서명(signature) : 매개변수와 반환형을 총칭

6-1. 파이썬의 함수 인자 동작방식

인자는 함수에 어떻게 복사되는가

  • 파이썬의 첫 번째 규칙은 모든 인자가 값에 의해 전달(passed by a value)된다는 것
  • 변형(mutable) 객체를 전달하고 함수 내부에서 값을 변경하면 결과에서 실제 값이 변경될 수 있음
  • 함수 인자를 변경하지 않아야 함

가변인자

  • 별표(*)를 사용해 인자를 패킹하여 함수에 전달해서 사용

  • 반복은 변수 언패킹의 가장 좋은 예

6-2. 함수 인자의 개수

  • 함수나메서드가 너무 많은 인자를 사용하면 나쁜 디자인
  1. 구체화(reification)
    • 전달하는 모든 인자를 포함하는 새로운 객체를 만듬
  2. 가변인자나 키워드 인자를 사용하여 동적 서명을 가진 함수를 만듬
    • 매우 동적이어서 유지보수하기가 어려워 남용하면 안됨

함수 인자와 결합력

  • 함수 서명의 인수가 많을수록 호출자 함수와 밀접하게 결합될 가능성이 커짐

많은 인자를 취하는 작은 함수의 서명

  • 공통 객체에 파라미터 대부분이 포함되어 있다면 리팩토링을 통해 코드를 향상시킬 수 있음
  • 변경 불가능한 객체를 사용하여 부작용을 최소화

7. 소프트웨어 디자인 우수 사례 결론ㄴ

7-1. 소프트웨어의 독립성(orthogonality)

  • 직교 : 두 요소가 독립적
  • 하나의 변경이 외부 세계의 영향을 최소화해야함
    • 관심사의 분리, 응집력, 컴포넌트 격리 등

7-2. 코드 구조

  • 여러 정의(클래스, 함수, 상수 등)가 들어있는 큰 파일을 만드는 것은 지양한다.
  • 유사한 컴포넌트끼리 정리하여 구조화
  • __init__.py 파일을 가진 새 디렉토리를 만들어서 파일 분할을 쉽게 할 수 있음
    • 모듈을 import할 때 구문을 분석하고 메모리에 로드할 객체가 줄어듬
    • 의존성이 줄었기 때문에 더 적은 모듈만 가져오면 됨