티스토리 뷰

💡 넥스트 스탭의 TDD, 클린 코드 with Kotlin 8기 강의를 수강하며 정리한 내용입니다.

 

목차

    배경

    TDD, 클린 코드 미션을 진행하며 TDD와 클린 코드, 객체지향 설계에 대한 인상 깊은 많은 피드백을 받을 수 있었다. 이번에는 그 중 하나인 객체지향 설계 중 클래스 네이밍에 대한 얘기이다.

     

    클래스

    클래스가 객체를 인스턴스화한다. 클래스는 객체의 팩토리이다. (객체가 살아있는 생명체라면 클래스는 객체의 어머니)

     

    클래스 네이밍

    클래스의 이름은 무엇을 하는지에 대한게 아닌 무엇인지에 기반을 두고 지어야 한다. 즉, 올바른 클래스 이름을 짓기 위해선 클래스의 객체가 무엇을 캡슐화할 것인지 관찰하고 적절한 이름을 지어야한다.

     

    즉 이를 통해, 외부에서 객체에게 무엇을 하도록 메시지를 던지면 객체 스스로 무엇을 할지 결정해야한다. 객체가 무엇을 하는지를 바탕으로 클래스 이름을 지어버리면, 거기에 강하게 묶여 추가 책임을 가질수도, 스스로 판단하여 메시지를 주고받을 수 었다.

     

    -er, -or 로 끝나는 이름을 사용하지 마세요

    예를 들어 다음과 같은 클래스가 있다. dollars를 포맷딩하여 반환한다. 이 클래스의 이름은 무엇은 하는지에 초점이 맞춰저 있다. (잘못된 예시) 가장 많이 하는 실수가 이와 같이 클래스명에 -er, -or을 붙이는 것이다. 결국 기능에 제한적이게 된다.

    class CashFormatter(
        val dollars: Int
    ) {
        fun format(): String {
            return String.format("$.d", this.dollars)
        }
    }

     

    클래스 이름은 무엇을 하는지가 아니라, 무엇인지에 기반해야한다. 즉, 객체는 그의 역량으로 지어져야한다.

    class Cash(
        val dollars: Int
    ) {
        fun usd(): String {
            return String.format("$.d", this.dollars)
        }
    }

     

    만약 원화로도 출력해야한다면 다음과 같이 추가 메시지를 부여할 수 있다. 객체가 단순히 정보를 전달하는 용도가 아니라 스스로 결정을 내리고 행동할 수 있는 자립적인 엔티티가 되었기 떄문이다.

    class Cash(
        val dollars: Int
    ) {
        fun usd(): String {
            return String.format("$%d", this.dollars)
        }
    
        fun krw(): String {
            val won = this.dollars * EXCHANGE_RATE
            return String.format("%,d원", won)
        }
        
        companion object {
        	// 대략적인 환율
        	private const val EXCHANGE_RATE = 1_300
        }
    }

    객체는 객체의 외부 세계와 내부 세계를 이어주는 연결장치가 아니다. 객체 내부의 데이터를 다루기 위해 요청할 수 있는 절차의 집합 또한 아니다. 객체는 캡슐화된 데이터의 대표자여야한다.

     

    예시

    다음은 TDD 미션을 진행하며 직면했던 상황이다. 다음과 같이 로또 번호 목록을 생성하는 클래스가 있다. 이 클래스의 이름은 적절할까? 클래스의 이름 Generator는 객체가 무엇인지가 아니라 무엇을 하는지에 초점을 맞추고 있다. 즉, 기능에 강하게 묶여있어 추가 책임을 가질수도 없고 스스로 판단하여 메시지를 주고받을 수 없다.

     

    예를 들어 로또 번호 생성 규칙에 대한 요구사항이 발생한다면 어떨까? Generator는 단순히 로또 번호 생성 기능에 대한 책임을 갖고있다. 기능에 강하게 묶여있어 추가 규칙을 부여할 수 없다.

    class Generator {
        fun generate(lottoCount: Int): Lottos =
            Lottos(List(lottoCount) { Lotto() })
    }

     

    무엇을 하는지가 아니라 무엇인지에 초점을 맞춘다면 클래스 이름을 LottoMachine으로 지을 수 있다. 이 클래스는 자신의 상태를 스스로 관리하는 자율적인 존재로 볼 수 있다. 

    class LottoMachine {
        fun generate(lottoCount: Int): Lottos =
            Lottos(List(lottoCount) { Lotto() })
    }

     

    만약 로또 번호 생성에 대한 규칙이 추가된다면 다음과 같이 규칙을 정의하고, 규칙을 기반으로 번호를 생성할 수 있다.

    class LottoMachine (
    	val rule: Rule
    ){
        fun generate(lottoCount: Int): Lottos =
            Lottos(List(lottoCount) { Lotto(rule()) })
    }
    
    // 로또 생성 규칙을 부여하여 로또 생성
    LottoMachine(rule).generate(lottCount)

    결론

    물론 실무에서 현업을 진행하다보면 기능에 기반하려 클래스 이름을 지을 수 밖에 없는 상황이 발생하기도한다. 중요한건 절대 -er, -or로 끝나는 이름을 짓지 말아라가 아니라, 가능한 객체가 무엇을 하는지(객체의 기능) 보다는 객체가 무엇인가(객체가 무슨 데이터들을 캡슐화하고 있는가)에 기반하여 클래스를 설계하는 것이다.


    출처

    Comments