본문 바로가기
Programming/Java

[도서] 켄트 벡의 구현패턴 - 읽기 쉬운 코드를 작성하는 77가지 자바 코딩 비법

by 읽고 쓰는 개발자 2022. 2. 10.

켄트 벡의 구현패턴 - 읽기 쉬운 코드를 작성하는 77가지 자바 코딩 비법 내용 정리

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788960770317 

 

켄트 벡의 구현 패턴 - 교보문고

읽기 쉬운 코드를 작성하는 77가지 자바 코딩 비법 | [ 책 소개 ] 기능적으로 올바르게 동작한다고 해서 모두 훌륭한 코드는 아니다. 훌륭한 코드는 프로그래머의 의도를 일관되게 전달해서, 다른

www.kyobobook.co.kr

커뮤니케이션을 돕는 프로그래밍 기법

  • 단계
    1. 생각을 하며 프로그래밍하는 것.
    2. 다른 사람들의 중요성을 인정하는 것.
    3. 다른 사람의 존재도 내 존재만큼 중요하다는 생각을 한 후 그러한 생각을 실천으로 옮기는 것.
  • 구현 패턴을 사용해 의식적으로 나 자신만을 위한 코드가 아닌 다른 사람을 위한 프로그램을 작성하는 것.

훌륭한 프로그래밍의 공통적 가치

  1. 커뮤니케이션
  2. 단순성
  3. 유연성

구현 패턴의 근간이 되는 원칙들

  1. 지역적 변화 : 코드를 수정할 때 함께 바꿔야 하는 부분을 최소화하라.
  2. 최소 중복 : 중복을 줄이기 위한 방법 - 짧은 구문, 짧은 메소드, 작은 객체, 작은 패키지 등 작은 부분으로 나누는 것.
  3. 로직과 데이터의 결합
  4. 대칭성 (Symmetry) : 하나의 아이디어를 프로그램 전체에서 일관된 방식으로 표현하는 통일성
  5. 선언적 표현
  6. 변화율 : 시간적 대칭성
  • 구현패턴은 미래 코드 확장이나 수정이 쉬운 깔끔한 코드를 추구하면서도, 당장 얻을 수 있는 이득에도 초점 맞춘다. (p.48)
  • 구현패턴은 프로그래머의 인간적인 욕구-자신의 일에 자긍심을 갖고 싶어 하고 공동체의 든든한 일원이 되고 싶어하는 것 등-을 성취하도록 도와준다.

[클래스] 관련 패턴

  • 클래스 : **"이 데이터들은 함께 사용되는데, 그에 관련된 로직이 이것이다"**라고 이야기하고 싶을 때 클래스를 사용한다.
  • 단순한 상위클래스 이름 : 클래스 계층의 최상위에 위치하는 클래스 이름은 단순하게 짓는다.
  • 한정적 하위클래스 이름 : 상위클래스와의 유사점과 차이점을 분명히 드러내는 이름을 사용한다.
  • 추상 인터페이스 : 인터페이스와 구현을 분리한다.
  • 인터페이스 : 자주 변하지 않는 추상 인터페이스에는 자바 인터페이스를 사용한다.
  • 버전 인터페이스 : 하위인터페이스를 사용해 기존 인터페이스를 안전하게 확장한다.
  • 추상 클래스 : 자주 바뀔 것 같은 추상 인터페이스에는 추상 클래스를 사용한다.
  • 값 객체 : 산술 값처럼 동작하는 객체를 사용한다.
  • 특화 : 관련된 연산 사이의 유사점 및 차이점을 분명하게 나타낸다.
  • 하위클래스 : 1차원적 변화는 하위클래스를 사용해서 표현한다.
  • 구현자 : 연산 내용이 바뀌었다면 기존 메소드를 오버라이드해서 사용한다.
  • 내부 클래스 : 클래스 내부에서 유용하게 사용할 수 있는 코드를 모아 전용 클래스로 사용한다.
  • 인스턴스별 행동 : 인스턴스에 따라 로직에 변화를 준다.
  • 조건문 : 명시적 조건에 따라 로직에 변화를 준다.
  • 위임 : 여러 종류의 객체 중 하나에 위임해서 로직에 변화를 준다.
  • 플러그인 선택자 : 리플렉션을 이용한 메소드 호출로 로직에 변화를 준다.
  • 익명 내부 클래스 : 필요한 메소드에서 한두 개의 메소드만 오버라이드하는 객체를 만들어서 사용한다.
  • 라이브러리 클래스 : 마땅히 들어갈 곳이 없는 기능들을 묶어서 정적 메소드로 표현한다.

[상태] 관련 패턴

  • 상태 : 시간에 따라 변화하는 값을 사용하여 연산한다.
  • 접근 : 상태에 대한 접근을 제한해서 유연성을 조절한다.
  • 직접 접근 : 객체 내의 상태를 직접 접근한다.
  • 간접 접근 : 좀더 나은 유연성을 위해 메소드를 통해 상태에 접근한다.
  • 공용 상태 : 클래스의 모든 인스턴스에 적용되는 상태는 필드에 저장한다.
  • 가변 상태 : 같은 클래스의 인스턴스마다 다른 상태를 유지해야 할 경우 상태를 맵에 저장한다.
  • 외재 상태(Extrinsic State) : 객체와 연동된 특수 상태는 상태의 사용자가 소유하는 맵에 저장한다.
  • 변수 : 변수는 상태 접근에 필요한 네임스페이스를 제공한다.
  • 지역 변수 : 지역 변수는 단일 범위 내에서만 유효한 상태를 저장한다.
  • 필드 : 필드는 객체가 생성될 때부터 소멸될 때까지 상태를 저장한다.
    • 도우미: 도우미 필드는 객체의 여러 메소드에서 사용하는 객체를 저장한다. 여러 메소드에서 객체를 파라미터로 전달받는다면, 파라미터를 도우미 필드로 바꾸고 생성자에서 필드를 설정하는 방법을 고려할만하다.
    • 플래그 : 불린 플래그는 "객체가 두 가지 다른 방식으로 동작함(생명기간동안 동작 방식이 변함)"을 의미한다. 플래그에 따라 결정을 내리는 코드가 중복되어 있다면 대신 전략 필드를 사용하는 것을 고려하라.
    • 전략 : 객체의 연산을 하는 다른 방법이 있음을 나타내는 경우, 그 부분을 수행하는 객체를 필드에 저장하라. 객체의 생명기간 동안 연산 방법이 바뀌지 않는 경우라면, 생성자에서 전략 필드를 설정하라. 그렇지 않다면 전략 필드를 수정하는 메소드를 제공하라.
    • 상태 : 객체의 행위 양식을 결정. 상태 필드는 스스로 다음 상태를 설정한다.
    • 부속 : 부속(component) 필드는 해당 객체가 소유하는 객체나 데이터를 저장한다.
  • 파라미터 : 파라미터는 메소드가 활성화된 동안 상태를 전달한다.
  • 수집 파라미터(collectiong parameter): 여러 개의 메소드를 통해 복잡한 결과를 얻기 위해 파라미터를 전달한다.
  • 파라미터 객체 : 자주 사용하는 긴 파라미터 목록은 객체로 만들어서 통합한다.
  • 상수 : 변하지 않는 상태는 상수로 저장한다. ( static final 로 선언 , enum)
  • 역할 제시형 작명 : 변수 이름은 연산에서의 역할을 반영하여 짓는다.
  • 선언형 타입 : 변수에 대한 일반적 타입을 선언한다.
  • 초기화 : 변수 초기화는 가급적 선언적으로 한다.
  • 열성적 초기화 : 인스턴스가 생성될 때 필드를 초기화한다.
  • 게으른 초기화 : 초기화 비용이 높은 객체의 경우, 객체가 실제 사용되기 직전에 초기화한다.

[행위] 관련 패턴

  • 제어 흐름 : 연산을 여러 단계로 나타낸다.
  • 주요 흐름 : 주요 제어 흐름을 명확하게 표현한다.
  • 메시지 : 메시지를 보내서 제어 흐름을 표현한다.
  • 선택 메시지 : 여러 선택 사항을 내타내기 위해 메시지 구현자를 다양화한다.
  • 더블 디스패치 : 두 가지 축으로 메시지 구현자를 다양화해서 중첩된 선택을 표현한다.
  • 분리 메시지 : 복잡한 연산은 밀접한 단위의 연산으로 나눈다.
  • 되돌림 메시지 : 메시지를 같은 수신자에게 보내서 제어 흐름에 대칭성을 부여한다.
  • 초청 메시지 : 다른 방식으로 구현될 수 있는 메시지를 보내서 미래에 일어날 변형을 대비한다.

[메소드]

로직을 나누는 이유

  • 중요한 부분과 덜 중요한 부분 구분
  • 기능 호출 / 기능 구현 부분 구분
  • 같은 기능 매번 다시 구현하는 것 방지 (재사용)
  • 로직 간의 연관성 쉽게 나타내고 파악 가능
  • 메소드 이름만으로도 자신이 원하는 정보 파악 가능

[메소드] 관련 패턴

  • 조합 메소드(composed method) : 다른 메소드에 대한 호출로 메소드를 작성한다.
    • 메소드의 길이는 평균 5-15줄 넘기지 말라.
    • 메소드 구성할 때 추측이 아닌 사실에 근거하라.
    • 일단 동작하는 코드 작성 후 구성 방식을 결정하라.
    • 메소드 구분이 어려울 땐 모든 메소드를 인라인시켜서 커다란 메소드를 만든 후, 메소드 구현에서 새로 얻은 경험을 바탕으로 다시 메소드를 나누는 방법을 사용하라.
  • 의도 제시형 이름: 메소드가 의도하는 바를 나타내는 이름을 사용한다. (구현 전략이 사용자에게 중요한 문제가 아니라면 이름에서 제외해라.)
  • 메소드 가시성: 메소드는 가급적 전용으로 한다.
    • 공용 : 패키지 외부에서도 이 메소드가 유용한 것이라고 이야기하는 것. 프로그래머가 코드 관리를 책임지겠다는 뜻.
    • 해당 메소드가 같은 패키지의 다른 객체에는 유용하지만 패키지 외부의 객체에는 공개하지 않겠음을 의미. 패키지 메소드 가시성을 사용했다면 그 메소드를 전용으로도 구현 가능하지는 않은지, 혹은 더 널리 유용하게 사용되기 위해 공용으로 선언하는 것이 낫지 않은지 확인하는 것이 좋다.
    • 보호 : 하위 클래스를 사용해서 코드를 재사용하려 할 때 유용. 패키지 가시성보다 제한적인 것 같지만 사실 둘의 관계는 독립적. (ex.패키지 외부의 하위 클래스에서도 보호 메소드를 사용할 수 있다.)
    • 전용 : 외부 객체와 상관 없이 모든 메소드 호출을 제어할 수 있다는 점에서 전용 메소드는 최고의 유연성을 확보해준다. 외부 객체에게 있어 해당 메소드는 큰 가치가 없다고 이야기하는 것.
  • 메소드 객체 : 복잡한 메소드는 새로운 객체로 바꾼다.
  • 오버라이드 메소드 : 특화를 나타내기 위해 오버라이드를 사용한다.
  • 오버로드 메소드 : 같은 연산에 대해 다른 인터페이스를 제공한다.
  • 메소드 반환 타입 : 반환 타입에는 가급적 가장 일반적인 타입을 사용한다.
  • 메소드 주석 : 코드 자체에서 쉽게 얻을 수 없는 정보는 주석을 통해 나타낸다.
  • 도우미 메소드 : 주요 연산을 좀더 명확하게 표현하기 위해서 작인 전용 메소드를 사용한다.
  • 변환 : 객체 형변환은 명확하게 표현한다.
  • 변환 메소드 : 단순하고 제한적인 변환에 대해서는 원본 객체에서 변환된 객체를 반환하는 메소드를 제공한다.
  • 변환 생성자 : 대부분의 변환에 대해서는 원본 객체를 인자로 취하는 변환될 객체의 생성자를 제공한다.
  • 생성 : 객체 생성을 명확히 표현한다.
  • 완결 생성자 : 완결된 형태를 갖는 객체를 반환하는 생성자를 작성한다.
  • 공장 메소드 : 좀더 복잡한 객체를 생성할 때, 생성자 대신 정적 메소드를 제공한다.
  • 내부 공장 : 좀더 많은 설명이 필요하거나 이후 개선이 필요한 객체 생성의 경우 도우미 메소드로 캡슐화한다.
  • 컬렉션 접근자 메소드 : 컬렉션에 제한적인 접근만을 허용하는 메소드를 제공한다.
  • 불린 설정 메소드 : 커뮤니케이션에 도움이 된다면, 불린 값을 설정하는 두 개의 메소드(상태별로 하나씩)를 제공한다.
  • 쿼리(질의) 메소드 : isXXX라는 이름으로 된 메소드를 사용해서 불린 값을 반환한다.
  • 동등성 메소드 : equals()와 hashCode() 를 함께 정의한다.
  • 취득 메소드 : 때로 필드 값을 반환하는 메소드를 사용해서 필드에 대한 접근을 제공한다.
  • 설정 메소드 : 드물게, 메소드를 사용해서 필드 값을 설정한다.
  • 안전한 복사: 접근자 메소드를 통해 전달하거나 전달되는 인스턴스를 복사해서 앨리어스(alias) 문제를 회피한다.
    • 안전한 복사는 통제할 수 없는 외부 접근에서 코드를 보호하는 일시적인 해결책일 뿐이다. 따라서 가급적 어떤 구현의 핵심 기법으로 사용하는 것은 피하는 편이 낫다. 변경 불가능한 객체 (immutable object) 와 적합한 조합 메소드(composed method) 를 통해 더욱 간결하고 커뮤니케이션에 도움이 되면서도 문제를 적게 발생시키는 인터페이스를 만들 수 있다.

[컬렉션]

  • 컬렉션에 깔려 있는 메타포
    • 여러 값을 가진 변수 (multi-valued variable)
    • 수학적 집합 (부분적 적용)
  • 이슈(컬렉션 사용으로 전달하는 내용에 관한 문제)
  • 인터페이스 (컬렉선 인터페이스가 독자에게 전달하는 내용 : 기능 등에 대한 정보 제공)
  • 컬렉션 구현이 독자에게 전달하는 내용
    • 컬렉션에 대해 구현 클래스를 선택하는 것은 주로 성능과 관련이 있다.
  • 자바 Collections 클래스에서 제공하는 주요 기능에 대한 개괄적 내용
  • 상속을 통해 컬렉션을 확장하는 법

[발전하는 프레임워크]

애플리케이션 수정 없이 프레임워크 수정하기

  • 이상적인 프레임워크 업그레이드는 기능 추가를 하면서도 기존 기능이 변함 없이 작동하게 해야 한다.
  • 어쩔 수 없이 호환성이 유지되지 않는 업그레이드가 필요한 경우 그 비용을 최소화해야 한다.
  • 프레임워크를 개발할 때는 복잡도를 낮출 만한 발전의 여지를 남겨두면서도 충분히 단순해서 당장 사용할 수 있어야 한다. 또한 적용 범위를 확대할 여지를 남겨두면서도 당장 사용할 수 있을 정도의 적용성을 가져야 한다.

호환성 없는 업그레이드 : 업그레이드를 여러 단계로 나누면 클라이언트에게 앞으로 어떤 변화가 생길지 미리 알려주게 되어 언제 코드를 고쳐야 하는지 결정할 수 있게 해준다.