이 글은 2004년 12월 14일, 네오위즈에서 일하던 시절에, 팀 내부에 공유한 메일입니다. (영어 단어나 어법 오류 등은 수정했습니다.) 현재의 제 생각과는 약간 다른 부분도 있지만,
- 문제 영역의 언어로 이름짓기
- 적절한 비유의 사용
- 최소화 인터페이스
등은 저 뿐만 아니라 많은 사람들이 동의하는 프로그래밍의 원칙들입니다.
이름 짓기
바쁜 일정과 많은 업무로 인해서 팀 내에서는 ‘이름 짓기’가 중요하게 여겨지지 않고 있지만, 저는 이름 짓기에 대해서 강한 의견을 가지고 있는 사람입니다. ;-)
객체와 메서드의 이름을 지을 때는 그것이 하는 역할에 맞게 이름을 짓는 것이 좋습니다. 당연히 이 역할은 객체가 해결하려는 문제의 도메인 안에서, 그 도메인의 언어로 정의됩니다. 따라서, 객체와 메서드의 이름은 문제 도메인의 언어로 기술하는 것이 가장 바람직합니다. (일반화된 이름은 원래 문제 도메인의 이름이 아닌 경우가 있지만, ‘일반화’ 역시 일종의 해결하려는 문제입니다)
예를 들어, 6살짜리 어린이가 가게에서 사탕을 사먹는 문제에 대해서 해결 방법을 기술하려면, 6살짜리 어린이가 이해할 수 있어야 하고, 사탕이라는 단어가 거의 반드시 들어가야겠죠. 또한, 사탕을 사 먹어야 하는데, 다른 도메인의 언어, 즉 음표라든가, 양자역학 같은 단어가 나오면 곤란합니다.
junkmemocheck를 희생양으로 삼아 예를 들어보죠. (junkmemocheck는 쪽지를 너무 많이 보내는 사람을 차단하기 위해 사용자별로 최근에 쪽지를 보낸 수를 세어주는 컴포넌트입니다.)
메서드 이름 짓기
제가 이해한 바로는, junkmemocheck(이하, 줄여서 JMC)에 관련된 시나리오는 다음과 같습니다.
- 사용자 A가 쪽지를 보낼 때, A가 쪽지를 보냈음을 JMC에 알려줍니다.
- 사용자 A가 미리 정해진 시간 내에, 쪽지를 보낸 수를 얻거나, 너무 많이 보냈다는 정보를 얻어옵니다.
이를 좀 더 단순한 말로 적어보죠.
- 특정 사용자가 쪽지를 보냈다.
- 특정 사용자가 최근에 쪽지를 보낸 수를 주세요.
메서드 이름으로 바꿔보죠.
쪽지는 Memo라는 용어로 대치하고, ‘미리 정해진 시간 내에’라는 requirement에 대해서 ‘Recently’라는 단어를 도입했습니다.
- sentMemo(user)
- getCountOfMemoSentRecently(user)
좀 더 줄이면,
- sentMemo(user)
- getRecentMemoSent(user) 또는 getMemoSent(user)
클래스 이름 짓기
이러한 역할에 비추어보면, junkmemocheck는 (Sent)MemoCounter 같은 이름이 더욱 적당하지 않을까 싶습니다. (사실 ‘정해진 시간 내’라는 제약 조건이 있어서, “Counter”라는 비유가 약간 부적절해 보이므로, 좀 더 나은 비유가 있으면 좋을 듯 하네요.)
MemoCounter
+ increaseCount(user)
+ getRecentCount(user)일반화된 이름 짓기
나중에 다른 요구사항이 생겨서, 쪽지만이 아니라 다른 곳에도 이 어플리케이션이 사용되기도 결정되었다고 합시다. 우리는 “Counter”라는 비유를 이미 사용했으므로, 이를 그대로 사용하면 될 것 같습니다. 사용자 대신 슬롯(slot)이라는 개념을 사용합니다.
Counter 클래스의 역할(Role)은 다음과 같습니다.
- k 슬롯의 카운터를 하나 증가시킨다.
- 일정 시간 내에 k 슬롯에 증가된 카운터 값을 가져온다.
이를 바탕으로 다음과 같이 이름을 지을 수 있겠네요.
Counter
+ increaseCount(k)
+ getRecentCount(k)개선된 디자인
사실 Counter 안에 k라는 슬롯을 찾는 것보다는, k라는 이름의 Counter가 있는 것이 더욱 직관적입니다. Counter의 메서드에 k를 명시하기 보다는, k에 해당하는 Counter 객체가 있는 것이 낫겠죠.
kCounter = CounterCollection.getCounter(k)
kCounter.increaseCount()
kCounter.getRecentCount()좀 더 단순하게 만들어볼까요? CounterCollection에 Count가 있는 것은 당연하고, Count를 increase하는 것은 당연하므로,
kCounter = CounterCollection.get(k)
kCounter.increase()
kCounter.getRecent()성능 고려하기
MemoCounter 든, Counter든 역할에 따라 두개의 operation으로 분리했습니다. 하지만, 내부적인 구현에 따르면, 각각이 server로 메시지를 따로 보내야 한다면, 그리고 거의 항상 두 operation이 같이 쓰인다면, 이러한 분리는 비효율적입니다. 한번에 물어볼 수 있는 방법이 필요합니다.
Counter
+increase(k)
+getRecent(k)
+increaseAndGetRecent(k)가능한 한 원래의 인터페이스가 직관적이므로 유지하는 것이 좋다고 생각하지만, 이를 유지하면서 성능을 개선할 수 있는 더 나은 방법이 있을지도 모르겠습니다.
인터페이스 최소화
거의 항상 단순한 인터페이스가 좋습니다. 필요없는 인터페이스는 사용자를 혼란스럽게 합니다. 예를 들어, increaseAndGet(k)만이 사용되고, 다른 메서드는 사용될 가능성이 별로 없다면,
Counter
+increaseAndGetRecent(k)만으로도 충분합니다.
결론
저는 메서드 이름부터 새로 짓기 시작했지만, 좀 더 일반적인 방법은,
- 문제 해결에 필요한 객체의 이름과 역할을 정합니다. 객체를 정할 때는 문제를 해결하기 위한 비유를 생각해보면 좀 더 쉽게 지을 수 있습니다. 문제는 얼마나 적절한 비유인가인데, 어떤 비유가 객체의 역할 즉, 문제의 해결방법에 얼마나 근접하느냐를 따져야할 것입니다. 객체의 이름은 항상 문제 도메인의 언어나, 비유의 이름으로 짓습니다.
- 객체 역할에 따라 메서드를 추가합니다. 하나의 동작에 하나의 메서드를 추가합니다. 메서드 이름 역시 항상 문제 도메인의 언어로 짓습니다.
- 이 후에도 얼마든지 성능 고려나 인터페이스 최소화를 할 수 있습니다.