켄트 벡의 구현 패턴 - 읽기 쉬운 코드를 작성하는 77가지 자바 코딩 비법
-
프로그램을 짤 때는 자신과 컴퓨터뿐 아니라, 다른 사람들을 생각해야 한다.
-
프로그래밍에서 변수가 사용되는 범위를 정하는 것이 중요한 것처럼, 책을 쓸 때도 책에서 다룰 내용의 범위를 정하는 것이 매우 중요하다.
-
여러분은 프로그래머로서 시간과 재능과 돈과 가회를 부여 받았다. 이러한 자원들을 책임감 있게 잘 사용하려면 어떻게 해야 하는가? … 프로그래머는 자기 자신과 CPU뿐 아니라, 자신의 코드를 보고 사용할 다른 사람들을 배려해서 코딩을 해야 한다.
-
코드를 통한 커뮤니케이션에는 몇 가지 단계가 있다. 첫 단계는 생각을 하며 프로그래밍하는 것이다. […] 첫 단계는, 본능에 의해 코딩하는 것을 멈추고 내가 어떤 생각을 하고 있는지 살펴볼 수 있는 여유를 갖는 것이다. 다음 단계는 다른 사람들의 중요성을 인정하는 것이다. […] 하지만 나만큼 다른 사람들도 중요하다는 생각이 들자 커뮤니케이션이 가능한 코드를 작성하기 위해 노력하게 되었다. […] 마지막 3단계, 다른 사람의 존재도 내 존재만큼 중요하다고 생각을 한 후, 그러한 생각을 실천으로 옮기게 되었다. 나는 이 책에 소개된 구현 패턴을 사용해 의식적으로 나 자신만을 위한 코드가 아닌 다른 사람을 위한 프로그램을 작성하기 시작했다.
목차
-
대부분의 프로그램에는 다음과 같은 법칙이 적용된다.
-
프로그램을 새로 짜는 경우보다는 기존 프로그램을 읽는 경우가 많다. 프로그램에 있어 “완성”은 없다. 최초에 프로그램을 개발하는 데 드는 노력보다는 이후 프로그램을 수정하는데 들어가는 노력이 더 크다.
-
프로그램 구조는 몇 가지 상태와 제어 흐름 개념으로 결정된다.
-
프로그램을 읽는 사람은 개념과 더불어 세부 사항까지도 이해해야 한다. 세부 사항을 이해해야 전체 개념에 대한 그림을 그릴 수 있고, 한편 전체 개념을 이해해야 세부적인 구현 내용을 이해할 수 있기 때문이다.
-
C가 복잡해져서 C++이 나왔고 C++이 복잡해져서 자바가 나왔지만, 자바는 또다시 복잡해지고 있다.
프로그래밍 이론
-
프로그램을 최대한 단순화하라. 의미 없는 코드는 모두 제거하라. 설계 시에도 과도한 요소는 모두 빼고, 요구 사항을 분석해서 꼭 필요한 사항만을 뽑아내라. 과도한 복잡도를 제거하면 코드를 새로운 관점에서 바라볼 수 있다.
-
때로 대칭성을 찾아서 표현하면 코드의 중복을 제거할 수 있다. 코드 곳곳에서 비슷한 아이디어가 사용되었다면 대칭성에 따라 아이디어를 일관된 방식으로 표현해야 한다.
-
마지막 원칙은 함께 변화는 로직과 데이터를 함께 관리하고, 변화율이 다른 로직과 데이터는 분리하는 것이다. 변화율을 시간적 대칭성으로 볼 수 있다. […] 변화율은 데이터에도 적용된다. 하나의 객체에 있는 모든 필드는 가급적 함께 변해야 한다.
클래스
-
클래스의 역사는 플라톤이 살았던 시대까지 거슬러 올라간다. 플라톤은 현실 세계에 존재하는 것은 클래스의 인스턴스일 뿐이라고 이야기했다. 플라톤 세계의 클래스는 완벽하지만 현실적이지 못하고, 현 세계에 있는 인스턴스는 존재하기는 하지만 어딘가 불완전하다. 객체 지향 프로그래밍은 후기 서양 철학의 아이디어를 받아들여서 프로그램을 클래스와 객체로 구성한 것이다. 여기서 클래스는 비슷한 성질을 가진 것을 총칭하며, 객체는 클래스가 구체화된 것이다.
-
클래스를 사용하는 기본 이뉴는 데이터가 로직에 비해 빈번하게 변해가기 때문이다. 각 클래스 선언은 “클래스의 로직과 데이트는 함꼐 사용되며, 로직은 데이터에 비해 변화율이 낮다. 클래스 내부의 데이터는 관련 로직에 의해 변화하며, 클래스 내부에서 사용하는 데이터의 변화율을 비슷하다.”는 내요을 전달한다.
-
클래스 계층을 구성하는 것은 일종의 압축 기법을 사용하는 것이다. 클래스 계층의 사용은 상위클래스의 코드를 모두 하위클래스에 붙여 넣는 것과 같은 효과를 가져온다. 다른 압축기법과 마찬가지로, 클래스 계층을 사용하면 코드를 읽기 어려워진다.
추상 인터페이스
-
소프트웨어 개발에 관한 오랜 격언 중 하나로, 구현이 아니라 인터페이스에 맞춰 코딩하라는 말이 있다. 이는 설계상의 결정을 필요 이상으로 노출하지 말라는 뜻이다.
-
여기서 ‘인터페이스’란 ‘구현이 빠진 여러 연산의 집합’을 의미한다. […] 인터페이스 추가에는 비용이 발생한다. 인터페이스를 사용하게 되면 우리는 인터페이스를 배우고 이해하며 문서화하고 디벙기하고 정리하고 조회해서 적당한 이름을 지어야 한다. 인터페이스 수를 최대한 늘린다고 해서 소프트웨어 비용이 최소화되는 것은 아니다. 따라서 인터페이스를 통해 유연성을 얻을 수 있는 경우에만 인터페이스에 비용을 지불해야 한다.
인터페이스
-
자바 인터페이스를 사용하는 것은 “여기까지가 내가 원하는 것이고, 이외의 내용은 상관하지 않는다.”라고 이야기하는 것과 같다. 자바 인터페이스는 대중적인 언어 중에서는 자바에서 처음 사용된 중요한 혁신적 개념이다. […] 인터페이스는 필드를 배제하고 연산만을 나타내므로 사용자는 구현이 변경되더라도 신경 쓸 필요가 없다.
-
인터페이스를 사용하면 구현을 바꾸는 것은 쉽지만, 인터페이스 자체를 바꾸기는 쉽지 않다. 인터페이스를 조금이라도 바꾸거나 추가하면, 그 인터페이스를 구현하는 모든 클래스를 수정해야 하기 때문이다. 만약 인터페이스가 이미 널리 사용되고 있어서 모든 구현을 바꾸는 것이 쉽지 않을 경우라면 전체 설계를 개선하기는 어렵다.
추상 클래스
-
결국 추상 클래스와 자바 인터페이스의 장단점은 인터페이스 수정이 용이성과 단일 클래스가 여러 인터페이스를 지원할 수 있는지 여부로 귀결된다. […] 추상 클래스에는 이런 문제가 없다. 구현을 사용할 수 있는 길이 열려있는 한, 기존 설계를 망가뜨리지 않고 새로운 연산을 얼마든지 추가할 수 있다.
하위 클래스
-
하위클래스를 선언하는 것은 “이 객체는 상위클래스와 같다. 이 부분만 제외하면..”이라 말하는 것과 같다. 제대로 된 상위클래스를 갖고 있다면 하위클래스는 강력한 힘을 발휘할 수 있다. 적당한 메소드를 오버라이드할 경우 간단한 코드 몇 줄이면 기존 연산과 다른 변형을 만들어 낼 수 있기 때문이다.
-
객체 지향 언어가 사용되기 시작할 무렵, 하위클래스 사용은 마법의 약인 것처럼 보였다. […] 하지만 곧 사람들은 상속이 구현을 공유하기 위한 기법이므로 공통된 구현을 갖는 경우에 가장 효과적으로 사용될 수 있다는 것을 알게 되었다. 그러나 오래지나지 않아 하위클래스 사용의 문제점이 드러났다. 첫째, 일단 하위클래스를 표현하고자 하는 변형을 효과적으로 나타내지 못한다고 생각되면 하위클래스가 표현하고자 하는 변형을 효과적으로 나타내지 못한다고 생각되면 하위클래스를 통해 얽혀 있는 코드를 모두 정리해줘야 한다. 둘째, 하위클래스를 이해하기 위해서는 먼저 상위클래스를 이해해야 한다. 상위클래스가 점점 복잡해지면서 이 문제는 더욱 심각해졌다. 셋째, 하위클래스가 상위클래스 세부 구현 특성에 의존할 수 있으므로 상위클래스의 수정이 위험해진다. 마지막으로, 클래스 상속 계층이 복잡해지면서 이 모든 문제가 삼화된다.
-
하위클래스 사용을 제한하는 마지막 요소는 동적으로 변화하는 로직을 나타낼 수 없다는 것이다. 하위클래스를 사용하는 경우, 객체를 생성할 때 그 객체의 목적을 알아야 하며 이는 이후에 바뀔 수 없다. 변화하는 로직을 나타낼 때는 조건문이나 위임을 사용하라.
상태
-
효과적으로 상태를 관리하기 위한 키포인트는 유사한 상태를 묶어서 관리하고 각 상태를 별도로 관리하는 것이다. 두 가지 상태가 유사한지 알 수 있는 두 개의 단서로는, 1) 두 개의 상태가 동일한 연산 안에서 사용되고, 2) 동일한 시점에 생성되고 소멸되는지 보면 된다. 함께 사용되고 생성 소멸 시점이 동일한 두 가지 상태를 밀접하게 관리하는 것은 좋은 아이디어일 가능성이 높다.
간접접근
-
내가 사용하는 기본 정책은 클래스 내부(내부 클래스 포함)에서는 직접 접근을 허락하지만 클래스 외부에서는 간접 접근을 사용하는 것이다. 하지만 객체 상태에 대한 대부분의 접근이 외부에서 이뤄지는 것이라면, 좀더 어려운 설계상 문제가 발생한다.
지역변수
-
지역변수는 다음과 같은 역할을 한다. 컬렉터 : 이후 사용을 위한 정보를 모은다. 카운터 : 특정 객체의 수를 저장하는 특수 컬렉터 설명 : 표현 내용을 지역 변수에 저장하면 독자가 좀 더 쉽게 이해할 수 있다. 재사용 : 값이 바뀌지만 기본 값을 다시 사용해야 하는 경우, 그 값을 지역 변수에 저장할 수 있다. 원소 : 지역 변수는 현재 사용하는 컬렉션의 원소를 저장히기 위해 사용될 때도 많다.
제어흐름
-
하지만 자바의 경우 제어 흐름은 언어의 근간 중 하나다. 인접한 구문은 순서대로 수행된다. 조건문을 사용해 특정 상태에서만 코드를 수행하게 할 수도 있다. 루프를 사용하면 반복적으로 코드를 수행할 수 있다. 예외를 사용하면 제어 흐름을 스택 아래쪽으로 한번에 바꿀 수도 있다.
되돌림 메세지
-
때로는 대칭성과 같은 “미학적” 만족만을 위해 새로운 메소드를 만드는 것이 의미 없게 느껴질 때도 있다. 하지만 미학은 생각보다 중요한 것이다. 미학은 엄격한 선형적 논리 사고보다 더 많은 두뇌 활동을 요구한다. 여러분이 코드의 미학에 대한 자신의 식견을 높이게 되면, 자신의 코드에서 받는 미학적 느낌을 통해 코드의 품질을 평가할 수 있게 된다. 코드에서 받는 느낌은 이미 유용성이 알려져 있는 다른 패턴만큼이나 중요하다.
메소드 가시성
-
내가 사용하는 전략은 가급적 가시성을 낮추는 것이다. 하지만 프로그래밍은 그렇게 단순하지 않다.
-
공용 : 메소드를 공용으로 선언하면 패키지 외부에서도 이 메소드가 유용한 것이라고 이야기하는 것이다. 또한 공용 메소드 가시성을 사용하는 것은 프로그래머가 코드 관리를 책임지겠다는 뜻이기도 하다. 프로그래머는 메소드를 수정할 경우 모든 수정을 본인이 담당하거나, 최소한 사용자들에게 수정 사항을 알려줄 책임이 있다.
-
패키지 : 패키지 가시성은 해당 메소드가 같은 패키지의 다른 객체에는 유용하지만 패키지 외부의 객체에는 공개하지 않겠음을 의미한다.
-
보호 : 보호 가시성은 하위클래스를 사용해서 코드를 재사용하려 할 때 유용하다.
-
전용 : 외부 객체와 상관없이 모든 메소드 호출을 제어할 수 있다는 점에서, 전용 메소드는 최고의 유연선을 확보해준다.
취득 메소드
-
객체 상태에 대한 접근을 허용하기 위한 한 가지 방법은 상태를 반환하는 메소드를 제공하는 것이다. 보통 자바에서 이런 메소드에 접두어 “get”을 붙인다. […] 나는 가시성 수준이 높은 취득 메소드를 좋아하지는 않지만, 몇 가지 상황은 인정한다.