JIGGAG

네이밍, 퍼블릭 상수

2021년 8월 29일

네이밍과 퍼블릭 상수

메서드 이름

클래스의 이름은 객체 자신이 주체가 되도록 *er을 사용하지 않도록 하였다. 그렇다면 클래스에 정의된 메서드의 이름은 어떻게 정의하면 좋을까?

이 책에서는 무언가를 생성하는 빌더는 명사, 데이터를 변환하는건 동사로 짓도록 제안하고 있다.

빌더 builder라는 무언가를 만들고 새로운 객체를 반환하는 메서드는 항상 무언가를 반환하기 때문에 그것이 또 다른 객체가 된다면 클래스의 이름처럼 그 자체가 주체가 될 수 있도록 명사로 짓는다. cell, parsedCell처럼 이 메서드가 cell을 반환할 것이라는 예상을 할 수 있도록 이름을 지어주었다.

조정자 manipulator에서는 대상이 되는 무언가를 수정하는 것으로 반환 타입은 항상 void이다. 빌더가 항상 새로운 무언가를 반환하는 것과 반대로 항상 아무것도 반환하지 않고 단순하게 데이터를 수정할 뿐이므로 print, save처럼 동작을 나타낸다.

빌더와 조정자 사이에 어떤 메서드도 추가적으로 존재해서는 안되고 무언가를 새로 만들어서(A) 데이터를 가공하고(B) 반환하고자 한다면 애초에 새로운 무언가를 만드는 C라는 빌더 메서드를 사용하면 된다.


빌더

무언가를 반환하는 메서드의 이름을 동사로 정의한다는 것은 뜨거운 물 주세요가 아니라 물을 끓여서 주세요가 되는 것이다. 이것은 원하는 뜨거운 물을 달라고 하는 것이 아니라 원하는 모양을 얻기 위해 순차적으로 물 + 끓이기를 호출하는 프로시저가 되며 객체지향에 어긋난다.

메서드의 이름을 통해 객체가 설계된 목적과 수행하는 역할 등을 이해할 수 있도록 하고, 객체 스스로가 의미를 갖고 있을 수 있도록 해주는 것이 객체를 지향하는 것 아닐까


조정자

조정자의 반환값이 생긴다면 음악의 볼륨을 줄인다가 아니라 음악의 볼륨을 줄이고 상태를 알려주세요라고 하는 것이다. 빌더만이 값을 반환하고 조정자는 엔티티를 조작할 뿐이다.


빌더 + 조정자

하나의 메서드에서 데이터를 변환하고 새로운 무언가를 반환한다면 목적이 하나가 아니라 복잡해지게 된다. 결국 무언가를 반환해야 하므로 새로운 객체를 만들고 그 객체가 반환하기, 변환하기 메서드를 각각 갖고 있도록하였다. 객체 + 객체 = 객체라는 모양이 계속 유지되는 방식이다.


boolean 반환하는 메서드

boolean 메서드 관련한 통상적이 네이밍 규칙을 따라서 isExist, isEmpty, hasA 이런 형태로 쓰고 있다. 그러나 저자는 이런 형태보다는 exist, empty처럼 형용사를 사용하기를 이야기하고 있다.

빌더만이 무언가를 반환한다는 앞에서 이야기한 내용에 따라 boolean을 반환하는 메서드도 무언가를 반환하는 빌더라고 분류하였기 때문이다. 문법을 따라가다보면 isEquals, isExists보다는 equalsTo, present로 작성하는 것이 올바르기 때문인데, 너무나 is*, has* 형태의 네이밍이 통용되고 있는 상황에서 고민이 더 필요한 부분이라고 생각이 든다.


그래서

모든 메서드의 목적이 무엇인지 확인하고 빌더, 조정자 둘 중 하나의 역할을 갖도록 하면 좀 더 유지보수가 수월한 작은 단위의 객체로 관리할 수 있게 된다.

네이밍을 명사, 동사, 형용사를 명명하는데 기준이 되는 포인트는 객체 입장에서 생각했을때라고 생각한다. read, add는 개발자 입장에서 읽어오고 추가한 것이지만, 객체 입장에서는 content, sum으로 자신의 컨텐츠나 값의 합을 가져온것이다. obj.save()는 객체 입장에서는 저장을 했을뿐이지 저장한 데이터를 반환까지 해야하는 것은 아니다. 그러나 이것이 통상적으로 저장한 결과값을 반환을 받을 것이라 기대하고 있는데, 이건 정말 논의가 필요하지 않을까


불변한 객체

근데 빌더가 immutable이라면 조정자는 mutable이라고 생각이 드는데, 그러고 다시 보니 앞선 스터디에서 진행한 내용 중 생성자에서만 초기화한다라는 의미와 상반되는 느낌이 들었다. 조정자로 이미 생성된 객체의 값을 변경하게 된다면 이것 생성자의 역할을 벗어나는 것 아닌가? 생성자가 아닌 곳에서 값을 변경하고 싶다면 변경된 모양의 객체를 새로이 반환하는 또 다른 객체를 만들었어야한다. 조정자는 새로운 객체를 반환값으로 갖게 되고 이건 조정자가 아니라 빌더가 되는 것이다. 그렇다면 조정자라는 것은 필요하지 않게 되고 모든것은 빌더로 대통합 될 거라고 생각되는데, (챕터를 넘겨보니 뒤에 불변한 객체 이야기도 나오는 것을 보니) 아직 앞부분이라서 메소드가 이런 역할을 한다 라는 것을 알려주는 챕터라고 이해하고 넘어가기고 했다.


퍼블릭 상수

처음에 상수 자체를 쓰지 말라는 것으로 알고 깜짝 놀랬다. 그런데 상수 자체가 아니라 클래스 안에 퍼블릭 상수를 쓰지 말자라는 의미였다.

객체를 생성하지 않고도 퍼블릭 상수에 바로 접근 할 수 있기 때문에 객체가 닫혀있지 않고 완전 오픈된 형태를 갖게 된다. 캡슐화된 객체를 사용하는 의미가 없어지기 때문인데, 전에 이야기 했던 캡슐화 대상이 없는 빈 객체로 정적 메서드 형태를 사용하지 않는 것과 같은 맥락으로 생각하였다.

class Constants {
	static EOL = "\r\n"
}

console.log(`줄바꿈${Constants.EOL}`); // 줄바꿈

이렇게 직접적으로 Constants.EOL로 퍼블릭 상수에 접근해서 쓸 수 있게 된다. 물론 클래스 마다 상수를 선언하지 않아도 되니깐 코드 자체의 중복은 줄어들었으나 각각 사용하는 곳마다 해당 클래스와 결합되어버렸다.


결합도 증가, 응집도 저하

상수를 사용하는 클래스들과 상수까지 모두 결합되어 Constants 클래스를 변경하게 되면 사용하던 곳에서 상수값에 의존된 동작이 모두 달라져 버릴 것이다. 퍼블릭 상수가 사용되어야하는 방법이나 어떤 영향력을 갖는지 전달하지 않았음에도 모든 곳에서 접근 가능하기 때문에 상수를 변경하거나 유지보수하기 어려워졌다.

퍼블릭 상수를 사용하는 객체마다 상수를 사용하는 방법을 알고 있어야하는데, 그렇게되면 객체가 원래 갖고 있던 의미에 상수를 사용하는 방법까지 더하면서 응집도가 낮아지게 된다.

객체가 처음 만들어지는 목적에서 점점 벗어나 이 객체가 하는 이야기가 무엇이지?하게 되고 하나의 객체가 하나의 목적을 갖는 단일 책임 원칙을 지키지 못하게 되는 것이다.


새로운 클래스

그렇다면 상수를 가져와서 하고자하는 기능을 담고 있는 새로운 클래스를 만들어서 사용하면 어떨까?

Constants.EOL 상수를 사용해 줄바꿈을 하고자했다면 줄바꿈을 해주는 역할을 하는 클래스를 만드는 것이다.

class EOLString {
  private readonly origin: string;

  constructor(text: string) {
    this.origin = text;
  }

  toString() {
    return `${this.origin}\r\n`;
  }
}
console.log(new EOLString('줄바꿈').toString()); // 줄바꿈

이제 줄바꿈 기능을 하는 클래스를 만들었으니 클래스를 사용하는 것 만으로도 해당 기능의 목적을 명확하게 전달할 수 있게 되었다.

처음에 상수를 사용하는 방법과 클래스를 가져와서 사용하는 것의 결합도가 차이가 없어보이기는 하지만 클래스마다 인터페이스를 통해 결합이나 분리가 가능하기 때문에 유지보수에 도움을 준다.

예외처리가 필요할때 상수를 사용하게 되면 사용처마다 매번 예외처리하는 로직을 구현해주어야하지만, 이처럼 클래스를 만들어 사용한다면 인터페이스를 유지하면서 동작의 예외조건을 추가할 수 있게 된다.

class EOLString {
  private readonly origin: string;

  constructor(text: string) {
    this.origin = text;
  }

  toString() {
		if (!this.origin.length) {
			throw new Error('최소 1글자 입력');
		}

    return `${this.origin}\r\n`;
  }
}
console.log(new EOLString('').toString()); // Error: 최소 1글자 입력

만약 또 다른 상수가 필요하다면 그 기능을 하는 새로운 클래스를 하나 더 만들어서 사용하면 된다. 클래스가 너무 많아지는 것은 아닐까? 하는 생각이 들지만 객체지향의 목적에 맞는 각각 객체마다 자신의 의미만을 최소한으로 갖고 있으면서 클래스 사이에 결합이나 중복을 최소화하는 것이 중요하고 궁극적인 지향점인 유지보수성 최대화를 위해서는 작은 단위의 클래스가 많아지는 것은 괜찮다는 것이 와닿는다.


중간에 이런 질문이 떠올랐다

애초에 다른 값을 가지는 퍼블릭 상수를 사용했어도 되는 것 아닐까?

class Constants {
	static EOL = "\r\n"
	static TAB = "\t"
}

이렇게 역할을 나눠서 상수를 만들었다면, 상수를 변경하지 않고 변경되는 역할을 하는 또 다른 상수를 만들었다면 괜찮지 않을까하는 생각을 해보았다. 그러나 이렇게 상수를 역할마다 나눴다면 상수 변경에 의한 코드 영향력은 최소화할 수 있었겠지만 예외처리가 필요해지는 경우를 대비하지 못하게 된다. 기존의 문제점인 구현부마다 로직을 추가해주어야하는 것이 동일하기 때문에 클래스로 만드는 것이 더 효율적으로 보여진다.

그렇다면 다음 질문으로 대체 어느 정도의 상수를 클래스로 만들어야 하는 것일까? 서비스 개발을 하다보면 정말 상수 그 자체로만 존재하는 예를 들면 시크릿키가 있다.

이 값들은 서비스 운영 중에는 절대 변하지 않을 값이기도 하고 만약 변경된다면 서비스 전체에 반영이 되어야하는 것이다. 물론 클래스로 만들어서 사용해도 동일하게 동작하지만 이런 것들은 상수로 그대로 접근해서 사용해도 괜찮지 않을까?

이 질문에 대한 대답은 스스로 생각해보았을때 둘 다 괜찮을 것 같았다. 단 하나 신경쓰이는 것이 있다면 어떤 것은 클래스 안에 존재하는 상수고 어떤건 퍼블릭 상수로 그냥 오픈되어있다는 것... 이럴때 유지보수를 다시 생각해보면, 한쪽으로 사용하는 것이 좋을듯하다!