객체지향 패러다임을 인간이 인지할 수 있는 다양한 객체들이 모여 현실 세계를 이루는 것처럼 소프트웨어 세계 역시 인간이 인지할 수 있는 다양한 ‘소프트웨어' 객체들이 모여 이뤄져있다는 믿음에서 출발한다.
하지만 딱 여기까지.
현실세계의 모방이라 하기엔 실행중인 객체지향 애플리케이션의 내부는 전혀 다르다.
현실 속의 객체와 소프트웨어 객체 사이의 가장 큰 차이점은 뭘까? 그것은 현실 속에서는 수동적인 존재가 SW객체로 구현될 때는 능동적으로 변한다는 것이다. 따라서 모방이 아니라 이런 앞의 두줄에서 말한 생각을 기반으로 새로운 세계를 창조하는 것이라고 이해하는게 받아들이기 용이하다.
저자는 이 새로운 세계를 <이상한 나라의 앨리스>의 세계에 비유해서 설명한다.
- 상태: 특정 시점에서의 앨리스의 키
- 행동: 케이크 먹기.. 등 이 행동들에 의해서 상태가 바뀜 앨리스 객체의 특징을 살펴보면
- 앨리스는 상태를 가지며, 상태는 변경가능하다. 또한 행동의 결과는 상태에 의존적이다.
- 앨리스의 상태를 변경시키는 건 앨리스의 행동이다.
a. 행동의 결과는 상태에 의존적이다.
b. 행동의 순서가 결과에 영향을 미친다. - 앨리스는 상태가 변하더라도 앨리스다. 즉, 상태의 변경과 무관하게 유일한 존재로 식별가능하다.
이 1~3의 특징들을 SW객체로 가져와보면,
- 객체는 식별가능한 개체이다.
- 변경가능한 상태/행동/식별자를 가진다.
그리고 앞으로는 이 3가지 상태/행동/식별자에 대해 각각 알아보자.
‘뭔가를 하는 행동'은 과거의 행동의 결과에 의존적이다. 그럼 맨처음부터 지금까지 하려는 행동까지 어떤 행동을 해왔는가를 다 기억해야하나? 너무 비효율적이다. 조금만 생각해보면 행동의 결과인 상태만 기억하면 과거의 모든 행동 이력을 설명하지 않고도 행동의 결과를 쉽게 예측하고 설명할 수 있다는 것을 알 수 있다. 바로 이러한 점 때문에 상태가 필요하다.
상태는 단순한 값과 객체의 조합으로 표현할 수 있다. 이 값과 객체들을 property라고 한다. 이 단순값을 attribute라고 하고, 다른 객체를 가리키는 것은 link라고 한다.
객체의 상태는 저절로 변경되지 않는다. 객체의 상태를 변경하는 것은 객체의 자발적인 행동 뿐이다. 반대로 말하면 외부에 의해 상태가 변경되는 것은 자율성 위반인 것이다. 다시말해, 객체는 스스로의 행동에 의해서만 상태가 변경되는 것을 보장함으로써 객체의 자율성을 유지한다.
이 객체 설계에 대한 것은 책을 읽어나가면서 계속해서 나오고, 배워가는 부분이다. 그런데 상태와 행동의 개념이 나오면부터 강조하는 것은 ‘객체 설계시 초점을 행동에 맞추라’는 것이다. 왜 그래야 하는가? 객체는 다른 객체와 협력하기 위해 존재한다. 따라서 객체가 적합한지를 결정하는 것은 그 객체의 상태가 아니라 행동이다.
상태를 먼저 결정하고 행동을 나중에 결정하는 방법은 설계에 나쁜 영향을 미친다. 이유는
- 상태를 먼저 결정할 경우 캡슐화가 저해된다. 상태에 초점을 맞출 경우 상태가 객체 내부로 깔끔하게 캡슐화되지 못하고 공용 인터페이스에 그대로 노출되버릴 확률이 높아진다.
- 객체를 협력자가 아닌 고립된 섬으로 만든다. 객체가 필요한 이유는 애플리케이션의 문맥 내에서 다른 객체와 협력하기 위해서다. 불행하게도 상태를 먼저 고려하는 방식은 협력이라는 문맥에서 멀리 벗어난 채 객체를 설계하게 함으로써 자연스럽게 협력에 적합하지 못한 객체를 창조하게 된다.
- 객체의 재사용성이 저하된다. 객체의 재사용성은 다양한 협력에 참여할 수 있는 능력에서 나온다. 상태에 초점을 맞춤 객체는 다양한 협력에 참여하기 어렵기 때문에 재사용성이 저하될 수밖에 없다.
이게 더 나아가면 ‘책임 주도 설계'까지 이어지며 이는 앞서 말한 것 처럼 앞으로 계속 배워나갈 주제 중 하나이다.
객체는 다른 객체와 협력하기 위해 존재한다. 따라서 객체가 적합한지를 결정하는 것은 그 객체의 상태가 아니라 행동이라고 했다. 객체가 다른 객체와 협력하는 유일한 방법은 다른 객체에게 요청을 보내는 것이다. 그리고 그 요청을 수신한 객체는 요청을 처리하기위해 적절한 방법에 따라 행동한다.
그리고 이 협력에 행동으로 메세지를 통해 참여하면서 다른 객체의 상태변경을 유발할 수 있다.
상태 변경을 ‘한다' 가 아니라 ‘유발할 수 있다.’ 인 이유가 뭘까
상태 캡슐화이다. 협력을 위한 메시지를 앨리스에게 전송하는 객체이건 음료에게 메시지를 전송하는 앨리스 객체이건 메시지 송신자는 메시지 수신자의 상태변경에 대해서는 전혀 알지 못한다. 이것이 캡슐화가 의미하는 것이다. 객체는 상태를 캡슐 안에 감춰둔 채 외부로 노출하지 않는다. 객체가 외부에 노출하는 것은 행동뿐이며, 외부에서 객체에 접근할 수 있는 유일한 방법 역시 행동뿐이다.
결론적으로 상태를 잘 정의된 행동 집합 뒤로 캡슐화하는 것은 객체의 자율성을 높이고 협력을 단순하고 유연하게 만든다. 이것이 상태를 캡슐화해야 하는 이유다.
트럼프와 토끼로 나눈건, 앨리스가 인물들의 차이점을 의도적으로 무시하고 공통점만을 강조함으로써 트럼프라는 개념으로 단순화, 분류했는데 이게 추상화의 일종이다.
분류는 객체지향의 가장 중요한 개념 중의 하나다. 어떤 객체를 어떤 개념으로 분류할지가 객체지향의 품질을 결정한다.
이 개념에는 3가지 관점이 있고, 이 3가지 관점이 만족 될 때 각 객체는 그 개념의 일원이 된다.
- symbol
- 내연
- 외연
컴퓨터 공학자에게 앞서 살펴본 ‘개념'의 용어를 대체하는 것이 바로 ‘타입'이다. 타입에는
- 데이터 타입
- 객체지향의 타입 이 있다.
컴퓨터가 어떤 작업을 수행하기 위해서는 작업에 필요한 데이터를 메모리 안으로 불러들여야 한다. 메모리에 불러들여진 데이터들은 무수히 많은 0과 1로 치장되어 저장된다. 사람들은 이 메모리 안의 데이터에 특정한 의미를 부여해서 논리형으로 분류했다.
변수
사람은 계산과 기억을 모두 두뇌에서 파지만, 컴퓨터는 연산과 기억을 수행하는 부품이 나눠져 있다. 컴퓨터는 CPU를 사용해 연산하고, 메모리를 사용해 데이터를 기억한다.
메모리상에 데이터를 읽고 쓰고
메모리는 데이터를 저장할 수 있는 메모리 cell의 집합체
메모리 cell = 1 byte(8 bit) size
applicatio이 메모리상에 올라왔을 때, 이 메모리는
- Code
- Data
- Stack
- Heap 의 메모리 구조를 가진다.
Application이 실행되면
- 입력(input) -> 처리(process) -> 출력(output)/저장(storage) 큰 3가지 줄기를 가진다.
- 이 중 처리(process)에서 처리 과정 중의 데이터(ex. 사용자에게 입력받은 데이터라던지..)를 잠시 저정해 두는 것이 필요.
10 + 20 이라는 연산에서 연산 결과 30을 재사용하고 싶다면 메모리 주소를 통해 연산 결과인 30이 저장된 메모리 공간에 직접 접근하는 것 외에는 방법이 없다. 하지만!
- 메모리 주소를 통해 값에 직접 접근하는 것은 치명적인 오류를 발생시킬 가능성이 높은 매우 위험한 일이다.
- 자바스크립트는 개발자의 직접적인 메모리 제어를 허용하지 않는다.
프로그래밍 언어느 ㄴ기억하고 싶은 값을 메모리에 저장하고, 저장된 값을 읽어들여 재사용하기 위해 변수를 사용한다. 따라서 변수(Variable)은
- 값을 저장하기 위해 확보한 메모리 공간
- 그리고 그 메모리 공간을 식별하기 쉽도록 이름이 주어진 기억장소
- 값의 위치를 가리키는 상징적인 이름.
그리고 프로그래밍 언어의 컴파일러 또는 인터프리터에 의해 값이 저장된 메모리 공간의 주소로 치환되어 실행된다.
자바스크립트의 경우는 자바스크립트 엔진이 변수명과 매핑된 메모리 주소를 통해 메모리 공간에 접근해서 저장된 값을 반환한다.
let result = 10 + 20
에서 변수(식별자에 속한다)result는 값 30이 저장되어 있는 메모리 주소 0x066..어쩌고 를 기억해야한다. 즉, 변수는 값이 저장되어 있는 메모리 주소와 매핑 관계를 맺으며, 이 매핑 정보도 메모리에 저장되어야 한다.
변수명 result --> 메모리 주소 0x066..어쩌고 -- 메모리 30
즉 데이터를 목록에 따라 분류하기 시작하면서 타입 시스템이 자라나기 시작했다.
데이터 타입(JS, Python)
JavaScript type is dynamic, weakly typed programming language
/**
* Data Type
*/
console.log(0 / 123); // 0
console.log(123 / 0); // Infinity
console.log(123 / -0); // -Infinity
console.log(123 / 'text'); // NaN (Not a Number)
let bigInt = 1234565677843523426133451243513461561432525125n; // bitInt는 뒤에 n을 붙이면 bigint type으로 다룰 수 있다.
console.log(bigInt);
console.clear();
// Falshy 인 값
console.log(!!0);
console.log(!!-0);
console.log(!!''); // 빈 문자열
console.log(!!null);
console.log(!!undefined);
console.log(!!NaN);
// Truthy 인 값
console.log(!!1);
console.log(!!-1);
console.log(!!'text'); // 비어있지 않은 문자열
console.log(!!{});
console.log(!!Infinity);
-
number
-
string
-
boolean
-
null : 텅텅 비어있는 것을 말함
-
undefined
-
Symbol
원시 타입은 어디에 변수가 선언되어 있느냐에 따라 Memory(local variable)의 Data(global variable)나 Stack memory에 값이 위치해있다.
-
object
- array
- function
- ...
object는 야러 데이터의 상태, 행동을 함께 묶어서 보관할 수 있는 데이터 타입을 복합 데이터, 객체. 라고 한다.
{key: value}
로 표현 가능.object는 어떤 데이터든 복합적으로 들어있을 수 있기 때문에 크기가 정해져있지 않다. 그래서 메모리 사이즈가 정해져있지 않고, 동적으로 조절가능한 Memory Heap에 여러 cell에 걸쳐서 데이터가 보관된다. 참고) 정확한 이해를 위해서는 그림이 좋다. 그림1
이게 전통적인 데이터 타입에서의 ‘타입'이고, 객체 지향의 ‘타입'과는 아래와 같은 연관성이 있다.
객체를 타입에 따라 분류하고 그 타입에 이름을 붙이는 것은 결국 프로그램에서 사용할 새로운 데이터 타입을 선언하는 것과 같다. 앞에서 데이터 타입에 관해 언급했던 두 가지 조언은 객체의 타입을 이야기할 때도 동일하게 적용된다. a. 어떤 객체가 어떤 타입에 속하는지를 결정하는 것은 객체가 수행하는 행동이다. b. 객체의 내부적인 표현은 외부로부터 철저하게 감춰진다.
왜 타입을 사용해야 하는가?
객체지향은 객체를 지향하는 것이므로 객체만 다루면 되지 않냐고 생각 할 수 도 있지만 타입을 사용하는 이유는 인간의 인지 능력으로는 시간에 따라 동적으로 변하는 객체의 복잡성을 극복하기가 너무 어렵기 때문이다.
타입은 시간에 따라 동적으로 변하는 앨리스의 상태를 시간과 무관한 정적인 모습으로 다룰 수 있게 해준다.
앨리스의 상태에 복잡성을 부과하는 시간이라는 요소를 제거함으로써 시간에 독립적인 정적인 모습으로 앨리스를 생각할 수 있게 해준다.
타입은 추상화다 타입을 이용하면 객체의 동적인 특성을 추상화할 수 있다. 결국 타입은 시간에 따른 객체의 상태 변경이라는 복잡성을 단순화할 수 있는 효과적인 방법인 것이다.
- TS abstract class: see https://github.com/Gracechung-sw/typescript-typepython-oop/blob/main/oop/abstract.ts
- Python abstract class: see
객체가 특정 시점에 구체적으로 어떤 상태를 가지느냐를 스냅샷, 객체 다이어그램이라고 하고, 스냅샷처럼 실제로 객체가 살아 움직이는 동안 상태가 어떻게 변하고 어떻게 행동하는지 를 포착하는 것을 동적 모델이라고 한다.
객체가 가질 수 있는 모든 상태와 모든 행도을 시간에 독립적으로 표현하는 것은 타입 모델이라고 하고 이 모델은 객체가 속한 타입의 정적인 모습을 표현하기 때문에 정적 모델이라고도 한다.
타입의 특징을 고려하면 객체지향 설계에 대한 중요한 원칙을 이끌어 낼 수 있다.
어떤 객체를 다른 객체와 동일한 타입으로 분류하는 길은 무엇인가? 그 객체가 타입에 속한 다른 객체와 동일한 행동을 하기만 하면 된다. 결론적으로 객체의 타입을 결정하는 것은 객체의 행동 뿐이다. 객체가 어떤 데이터를 보유하고 있는지는 타입을 결정하는데 마루언 영향도 미치지 않는다. 이러한 이유로, 타입이 데이터가 아닌 행동에 의해 결정된다는 사실은 객체지향 패러다임을 특징 짓는 중요한 몇 가지 우너리와 원칙에 의미를 부여한다.
같은 타입에 속한 객체는 행동만 동일하다면 서로 다른 데이터를 가질 수 있다. 여기서 동일한 행동 = 동일한 책임 = 동일한 메시지 수신
따라서 동일한 타입에 속한 객체는 내부의 데티터 표현 방식이 다르더라도 동일한 메시지를 수신하고 이를 처리할 수 있다. 다만 내부의 표현 방식이 다르기 때문에 동일한 메시지를 처리하는 방식은 서로 다를 수 밖에 없다. (다형성 Polymorphism)
그리고 캡슐화로 훌륭한 객체지향 설계는 외부에 행동만을 제공하고 데이터는 행동 뒤로 감춰야 한다.
책임-주도 설계라고 부르는 객체지향 설계 방법은 데이터를 먼저 생각하는 데이터-주도 설계 방법의 단점을 개선하기 위해 고안됐다.
객체지향에서 일반화/특수화 관계를 결정하는 것은 객체의 상태를 표현하는 데이터가 아니라 행동이다. 어떤 객체가 다른 객체보다 더 일반적인 상태를 표현하거나 더 특수한 상태를 표현한다고 해서 두 객체가 속하는 타입 간에 일반화/특수화 관계가 성립하는 것은 아니다. 그렇다고 생각이 드는게, 다형성으로 데이터, 상태는 각 객체마다 다를 수 있다는 것을 앞에서 살펴봤기 때문이다.
일반화/특수화는 행동에 관한 것이고 일반적인 타입은 특수한 타입에 비해 더 적은 수의 행동을 가지며 특수한 타입은 일반적인 타입에 비해 더 많은 행동을 가진다.
일반화/특수화 관계는 좀 더 이란적인 한 타입과 좀 더 특수한 한 타입 간의 관계다. 이 때 좀 더 일반적인 타입을 슈퍼타입, 좀 더 특수한 타입을 서브타입이라고 한다. 특수한 타입은 일반적인 타입이 할 수 있는 모든 행동을 동일하게 수행할 수 있어야 한다.
결국 객체지향에서 중요한 것은 동적으로 변하는 객체의 ‘상태'와 상태를 변경하는 ‘행위'이다. 클래스는 타입을 구현하기 위해 프로그래밍 언어에서 제공하는 구현 메커니즘일 뿐이다.
객체의 세계에서도 협력이라는 문맥이 객체의 행동방식을 결정한다. 객체지향에 갓 입문한 사람들의 가장 큰 실수는 협력이라는 문맥을 고려하지 않은채 객체가 가져야 할 상태와 행동부터 고민하기 시작한다는 것이다.
중요한 것은 개별 객체가 아니라 객체들 사이에 이뤄지는 협력이다.
그래서 어쩌라구?!
-> 훌륭한 객체지향 설계란 조화를 이루며 쩍극적으로 상호작용하는 협력적인 객체를 창조하는 것이다.
따라서 이번 장에서는
- 먼저 객체지향 설계의 품질을 결정하는 역할, 책임, 협력의 개념에 대해 살펴보고
- 협력이 어떤 식으로 객체의 외양과 특성을 결정하는지 설명한다.
협력이란, 자신에게 할당된 일이나 업무를 처리하던 중에 스스로 해결하기 어려운 문제에 부딪히게 되면 문제를 해결하는데 필요한 지식을 알고 있거나 도움을 받을 수 있는 누군가에게 도움을 요청하게 된다. 요청을 받은 사람은 일을 처리한 후 요청한 사람에게 필요한 지식이나 서비스를 제공하는 것으로 요청에 응답한다.
협력의 이런 요청과 응답에 초점을 맞춰보면, '내가 처리하기 어려운 일'에는 '나에게는 이 일을 수행할 책임이 없고, 내가 요청을 보낸, 이 일을 할 줄 아는 너는 이 일을 수행할 책임이 있어.' 란 의미가 있다.
결국 어떤 등장인물들이 특정한 요청을 받아들일 수 잇는 이유는 그 요청에 대해 적절한 방식으로 응답하는 데 필요한 지식과 행동 방식을 가지고 있기 때문이다. 그리고 요청과 응답은 협력에 참여하는 객체가 수행할 책임의 정의한다.
앞에서 정리했듯이 '결국 어떤 대상에 대한 요청은 그 대상이 요청을 처리할 책임이 있음'을 암시한다. 따라서 책임이란, 어떤 객체가 요청을 처리하기 위해 객체가 알아야 하는 정보와 객체가 수행할 수 있는 행위에 대해 개략적으로 서술한 문장
위의 책임의 정의에서 알 수 있듯이 객체의 책임을
- 객체가 무엇을 알고 있느냐 (Knowing)
- 객체를 생성하거나 계산을 하는 등의 스스로 하는 것
- 다른 객체의 행동을 시작시키는 것
- 다른 객체의 활동을 제어하고 조절하는 것
- 무엇을 할 수 있는가 (Doing)
- 개인적인 정보에 관해 아는 것
- 관련된 객체에 관해 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
으로 구성된다.
그럼 이러한 두 가지를 개발시 객체지향적인 설계르 ㄹ위해선 책임을 어떻게 디자인(나타낼) 할 수 있는가? => 객체의 책임을 이야기 할 때는 일반적으로 외부에서 접근 가능한 공용 서비스의 관점에서 이야기 한다. 즉, 책임은 객체의 외부에 제공해 줄 수 있는 정보와 외부에 제공해 줄 수 있는 서비스의 목록이라 공용 interface를 구성한다.
see TS polymorphism and interface: https://github.com/Gracechung-sw/typescript-typepython-oop/blob/main/oop/polymorphism.ts, https://github.com/Gracechung-sw/typescript-typepython-oop/blob/main/oop/oop-make-coffee.ts
한 객체가 다른 객체에게 전송한 요청은 그 요청을 수신한 객체의 책임이 수행되게 한다. 이처럼 객체가 다른 객체에게 주어진 책임을 수행하도록 요청을 보내는 것을 메시지 전송이라고 한다.
위 그림처럼 세 개의 협력이 협력에 참여하는 등장인물을 제외한 나머지 과정이 너무 유사해서 '하나의 협력'으로 다루고자 한다. 어떻게? '등장인물'을 '역할(role)'개념을 사용하면 세 가지 협력을 모두 포괄할 수 있는 하나의 협력으로 추상화 할 수 있다. 그리고, 어떤 역할에는 어떤 객체라도 가능한게 아니라, 그 역할을 해낼 수 있는 객체는 동일한 메시지를 이해하고 수행할 수 있는 객체로 한정된다.
이제 역할-책임-메시지의 연관성이 연결되어 보인다.
동일한 역할을 수행하는 객체들이 동일한 메시지를 수신할 수 있기 때문에 동일한 책임을 수행할 수 있다.
그리고 협력-역할-책임-메시지-추상화 까지 연결지어 생각해보면
유사한 협력을 추상화해서 역할로 정의하면, 더 다양한 객체들이 협력에 참여할 수 있기 때문에 협력이 유연해지면서 객체지향 설계의 단순성, 유연성, 재사용성을 뒷받침해준다.
referenced by https://prohannah.tistory.com/107
시스템의 기능은 더 작은 규모의 책임으로 분할되고 각 책임은 책임을 수행할 적절한 객체에게 할당된다. 만약 책임을 여러 종류의 객체가 수행할 수 있다면 협력자는 객체가 아니라 추상적인 역할로 대체된다.
이처럼 책임-주도 설계에서는 시스템의 책임을 객체의 책임으로 변환하고, 각 객체가 책임을 수행하는 중에 필요한 정보나 서비스를 제공해줄 협력자를 찾아 해당 협력자에게 책임을 할당하는 순차적인 방식으로 객체들의 협력 공동체가 구축된다. 책임-주도 설계는 객체의 책임과 상호작용에 집중한다.
책임 주도 설계 절차
- 시스템이 사용자에게 제공해야 하는 기능인 시스템의 책임을 파악한다.
- 시스템의 책임을 더 작은 책임으로 분류한다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
- 객체가 책임을 수행하는 중에 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.
책임-주도 설계는 객체의 역할, 책임, 협력을 고안하기 위한 방법과 절차를 제시한다. 설계를 위한 프레임워크를 제공하는 것이다. 반면 디자인 패턴은 책임-주도 설계의 결과를 표현한다.
패턴은 해결하려고 하는 문제가 무엇인지를 명확하게 서술하고, 패턴을 적용할 수 있는 상황과 적용할 수 없는 상황을 함께 설명한다. 패턴은 반복해서 일어나는 특정한 상황에서 어떤 설계가 왜(why) 더 효과적인지에 대한 이유를 설명한다.
특정한 상황에 적용 가능한 디자인 패턴을 잘 알고 있다면 책임-주도 설계 절차를 순차적으로 따르지 않고도 시스템 안에 구현할 객체들의 역할, 책임, 협력 관계를 빠르고 손쉽게 포착할 수 있을 것이다. 디자인 패텅능 책임-주도 설계의 결과물인 동시에 지름길이다.
테스트-주도 개발이 응집도가 높고 결합도가 낮은 클래스로 구성된 시스템을 개발할 수 있게 하는 최상의 프랙티스인 것은 맞지만 객체지향에 대한 경험이 적은 초보자들은 개발을 주도하기 위해 어떤 테스트를 어떤 식으로 작성해야 하는지를 결정하는 데 큰 어려움을 느낀다. 테스트-주도 개발은 객체가 이미 존재한다고 가정하고 객체에게 어떤 메시지를 전송할 것인지에 관해 먼저 생각하라고 충고한다. 그러나 이 같은 종류의 충고는 역할, 책임, 협력의 관점에서 객체를 바라보지 않을 경우에 무의미하다.
테스트-주도 개발은 테스트를 작성하는 것이 아니라 책임을 수행할 객체 또는 클라이언트가 기대하는 객체의 역할이 메시지를 수신할 때 어떤 결과를 반환하고 그 과정에서 어떤 객체와 협력할 것인지에 대한 기대를 코드 형태로 작성하는 것이다.
테스트-주도 개발은 책임-주도 설계의 기본 개념을 따른다. 책임-주도 설계를 통해 도달해야 하는 목적지를 테스트라는 안정장치를 통해 좀 더 빠르고 견고한 방법으로 도달할 수 있도록 해주는 최상의 설계 프랙티스다.
테스트-주도 개발은 다양한 설계 경험과 패턴에 대한 지식이 없는 사람들의 경우에는 온전한 혜택을 누리기 어렵다. 초보자들이 테스트-주도 개발 기법을 따르는 것이 따르지 않는 경우보다 더 훌륭한 코드를 작성하는 것은 사실이지만, 경험 많은 개발자들이 테스트-주도 개발 없이 작성한 코드보다 더 훌륭한 코드를 작성할 수는 없다.
테스트-주도 개발은 객체지향에 대한 깊이 있는 지식을 요구한다. 테스트를 작성하기 위해 객체의 메서드를 호출하고 반환값을 검증하는 것은 순간적으로 객체가 수행해야하는 책임에 관해 생각하는 것이다. 테스트에 필요한 간접 입력 값을 제공하기 위해 스텁(stub)을 추가하거나 간접 출력 값을 검증하기 위해 목 객체(mock object)를 사용하는 것은 객체와 협력해야 하는 협력자에 관해 고민한 결과를 코드로 표현한 것이다.
객체지향을 강력하게 만드는 비밀은 책임과 메시지에 숨겨져 있다.