[자바] 로또 게임 구현

2020. 5. 18. 16:46TIL/자바

스터디에서 진행하는 자바 콘솔 애플리케이션 개발의 세번째 작품인 로또 게임 구현은

지난 레이싱 게임 때보다 훨씬 많은 조건을 충족시키며 개발하였다.

덕분에 많은 고민을 하며 스트레스를 받았지만 그만큼 알게된 것과 얻은 것이 많았다.

전체 프로젝트를 진행하면서 새로 알게된 내용들을 정리하게 되었다. (로또게임)

 

 

1. Collection에서 제공하는 unmodifiable 메소드.

개발을 하다보면 어느 시점에서 더이상 데이터가 변경되지 않아야 하는 상황이 온다.

로또 프로그램을 만들면서 로또의 번호가 한번 정해지면 더이상 변경되지 않아야 한다던가,

게임 결과가 변경되지 않아야 한다는 것처럼 말이다.

위의 두 메소드는 한 로또에 속해있는 번호를 반환해주는 메소드와 당첨 결과를 담은 map을 반환해주는 메소드이다.

이때, 이 메소드를 통해 넘겨받은 list와 map이 변경될 수 있는 여지가 있다면 반환값이 의미가 없어질 수 있다.

(예를들어 1등에 당첨된 로또가 없는데 있도록 수정이 가능하다면 결과를 원하는대로 조작할 수 있게 된다.)

이러한 상황을 막기 위해서는 반환해주는 쪽에서 수정이 불가능한 컬렉션으로 만들어 넘겨주는 것이다.

이때 사용되는 것이 Collection에서 제공하는 unmodifiableList 또는 unmodifiableMap 등이다.

이와 같은 메소드로 컬렉션을 감싸서 반환해주면 해당 컬렉션을 수정하려는 행위가 있을때 에러를 발생시켜준다. 

 

실제로 unmodifiableMap을통해 반환받은 결과값에 새로운 값을 put했을때 발생하는 오류이다.

이렇게 함으로서 객체의 상태를 불변성을 갖도록 만들어 줄 수 있다.

(단, 원본 데이터의 수정을 막지는 못한다.) 

 

 

2. View에 접근하는 것은 Controller가 되어야한다.

Controller가 View와 Model에 데이터를 주고받으며 전체적인 흐름을 담당한다는 것은 MVC구조를 공부하며 알고 있었다.

하지만 Model에서 View에 직접 접근하는 것이 좋지 않은 개발이라는 것은 크게 생각하지 못하였다. 

단순한 출력 또는 특정 시점에서 필요한 입력등은 Model에서 직접 View에 접근하는 것이  쉽다고 생각하여 처음에는 그렇게 코드를 작성했었다. 하지만, MVC의 장점이 극대화 되기 위해서는 각 역할을 철저하게 분리되어야 한다는 것을 알았다.

즉, UI와 비즈니스 로직이 철저하게 분리되어야 한다는 것이다. 

간단하든 복잡하든 Model에서 View에 직접 접근하는 코드가 생기는 순간 결국 둘간에는 종속성이 생기게 된다.

이것은 약한 결합을 깨뜨리게 되고 추후에 수정할 일이 생겼을때 더 많은 코드를 손봐야 하는 상황이 발생할 수 있다.

이 문제에 대해 검색해 보니 많은 사람들의 의견이 갈린다고 한다.

우선 이 프로젝트만 두고 보았을때 model이 view에 직접 통신할 경우 많은 종속성이 발생하게 되므로, 코드가 우회하더라도 모든 입출력은 컨트롤러를 통해 하도록 해주었다. ( 이후에 스터디를 진행해주시는 선배분에게 여쭈어 보았는데, view가 여러개가 있을 경우에 대한 답변을 받았다. 즉, 웹 또는 앱 등에서 여러 가지 view가 생성되었을때 어떤 Model을 사용하려고 하는데 그것이 다른 view와 종속되어있다면 문제가 발생할 수 있다는 것이다. 따라서 view와 통신하는 것은 Controller에게 맡겨야 한다는 결론을 우선 내게 되었다. )

3. Custom Exception 

이번 로또 프로젝트를 하면서 미루고 미루었던 예외 처리에 대해서 공부하였다.

먼저 예외는 오류와는 다른 개념이다.

오류는 시스템 자체에 비정상적인 상황이 생겼을때 발생하는데, 이것은 개발자가 미리 예측하여 처리할 수 없기 때문에, 애플리케이션에서 오류에 대한 처리를 신경쓰지 않아도 된다. 반면에, 예외는 개발자가 구현한 로직에서 발생한다. 즉, 예외는 발생활 상황을 미리 예측하여 처리할 수 있다. 

 

이러한 예외는 Checked Exception과 Unchecked Exception로 나누어진다.

간단히 표로 만들어 정리해 보자면, 아래와 같다.

  Checked Exception Unchecked Exception
처리 반드시 처리해야함 처리를 강제하지 않음
확인시점 컴파일단계 실행 단계
대표 예외 IOException
SQLException
그 외 
Runtime Exception을
제외한 모든 예외
RuntimeException
하위에 있는 예외들
IllegalArgument
NullPointer
IndexOutOfBound
System

로또 프로젝트에서 만든 나만의 CustomException은 RuntimeException을 상속하여 만든 예외이다.

검증하는 단계에서 조건에 맞지 않는 상황이 발생할 경우 throw new를 통해 Custom Exception으로 예외를 던져주고,

Custom Exception은 출력하고자 하는 메세지를 super키워드를 통해 catch문으로 넘겨준다.

controller로 부터 받은 입력값이 여러 클래스를 거친다음 예외를 발생시켰기 때문에, 

거쳐온 클래스들이 모두 영향을 받을거라고 생각했지만 예외가 발생했을 경우 그 예외를 잡아주는 try -catch문이 있는 클래스로 바로 넘어가기 때문에 생각보다 복잡하지 않았다.

특히, 입력값에 대한 예외상황이 발생했을 경우 try-catch문이 컨트롤러에 위치해야한다는 점을 알기까지 많은 시행착오를 겪었다.

위와 같이 입력받은 문자열 numbers가 어딘가에서 검증을 통과하지 못해 예외를 발생할 경우 다시 catch문으로 돌아오게 되고

custom Exception에서 발생시킨 메세지를 출력시켜 준다.

(자세한 흐름은 전체 코드를 봐야만 이해하기 쉬울 것 같다 ...)

4. 객체 동등성을 통한 테스트

객체를 비교하는데에는 동일성과 동등성의 측면이 있다.

우선 객체는 주소값을 가지기 때문에 이 주소값이 척도가 되는데, 같은 주소값을 갖는지에 대한 비교가 동일성 비교이다. ==을 통해 비교연산을 할 수 있는데, 같은 주소를 가질 경우 true를 반환해준다.

하지만 테스트 코드를 작성할때 필요한 비교는 주소값 비교가 아니다.

어떠한 로직을 통해 갖게된 정보의 값이 같은지를 비교하는 것이 필요하다.

자바의 Object에 정의된 equals를 확인해보면 단순히 ==를 통해 비교하고 있기 때문에 객체자료형의 동등성 비교를 위해서는 이것을 Override하여 재정의 해주어야 하는데 , 인텔리제이와 같은 IDE에서는 이러한 것을 손쉽게 만들수 있도록 해준다.

 

위와 같이 equals()와 hashCode()를 오버라이드해서 사용하면 동등성비교를 통해 테스트 코드를 작성할 수 있게된다.

결과적으로 getter를 사용하지 않고도 동등성비교를 통해 테스트 코드를 작성할 수 있게 된다.

(getter의 사용이 바람직하지 않은 이유는 따로 정리해 두었다.)

'TIL > 자바' 카테고리의 다른 글

[자바] 사다리 게임 구현  (0) 2020.05.24
[자바] Java와 JVM  (0) 2020.05.06
[자바] getter메소드 사용을 지양하자  (0) 2020.04.25
[테스트] JUnit 과 AssertJ  (0) 2020.04.23
[자바] 레이싱 게임 구현  (0) 2020.04.22