본문 바로가기

책 리뷰/엘레강트 오브젝트

엘레강트 오브젝트 [2장 - (2)]

⌗ 엘레강트 오브젝트 책을 리뷰해보도록 하겠습니다.

책의 내용은 다양한 언어로 되어있지만, 저는 예시 코드로 Typescript 를 사용해 제 사족을 조금씩 붙일 예정입니다.

 

메서드 이름을 신중하게 선택하세요

 

네이밍.. 항상 개발 시간 중 상당한 시간을 잡아 먹는 부분입니다.

책의 저자가 얘기하는 경험에 의한 가장 간단한 법칙은 빌더 이름은 명사로, 조정자의 이름은 동사로 짓는 것입니다.

빌더는 뭔가를 만들고 새로운 객체를 반환하는 메서드를 가리키며, 항상 뭔가를 반환합니다.

 

pow(base: number, power: number): number;
speed(): number;
employee(id: number): Employee;
parsedCell(x: number, y: number): string;

 

저는 이것과는 항상 반대로 해왔던 것 같습니다. 뭔가를 반환해야하기에, get 이나 generate 같은 동사를 항상 메서드 앞에 붙이곤 했었는데요.. 이 책의 내용이 무조건 맞다고 생각하진 않기 때문에 메서드 네이밍에 대해선 좀 더 찾아봐야겠지만, 제가 해왔던 네이밍들을 예시로 들면서 이 책에서 제시하는대로 조금 수정해보겠습니다.

 

class ABCRequest {
  ...
  
  generateRequestConfigs(): ABCRequestConfigs { // -> configs(): ABCRequestConfigs
    ...
    return new ABCRequestConfigs(...);
  }
  
  ...
}

 

조정자란 객체로 추상화한 실세계 엔티티를 수정하는 메서드를 의미합니다.

조정자의 반환 타입은 항상 void 고, 동사로 써야 합니다.

 

save(content: string): void;
put(key: string, value: number): void;
remove(employee: Employee): void;
quicklyPrint(id: number): void;

 

이부분은 기존에 제가 해왔던 네이밍과 거의 동일한 것 같습니다. 무언가를 하는 행동이라고 생각해 항상 동사를 써왔었습니다.

저자가 주장하는 바를 반대로 말하자면 빌더가 void 를 반환해서는 안되고, 조정자가 무언가를 반환하는 식으로 작성하면 안되겠네요.

좀 더 딥하게 가보겠습니다.

 

- 빌더는 명사다

 

OOP 가 바라보는 객체라는것은 살아있는 유기체고, 본인의 의무를 수행하기 위한 방법을 스스로 알고 있습니다.

무언가 지시를 따르는 것이 아니고, 객체의 외부 입장에선 요청만 할 뿐 어떻게 하는지 알 필요도 없습니다.

그리하여 메서드의 이름을 동사로 지으면 객체에게 무엇을 할지 를 알려주는 형태가 됩니다. 객체에게 뭘 만들라고 하는건 어긋나는 방식이고, 요청은 할 뿐 방법은 객체 스스로 결정해야 합니다.

 

load(url: URL): ReadableStream;
read(file: File): string;
add(x: number, y: number): number;

 

따라서 위 메서드들은 전부 잘못 네이밍되었고, 아래처럼 변경되어야 합니다.

 

stream(url: URL): ReadableStream;
content(file: File): string;
sum(x: number, y: number): number;

 

add 와 sum 은 언뜻 보기에 뭐가 다른건가.. 싶긴한데 add 는 더하다 / sum 은 합계 라는 뜻을 가지고 있죠.

이 부분은 즉 객체에 add 하라고 명령하는게 아닌, sum 을 달라고 요청을 하고 그 안에서 add 는 객체가 알아서 해야 한다는 얘기입니다.

이러한 사고 방식을 갖는데는 꽤나 시간이 걸릴 것 같기도 합니다.

 

- 조정자는 동사다

 

이것도 사고 방식부터 접근해보자면, 조정자 라는건 객체를 조작하고 그냥 끝입니다.

조작을 하고 그 바뀐 상태까지 알려달라는건 조정자에 대한 예의가 아닌거죠. 따라서 조정자는, 즉 객체를 조정한다면 그 이름은 동사고 반환값이 없습니다.

 

- 빌더와 조정자 혼합

 

하지만 가끔은 조정을 하고 그 결과를 동시에 알고 싶은 경우가 많습니다. 실무에서 상당히 자주 보이는 부분이죠.

 

class Document {
  write(content: ReadableStream): number;
}

 

위 예시에서 write 메서드는 데이터를 쓰는 동시에 쓰여진 바이트 수를 카운트합니다.

하나의 메서드가 여러가지 일을 처리하고 있고, 그 목적이 명확하지 않기 때문에 명사로 해야할지 동사로 해야할지 애매합니다.

 

class OutputPipe {
  write(content: ReadableStream): void;
  bytes(): number;
  time(): number;
}

class Document {
  output(): OutputPipe;
}

 

output 메서드는 빌더입니다. 여기서 생성한 객체로 write 를 호출하면, 관련 데이터를 모으기 시작합니다.

이 객체를 이용해 bytes, time 등의 메서드로 다양한 정보를 가져올 수 있습니다. 책에서는 이런 방향의 리팩토링을 추천하고 있습니다.

 

OOP 의 전체 목적은 개념을 고립시켜 복잡성을 낮추는 것입니다. 하나 이상의 값을 반환하는 메서드는 코드를 지저분하게 만들고 유지보수성을 저하시킵니다.유지보수성 결국 이게 중요합니다.

 

- boolean 값을 결과로 반환하는 경우

 

대표적으로 isEmpty, equals, exists 와 같은 메서드가 boolean 을 반환합니다. 이 메서드들도 결국 지금까지의 네이밍과 맞지 않는데요.

책에서는 boolean 을 반환하는 메서드들은 이 규칙에서 예외적인 케이스라고 합니다.

값을 반환하기 때문에 빌더에 속하지만, 가독성을 생각해서 형용사로 짓는 방법을 추천하고 있습니다.

 

empty(): boolean;
readable(): boolean;
negative(): boolean;

 

우리는 isEmpty 와 같은 것에 너무 익숙해져있고 이게 훨씬 자연스럽게 들리므로 실제 코드에선 is 를 빼고 코드를 읽을 땐 isEmpty 로 읽는 방법을 추천하는데요. 이건 굳이 이렇게까지 해야하나.. 싶은 생각이 바로 들긴 하네요.

boolean 을 반환하는 메서드 앞에 is 나 has 를 붙이는건 너무 오래된 관습이다보니.. 무엇이 좋을지는 좀 더 생각해봐야겠습니다.

 

요약하자면 메서드의 목적이 중요합니다. 메서드는 빌더나 조정자 둘 중 하나여야하고 빌더이면서 조정자여선 안됩니다.