몇 주 동안 고민하던 클래스 내에서의 메소드 분리에 대한 문제를 어제 카페에 질문했었다.
다행히도 질문을 좋게 봐주시는 분이 계셨고, 정말 내가 원하는 답변을 들을 수 있었다.
몇 주 동안 답답하던 마음을 한 방에 뚫어주셨다.
우리가 사용하는 클래스와 함수는 자신만이 가지고 있는 단 한 가지의 책임이 존재한다.
SRP라는 단일 책임 원칙을 지켜야 한다는 것을 알아두자.
내가 궁금했던 것은 클래스 내의 메소드를 분리했을 때, 그 메소드가 프로토타입에 선언되기 때문에
인스턴스가 프로토타입 체인을 타고 자신의 것 처럼 메소드를 사용할 수 있는데, 그렇게 되면 인스턴스에게는 필요하지 않은 분리된 메소드를 사용할 수 있는 점이 마음에 들지 않았고, 이 문제를 어떻게 해결할 수 있을까에 대한 것이었다.
다시 한 번 생각해보자.
분리된 메소드가 인스턴스에게 필요하지 않다면 그 분리된 메소드는 클래스의 책임일까?
예를 들어보자.
class Baseball {
constructor() {
this.answerNumbers = [];
}
createAnswerNumbers() {
const answerNumbers = [];
const allNumbers = this.getAllNumbers(9);
while (answerNumbers.length < 4) {
const randomIndex = Math.floor(Math.random() * allNumbers.length);
const pickedNumber = allNumbers.splice(randomIndex, 1);
answerNumbers.push(...pickedNumber);
}
return (this.answerNumbers = answerNumbers);
}
getAllNumbers(length) {
const allNumbers = Array(length)
.fill()
.map((_, i) => i + 1);
return allNumbers;
}
}
위와 같은 Baseball이라는 클래스가 존재할 때, createAnswerNumbers메소드에서 사용할 수 있는 숫자를 모아서 return하는 getAllNumbers메소드를 분리했다고 하자.
인스턴스의 관점에서 보지 말고 Baseball이라는 클래스의 관점에서 바라보자.
Baseball 클래스가 1부터 length만큼의 배열을 return하는 함수가 필요할까? 즉, 책임이 있을까?
내 생각에는 getAllNumbers메소드는 Baseball 클래스의 책임이 아니다. 즉, util함수로 빼서 이를 참조하게 해야 한다.
Baseball클래스는 게임을 진행하는 데 필요한 4자리의 숫자만 생성하면 된다.
그렇다면 한 번 더 생각해보자.
util함수로 getAllNumbers함수를 만들게 되면 과연 이 함수는 의미가 있는 함수일까?
단순히 1부터 length만큼의 배열만을 반환하는 이 함수는 그닥 쓸모가 있어보이지 않는다.
즉, getAllNumbers함수 자체는 필요하지 않은 함수이다.
쓸모 있게 만들기 위해서는 다른 함수에서도 쓰일 수 있도록 한 가지 "쓸모 있는"기능을 하도록 만들어야 한다. 쓸모 있게 만들면 "재사용"이 가능해진다.
getAllNumbers를 쓸모 있게 만들어보자.
1부터 length만큼 배열을 만드는 것에서 1이 아닌 주어진 숫자부터 length까지 배열을 만드는 함수로 만들어보자.
const createNumbersFromStartToFinish = (startNumber, finishNumber) => {
const numbers = Array(finishNumber - startNumber + 1)
.fill()
.map((_, i) => i + startNumber);
return numbers;
};
이렇게 만들어 볼 수 있다.
아까보다는 훨씬 나아진 것 같다. 하지만 아직 부족하다. 왜냐하면 단순히 1씩 증가하기 때문이다.
1씩 증가하는 함수를 쓸 곳이 많아보이지는 않는다.
그렇다면 1씩 증가하는 것이 아닌 증가시키고자 하는 수도 외부에서 받아보자.
const createArithmeticSequence = (startNumber, finishNumber, commonDifference) => {
const length = parseInt((finishNumber - startNumber) / commonDifference) + 1;
const numbers = Array(length)
.fill()
.map((_, i) => i * commonDifference + startNumber);
return numbers;
};
조금 더 복잡해졌지만 결과적으로 등차수열을 얻을 수 있게 되었다.
위 코드가 좋은 코드인 것은 아직 잘 모르겠다.
하지만 확실한 것은 맨 처음 getAllNumbers함수와 비교했을 때 훨씬 재사용성이 높아졌다.
이렇게 한 함수에 한 가지 "쓸모 있는"책임을 갖도록 꾸준하게 연습해야겠다는 생각을 했다.
카페에서 답변을 통해 방향을 잡게 되었고, 이제는 연습만이 살길이다.
사실 지금까지 메소드 분리에만 집중하느라 생각하지 못했던 부분인데, getAllNumbers메소드는 애초에 필요가 없는 메소드였다. 왜냐하면 Baseball클래스의 constructor에
this.allNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
이렇게 할당해놓고, 필요할 때 사용하면 되기 때문이다.
성능에도 함수로 1~9까지 만드는 것보다 변수에 담아놓고 사용하는 것이 더 좋을 것이다.
난 하드코딩을 하지 않기 위해 위와 같은 변수를 만들지 않았던 것인데, createAnswerNumbers메소드에서 결국은 "9"라는 하드코딩을 하기 때문에 결국은 똑같다. 아니 오히려 성능과 가독성이 나쁘기 때문에 더 안좋다.
카페에서 좋은 답변을 받아서 너무 기분이 좋고, 달라진 시야를 통해 더 노력하는 사람이 될 것이다.
아, 그리고 내일은 우테코 마지막 코딩 테스트가 있는 날이다. 코딩 테스트의 결과에 너무 신경 쓰지 않고 지금까지 배운 것을 시험해본다는 생각으로 임할 것이다.
그렇게 괴물이 많은 곳에서 합격할 수 있다는 생각이 들지 않는다.