⌗ 엘레강트 오브젝트 책을 리뷰해보도록 하겠습니다.
책의 내용은 다양한 언어로 되어있지만, 저는 예시 코드로 Typescript 를 사용해 제 사족을 조금씩 붙일 예정입니다.
Chapter1. Birth
if (price < 100) {
const extra: Cash = new Cash(5);
price.add(extra);
}
객체는 자신의 가시성 범위 안에서 살아갑니다. 위 예제에서 if 블록 안에서만 extra 라는 객체를 볼 수 있기 때문에,
if 블록 내부가 extra 객체의 가시성 범위가 됩니다.
객체는 살아있는 유기체이기에 가시성 범위가 중요하고, 살아있게 만들기 전에 객체의 환경을 정의해야 합니다.
무엇이 객체 외부에 있고, 내부에 남아있는지를 구분하는 것으로 이 주제를 시작합니다.
-er 로 끝나는 이름을 사용하지 마세요
가시성 범위를 이해하고 난 후 가장 먼저 할 일은 클래스에 적합한 이름을 짓는 것입니다.
class Cash {
public constructor(dollars: number) {
// ..
}
}
const five: Cash = new Cash(5);
클래스는 객체의 팩토리이고, 클래스는 객체를 생성합니다. (인스턴스화한다 라고 표현)
예시가 팩토리 패턴이라는 디자인 패턴과는 다르지만, 여기서 new 연산자로 할 수 있는 유일한 작업은 객체라고 불리는 클래스의 인스턴스를 생성하는 것뿐입니다.
팩토리 패턴은 new 연산자가 실행되기 전에 부가적인 로직을 더할 수 있기에 new 연산자를 보다 유연하고 강력하게 만들 수 있습니다.
아래 예시를 보겠습니다.
class Shape {
...
}
class Circle extends Shape {
...
}
class Rectangle extends Shape {
...
}
class Shapes {
public make(name: string): Shape {
if (name === 'circle') {
return new Circle();
}
if (name === 'rectangle') {
return new Rectangle();
}
throw new Error('not found');
}
}
팩토리에서도 객체를 생성하는 최종 단계에서는 여전히 new 연산자를 사용합니다.
핵심은 개념상 팩토리 패턴과 new 연산자가 크게 다르지 않다는 점입니다.
종종 클래스를 객체의 템플릿으로 설명하지만, 이 설명은 잘못됐으며 기술적인 관점에서 클래스가 템플릿과 동일하더라도 이런식의 접근은 해선 안됩니다. 클래스는 객체의 팩토리이고, 객체의 어머니라고 할 수 있습니다.
요점은 클래스를 객체의 능동적인 관리자로 생각해야 한다는 것이며, 클래스는 객체를 꺼내거나 반환할 수 있는 위치이기 때문에 클래스를 저장소 또는 웨어하우스라고 불러야 한다는 점입니다. (저자는 팩토리 패턴을 좋다고 얘기하진 않습니다)
클래스의 이름을 짓는 잘못된 방법은 클래스의 객체들이 무엇을 하고 있는지를 살펴본 후 기능에 기반해서 이름을 짓는 방법입니다.
class CashFormatter {
private dollars: number;
public constructor(dollars: number) {
this.dollars = dollars;
}
public format(): string {
return `$ ${this.dollars}`;
}
}
CashFormatter 클래스의 객체는 dollar 에 저장된 금액을 문자열로 포맷팅하는 일을 수행합니다.
따라서 위 예시에선 객체의 이름을 formatter 라고 짓게 된 것입니다.
이런 명명 원칙은 아주 잘못된 방식이지만 인기가 많습니다. 이 방식을 따르지 않기를 저자는 권합니다.
클래스의 이름은 객체가 노출하고 있는 기능에 기반해서는 안됩니다. 무엇을 하는지가 아니라 무엇인지에 기반해야 합니다.
따라서 위 클래스는 아래와 같이 변경되어야 합니다.
class Cash {
private dollars: number;
public constructor(dollars: number) {
this.dollars = dollars;
}
public usd(): string {
return `$ ${this.dollars}`;
}
}
다시 말해 객체는 역량(capability)으로 특징지어져야 합니다. 속성이 아니라, 할수 있는 일로 설명해야 합니다.
저자가 말하는 클래스의 이름을 객체가 노출하고 있는 기능에 기반해서는 안되고, 무엇인지에 기반해야 한다는 점은 어느정도 공감이 갑니다. 객체지향을 말하는 어떤 책에서는 무엇을 하는지에 중점을 두라고 했던 것 같기도 하지만 말이죠.
여기에 숨어있는 악마는 접미사 '-er' 입니다.
Manager, Controller, Helper, Validator, Dispatcher 등등 전부 잘못 지어진 이름이라고 말하고 있습니다.
이 중에서 규칙에 예외로 둘 수 있는건 Computer, User 정도일 겁니다.
객체는 객체의 외부 세계와 내부 세계를 이어주는 연결장치가 아닙니다.
객체는 캡슐화된 데이터의 대표자입니다. 이 차이를 이해하는게 중요할 것 같네요.
연결장치는 존중받지 못하고, 단순히 정보를 전달하기만 합니다. 반면 대표자는 스스로 결정을 내리고 행동할 수 있는 엔티티입니다.
클래스의 이름이 '-er' 로 끝난다면 이 클래스의 인스턴스는 실제로 객체가 아니라 어떤 데이터를 다루는 절차들의 집합일 뿐입니다.
그렇다면 올바른 클래스의 이름을 짓는 방법은, 객체들이 무엇을 캡슐화할 것인지를 관찰하고 이 요소들에 붙일 이름을 찾아야 합니다.
예를 들어 임의의 숫자 리스트가 존재하고 이 요소 중 소수를 찾는 알고리즘을 만든다고 가정할 때 소수만으로 구성된 리스트를 얻는 것이 목적이라면 클래스의 이름은 Primer, PrimeFinder 등이 아닌 PrimeNumbers 라고 지어야 합니다.
다시 한번 강조하자면 새로운 클래스에 이름을 붙일 때는 무엇을 하는지가 아니라 무엇인지를 포인트로 잡아야 한다는 것입니다.
추가로 언급할만한 좋지 않은 클래스 이름은 Util 이나 Utils 로 끝나는 케이스입니다.
DBClientManager
CacheManager
ClusterManager
ServicesFetcher
EventsFetcher
저도 지금까지 클래스를 만들고 네이밍하면서 위와 같이 manager, handler, fetcher 처럼 '-er' 을 붙인 클래스를 자주 사용하는 편이었습니다. DBClientManager 를 보면 이렇게 네이밍한 이유는 앱에서 사용할 모든 db pool 을 만들고, 앱 종료시 연결을 종료하는 역할등을 하기 때문이었는데요. 무엇을 하는지에 맞춘 네이밍이었다고 생각하지만, 이게 무엇인지? 라고 생각해도 지금은 매니저 같은데.. 하는 생각이 들긴 하네요. 이 책에서 말하는 대로면 그냥 DBClients 라고 하는게 맞았을 것 같기도 하구요.
지금까지 '-er' 을 붙여왔던 클래스 네이밍에 대해 전체적으로 고찰을 해봐야할 것 같습니다.
Util 에 대해서도 참 지금까지 고민 포인트들이 많았는데, 3장에서 다룰 때 저의 의견도 같이 달아보도록 하겠습니다.
'책 리뷰 > 엘레강트 오브젝트' 카테고리의 다른 글
엘레강트 오브젝트 [2장 - (4)] (0) | 2022.05.21 |
---|---|
엘레강트 오브젝트 [2장 - (3)] (0) | 2022.05.14 |
엘레강트 오브젝트 [2장 - (2)] (0) | 2022.05.07 |
엘레강트 오브젝트 [2장 - (1)] (0) | 2022.04.30 |
엘레강트 오브젝트 [1장 - (2)] (0) | 2022.04.24 |