공부방/프리코스

우테코 프리코스 3주차 후기 + 피드백 정리

EVO. 2023. 11. 14. 01:41

이번주차의 문제는 로또 미션이었다.(문제 및 코드는 아래에)

 

새롭게 도전한 요구사항

 

불변으로 관리하자

"불변으로 관리하자"라는 주제로 배운 점을 정리해보자면, 로또 번호와 같이 중요한 데이터는 어느 곳에서도 수정되지 않도록 관리해야 한다는 것이다. 이를 위해 로또 번호를 발급하거나 당첨 번호를 생성하는 순간, 이를 객체로 감싸야 한다. 이 객체를 통해 필요한 번호를 꺼내는 것이 중요하다.

 

 이때, 이 로또번호를 감싼 객체는 일급 컬렉션으로 볼 수 있다. 일급 컬렉션에서 번호를 꺼낼 때, 단순히 List로 반환하는 것은 의미가 없다. 그래서, 나는 List.copyOf()와 unModifableList() 중에서 선택해야 했다. 둘 다 읽기 모드이지만, 후자의 경우 일급컬렉션 내부에서 수정이 일어나면 읽기 모드에 가져온 리스트에도 수정이 반영된다. 그러나, 로또 번호가 수정될 가능성이 없다고 판단하여 List.copyOf()로 꺼낼 수 있게 하였다. 이를 깊은 복사라고 볼 수 있다.

 

 그러나, 이는 컬렉션을 복사하는 것으로, 내부 객체의 요소가 변하지 않음을 보장하지는 않는다. 따라서, 로또 번호와 같이 불변성이 필요한 객체들이 불변성을 유지하기 위해서는 추가적인 고민과 노력이 필요하다.

 

 구현이 끝나고 테스트를 실행했는 데 우테코에서 제공한 일부 테스트가 통과하지 않는 문제에 직면했다. 이 문제는 'List.of' 메서드가 수정할 수 없는 unmodifiable list를 반환하며, 이 리스트에 정렬 로직을 적용하려다 발생한 것이었다. 이때 처음으로 'List.of'가 수정 불가능한 객체를 반환한다는 사실을 알게 되었다. 해결책으로, ArrayList에 복사하여 정렬을 수행했다. 이 과정에서 'Arrays.asList'와 'List.of'의 차이점을 확실히 이해할 수 있었다.

 

해당 작업을 수행하기 위해 도움이 되었던 글

방어적 복사와 Unmodifiable Collection Tecoble

해시코드로 살펴보는 깊은복사 얕은복사 차이점 - Hudi

불변 컬렉션 정렬하는 여러가지 방법 - stackoverflow

List.of()의 반환

 

Enum 잘 써보기

 Enum을 사용할 때마다 이동욱 개발자의 ‘Java Enum활용기’ 글을 읽곤 한다. 처음 이 글을 접했을 때는 30%만 이해했지만, 이제는 90% 이상을 이해한다는 것이 뿌듯하다(?)

 

 Enum을 사용하면서 그 장점을 체감할 수 있었다. 첫째로, Enum을 통해 코드 내에서 이 두 로직이 관련되어 있다는 것을 명확하게 표현할 수 있다. 로또 당첨 1~5등은 상금과 출력되어야 하는 내용이 정해져 있는데, 이 둘을 분리할 필요가 없다는 생각이 들었다. 이 둘을 분리하게 되면 어떤 한쪽에서 수정사항이 발생했을 때 이 둘이 관련있는 것인지 코드를 이해하기 위해 추가적으로 다른 코드페이지를 열어 찾아봐야 하는 번거로움이 있다(시간적 소모, 피로증가). 그러나 Enum으로 관련 요소들을 모아두면, Enum 내에서만 수정하면 되고, 새로운 등수가 추가되어도 전체 클래스에 영향을 주지 않는다.

 

 EnumMap과 HashMap의 성능차이를 배우며 당첨된 개수를 관리할때 EnumMap을 적절히 활용했다. 어떤 로또 구입자가 당첨된 로또에 대해 1등 - 00개, 2등 - 00개 ... 이런식으로 정리해서 결과를 표시하고 1등부터 출력 혹은 5등부터 출력 등 정렬도 필요하고 여러가지로 활용하기 위해 Map이 필요했다. 이때 Enum으로된 등수를 EnumMap을 활용하면 좋은 성능을 낼 수 있다. 내부적으로 ordinal이 있기 때문에 기존 해시로 HashMap을 구현하는 것보다 확연한 차이가 존재한다. 

 

4주차 미션에서도 Enum이 많은 요구사항을 해결하였다.

 

해당 작업을 수행하기 위해 도움이 되었던 글

EnumMap vs HashMap - Hudi

EnumMap 활용 - 연로그

 

private는 테스트를 어떻게?

 이번 주차도 어설픈 TDD를 통해 작업을 진행했다. 그런데, private 메서드가 많아지면서 이를 사용하는 public 메서드만으로는 테스트 코드 작성이 어려워졌다. 이 문제를 해결하기 위해, 클래스를 분리하고 private 메서드를 public으로 변경하는 방법을 고려해보았다. 이 방법을 시도해보니, 테스트 코드 작성이 훨씬 수월해졌다.

 

 이 경험을 통해 중요한 교훈을 얻었다. "private 메서드가 많아 테스트 코드 작성이 어려운 경우, 클래스 분리가 필요할 수 있다. 클래스를 분리하면, 안정적으로 메서드를 public으로 변경할 수 있고, 테스트 코드 작성도 쉬워진다. 또한, 클래스와 메서드의 책임도 줄어든다." 이렇게 정리하고 공유하니, 같은 생각을 가진 다른 개발자들도 많다는 것을 알게 되었다. 이는 상당히 놀라웠다(?_?) 

 

테스트코드가 작성하기 어렵다면, 클래스 분리가 필요하다는 신호이다.

 

해당 작업을 수행하기 위해 도움이 되었던 글

언제 private, public을 써야 할까 - 향로

테스트하기 좋은 코드 - 향로

단위테스트와 TDD는 다르다(지금까지 내가 하고 있었던 것은 단위테스트일뿐, TDD가 아니다)

private 메서드 테스트 꼭 해야할까? 

 

3주차 피드백 (공통 피드백 및 다른 코드 리뷰들 보고 개선해야 할 점)

기능 요구사항 정리 방법

  1. 먼저 요구사항을 계속 읽어보며 이해한다 
  2. 요구사항 정리(작게 쪼개기) 
  3. 핵심기능 맨 윗줄에 한줄로 적기
    > 이는 구현을 하면서 항상 머리속에 떠올려야 하는 한 문구이다. 이를 기억하면서 진행하면 딴 길로 새지 않는다.
  4. 그 핵심기능과 관련된 기능부터 구현 시작. 동시에 docs정리 (미리 쓰는게 더 설계하는 데 효과적)

4주차때 이런식으로 해보니 매우 효과적이었다. 시간도 단축되고 구현할 때는 구현에만 집중할 수 있게 되었다.

 

리스트 형식으로 정리

  • 현재 객체의 상태를 보기 위한 로그 메시지 성격이 강하다면 toString()을 통해 구현한다. View에서 사용할 데이터라면 getter 메서드를 통해 데이터를 전달한다.
  • final 키워드를 사용해 값의 변경을 막는다. 최근에 등장하는 프로그래밍 언어들은 기본이 불변 값이다. 자바는 final 키워드를 활용해 값의 변경을 막을 수 있다
  • getter을 사용하는 대신 객체에 메시지를 보내자 : 이는 getter을 아예 쓰지말라는 소리가 아니다. 가져온 값을 가지고 또 다른 로직을 구현하면 먼저 이가 굳이 다른 클래스에서 수행해야하는 지 먼저 고민을 해보고 책임이 잘못된 것 같으면 메세지를 보내서 해당 로직을 수행하도록 하고 결과를 받자.
  • AppConfig로 주입 방식 결정. 프로그램의 시작인 Application에서 여러 클래스를 주입하고 그러는 것보다 AppConfig와 같이 설정 클래스 파일을 만들어 미리 주입해서 만들고 해당 클래스에서 꺼내서 쓰면 좋을 것 같다.
  • 확률계산, 돈계산 등 소수점이 들어갈때는 BigDecimal을 꼭 활용하자
  • 예외 출력도 OutView에서 보이도록 하자