[TDD, Clean Code with JAVA] 로또 미션

Dayeon myeong·2021년 5월 4일
1

로또

요구사항 정의

  • 구입 금액 입력
  • 구입 금액에 따라 1장에 1000원씩 계산하여 로또 발급하기
  • 로또 한장에는 6개의 숫자가 담겨있다
  • 수동으로 구매한 로또 수 입력
  • 수동 로또수에 따라 하나의 로또당 6개의 번호를 입력해야함
  • 수동 로또를 제외하여 나머지는 자동 로또 발급
  • 지난 주 당첨 번호와 보너스볼을 입력
  • 당첨번호와 보너스볼과 일치하는 개수에 따라 통계를 내기
  • 통계에는 3개 일치, 4개 일치, 5개 일치, 5개 일치 + 보너스볼 , 6개 일치하는 로또 개수를 보여주고, 수익률을 계산한다.(기준 1)

문자열은 상수로 관리하기

가독성 및 관리에 용이하도록 문자열은 상수로 관리한다. 이때 순서는 상수 -> 인스턴스 변수 순서

	private static final INVALID_NUMBER = "...";
	private final int number;

패키지 분리하기 : MVC 패턴

이번 미션에서는 MVC 패턴을 사용하여 패키지를 구분했다.
입출력을 담당하는 부분(view), 비즈니스로직 담당과 도메인 부분(model), 모델과 뷰를 연결하는 부분(controller)으로 나누었다.

각 패키지들의 역할이 분리될 수 있고
단일 책임의 원칙도 지키게 된다.
그리고 domain 영역에 대한 부분만 단위 테스트가 가능하기 때문에 사용하게 되었다.

도메인의 역할은 도메인에서만.

view에서는 단지 입출력만 담당할 뿐 도메인의 역할을 다뤄서는 안된다.

public Lotto inputWinningLottoNumbers() {

        System.out.println("지난 주 당첨 번호를 입력해 주세요");
        String winngLottoNumbers = new Scanner(System.in).nextLine();
        ...
}
        

처음 inputview를 만들 때 위와 같이 Lotto 객체를 반환하도록 했다.

그런데 Lotto라는 도메인의 생성방식이 바뀌면 View에서도 변경이 발생하게 된다. 그렇기 때문에 View에서는 도메인 클래스를 만들도록 하면 안되고,
도메인의 역할은 도메인의 영역에서만 행해져야 한다.

public String inputWinningLottoNumbers() {

        System.out.println("지난 주 당첨 번호를 입력해 주세요");
        return new Scanner(System.in).nextLine();
        
}

toString과 getter

처음에는 toString을 활용해서 view에서 값이 그려지도록 했다. 하지만 view에서 표현하는 요구사항이 변한다면 도메인영역에서도 변경이 발생한다.

그렇기 때문에 toString은 클래스를 단순히 문자열로 표현하기 위한 수단일 뿐, view 로직이 담겨서는 안된다..!

그런데 getter를 지양하라는 것을 강의 시간에 들어서 어떻게 값을 view에서 표현해야 하는 걸까 많은 고민이 되었다. getter를 사용하지 않는다면 어떻게 view에 값을 전달하지..???

리뷰어님에게 물어보니 getter를 지양하라는 뜻은 "getter는 다른 계층에 데이터를 전달할 때를 제외하고는 사용하지 않는 것이 좋다"라는 뜻이라고 한다. 그렇기 때문에 view에서 데이터를 표현할 때는 getter 사용이 괜찮다.

getter를 지양하라

객체 지향 언어인 java 프로그램에서 getter, setter를 자주 사용하게 된다면 객체 지향적인 코드가 되지 못한다. 즉, 객체가 일을 하도록 하는 코드가 되지 못한다.

그렇기 때문에 getter, setter를 사용하지 않고 객체에 메시지를 보내 객체 스스로가 로직을 구현하도록 한다.

static 메서드

처음에 도메인 계층에서 예외 처리 테스트를 할 때 static 메서드를 사용했었다.

그런데 만약 이 메서드가 사용되지 않는다면 static은 오히려 메모리 관점에서 좋지 않다고 생각했다.(static은 모든 객체가 공유해서 사용이 가능하고 객체 생성없이 바로 사용가능하다는 장점이 있지만, 프로그램이 끝날때까지 메모리에 할당되어 있다는 단점이 있다.) static은 언제 사용해야 하는 것일까?

그 기준은 해당 static 메서드가 여러 곳에 공유되어 있는지, 그리고 static 메서드 로직에 인스턴스 변수를 사용하지 않는지라고 생각된다.

로또 발급

로또를 발급하는 방식에는 수동 발급 방식 과 자동 발급 방식이 있다.

처음 설계에서는 DefaultGenerator를 만들어 여기서 수동발급과 자동발급을 통합해서 생성하도록했다.

그런데 리뷰어님이 만약 요구사항이 '수동' 혹은 '자동'만 가능하게 될 경우 DefaultGenerator를 수정해야하는 것이 아니냐고 피드백을 주셨다. LottoGenerator 인터페이스를 통해 자동 생성, 수동생성 추가적으로 이 수동/자동을 합성할 Generator를 만들어보면 좋겠다고 의견을 주셨다.

또한, generator 계층 외에서 로또 발급 Generator 타입을 인자로 사용할 때에는AutoGenerator, ManualGenerator 타입을 직접적으로 전달받지 않고 인터페이스인 LottoGenerator 타입만이 사용되어야 한다고 의견을 주셨다.
(인터페이스의 구현체가 인자로 사용되어서는 안된다.)

이유는 LottoGenerator의 인터페이스 구현 클래스가 바뀌어도 다른 계층에는 영향을 주지 않기 때문..!

만약 인터페이스의 구현체로 인자를 받게 되어 좀 더 구체적인 타입에 의존하게 되면 "DIP 원칙"을 지키지 못한다고 한다.

DIP(Dependency Inversion Principle) : 의존관계 역전 원칙
변하기 쉬운 구체적인 하위 클래스에 의존하던 것을 추상화된 인터페이스, 클래스나 상위 클래스를 두어 변화에 영향을 받지 않도록 하는 것.

구체적인 하위 클래스에 의존할 때에는 이 하위 클래스가 변경된다면 해당 클래스를 사용하는 다른 계층에도 영향이 간다.

즉, 변경을 최소화하기 위해 추상화된 클래스나 인터페이스, 상위 클래스에 의존하도록 해야한다.

위 이유에 따라서 LottoGenerator 인터페이스로만 접근이 가능하게 하였고,
ManualLottoGenerator, AutoGenerator, 그리고 이 다른 generator를 합성할 MergedGenerator를 구현 클래스를 만들었다.

TDD로 구현해 단위 테스트가 존재해야 한다. (UI로직은 제외)

  • 테스트 코드란?
    프로덕션 코드가 정상적으로 동작하는지를 확인하는 코드
    (프로덕션 코드란 프로그램 구현을 담당하는 부분으로 사용자가 실제로 사용하는 소스코드를 의미)

  • TDD란?
    TFD(Test First Development) + 리팩토링

  • TDD 사이클

  1. 실패하는 테스트를 구현한다
  2. 테스트가 성공하도록 프로덕션 코드를 구현한다.
  3. 프로덕션 코드와 테스트 코드를 리팩토링한다.
  • TDD를 하는 이유
  1. 디버깅 시간을 줄여준다.
  2. 동작하는 문서 역할을 한다.
  3. 변화에 대한 두려움을 줄여준다.

이번 과제에서는 TDD 사이클 과정을 쓰려 노력했지만 실제로는 프로덕션 코드를 먼저 신경쓰게 되었다. 앞으로 자주 습관을 들여야 할 듯..

그 외 프로그램 요구사항

  • 자료구조형 대신 복수형으로 변수명 표현하기 : 이후 자료구조가 바뀐다면 변수명도 바뀌어야 하기 때문에
  • 예외처리
  • indent(들여쓰기) depth를 2를 넘지 않도록 구현한다.
  • else 예약어 : 길어지는 코드
  • 규칙3 모든 원시값과 문자열을 포장한다
  • 규칙 8 : 일급 콜렉션을 쓴다

참고

https://www.slipp.net/questions/559

profile
부족함을 당당히 마주하는 용기

0개의 댓글