과제의 늪에 빠지며 점점 힘들어지는 중,, 이지만 오늘도 배울 점들을 찾아보자!
10/29
학습 목표 & 피드백
이번주차의 주요 학습 목표는 다음과 같다
- 관련 함수를 묶어 클래스를 만든다. 객체들의 협력을 배울 수 있는 기회
- 함수나 클래스에 대해서 단위테스트에 대한 정확한 정보를 익힌다.
이번 주차도 공통 피드백이 왔다. 새겨들어야할 부분을 따로 뽑아봤다.
- README(기능명세서)는 상세히 작성하자!
사실 상세히 한다고 했지만 다른 참여자 분들에 비하면 심히 부족함을 느낀다. 3주차부턴 좀 더 열심히 작성해보자! - 변수 명에 자료형은 넣지 말자!
이번에 car 이름을 넣거나 car 객체들을 넣는 리스트를 만들면서 많이 놓친 부분 같다. 자료형 말고도 더 명확하게 이름을 작성할 수 있도록 검토해야겠다. - 처음부터 큰 단위의 테스트는 금물
기능 요구사항
전체적으로 로또 발행과 당첨 번호 입력, 발행로또와 당첨 번호와의 비교 후 수익률 계산의 과정으로 이루어지는 프로그램이다.
도메인 잘 이해했니?
- 로또 발행은 랜덤으로 이루어진다.
- 사용자의 결제 금액에 따라서 로또 발행 횟수가 결정된다.
- 번호 일치에 대한 로직이 필요하다. 검증하는 객체 생성이 필요할 듯
- 수익률 계산에 대한 로직 필요해보인다. 마찬가지로 객체가 필요할 듯
어떤 예외가 있을 거 같아?
- 예상되는 예외 사항은 금액 입력 부분, 당첨 번호 입력부에서 발생할 것으로 예상된다.
- 로또 구입 금액은 1000원 단위로 떨어지지 않으면 예외를 던져야한다.
- 당첨 번호 입력 시 6개의 번호를 입력 받아야한다.
- 당첨 번호 입력 시 1에서 45 사이의 숫자를 입력 받아야한다.
- 당첨 번호 입력 시 숫자 이외의 다른 문자나 특수문자가 오지 않아야 한다.
- 보너스 번호는 길이 1의 숫자여야 한다.
- 당첨 번호에는 중복이 있으면 안 된다.
왜 로또야?
객체 분리를 하거나 예외 사항에 대한 학습을 잘 할 수 있는 주제여서 그러지 않을까 싶었다.
사용자의 입력이 이전 과제보다 더 복잡하고, 하는 기능도 나누기 편해보였다. 또한 입력이 많다보니 예외사항에 대한 대비를 연습하고 공부하기 쉬울 거 같다는 생각을 했다.
프로그래밍 요구사항

새로운 프로그래밍 요구사항에서는 이 두가지 요구사항이 생각해볼 부분이었다.
- “
else를 쓰지 말라고 하니” 이 부분을 보고 엄청 웃었다. 내내 딱딱한 문어체를 쓰다가 어이없고 화난 말투가 보이는 요구사항이어서 말이다. 이번 주차도early return을 이용해서depth를 줄여보려고 한다. - Java Enum
사실 아직은 어디에 Enum을 써야할 지 잘 모르겠다. Enum을 한 번 정리해보려고 한다
Enum?
enum이란 상수다. 근데 클래스에 쓰이는 상수들을 따로 모아서 클래스 형식으로 정리한 것이다.
주로 쓰이게 될 기능은 스태틱 메서드인 values 와 valueOf가 될 것 같다. values는 enum 클래스에 있는 요소들을 선언 순서대로 담아서 반환해준다. valueOf는 뒤에 오는 인자에 따라서 enum 요소 중 알맞는 요소가 반환된다.
enum Week2 {
SUN("일"), MON("월"), TUE("화"), WED("수"), THU("목"), FRI("금"), SAT("토");
final private String day;
Week2(String day) {
this.day = day;
}
String getDay() {
return day;
}
}
출처: https://kadosholy.tistory.com/114 [KADOSHoly:티스토리]
심지어 이렇게 enum 요소 마다 또 다른 필드값을 넣어줄 수도 있다.
그럼 이거 왜 쓸까?

우아한 기술블로그에 따르면 다음과 같은 장점이 있다고 한다.
- 클래스로 선언되기에 IDE가 잘 찾아준다. 오타로부터 자유로움
- 허용 가능한 값들을 제한. 틀을 정해주니 안정성이 좋다.
- 리팩토링 시 변경 범위가 최소화된다. enum 코드만 수정해도 되니 좋다.
10/31
기능 명세서를 먼저 짜봐야겠다!
기능 명세서
기능 명세서는 다음과 같다.

점점 기능 명세서 쓰는 게 재밌어진다. 아직 부족하지만 엄청 꼼꼼한 성격이 되어가는 느낌.
입력부에 대한 설명이 길어졌다. 예외 처리할 사항이 많이 몰려있기 때문 내일 1번을 구현할 예정이다.
11/1
오늘은 입력부와 로또 발급까지의 구현을 해보았다.
이번 입력부를 구현할 때는 지난 번 1,2 주차와는 조금 다른 점이 생겼는데 바로 inputhandler가 없어졌다는 것이다. 첫주차에서의 inputhandler는 model에 있었는데 이걸 2주차에도 적용하려다 보니 그렇게 엄청난 로직도 아닌데, 그렇다고 view에 빼도 되는 건가라는 생각이 많이 들어서 controller에 넣었었다.
다시 생각해보니 그렇게 복잡한 기능이 아니라면 view에서 처리하고 간단한 예외는 거기서 처리하는 것도 괜찮다는 생각이 들어서 view에 몰아 넣게 되었다.
public class ConsoleView {
public Integer getPurchaseLottoAmount(){
System.out.println("구입금액을 입력해 주세요.");
return Integer.parseInt(Console.readLine());
}
public List<Integer> getWinningNumbers() { // 숫자가 아닌 값이면 예외
System.out.println("당첨 번호를 입력해 주세요.");
String userInputWinningNumbers = Console.readLine();
return Arrays.stream(userInputWinningNumbers.split(",")).map(Integer::parseInt).toList();
}
public Integer getBonusNumber() { // 한글자의 숫자가 아니면 예외
System.out.println("보너스 번호를 입력해 주세요.");
return Integer.parseInt(Console.readLine());
}
}
view의 코드이다. 아마 int로 바꾸는 과정이나 split하는 과정에서 사용자의 예외가 있을 것으로 예상된다. 처리하는 코드도 넣어줘야겠다. 오류 처리에 대한 클래스를 하나 더 만들까 생각이다.
🤨🤨🤨
로또 발급 부분을 구현할 때는 Lotto 객체를 어디서 사용할 지를 고민했다. 그러니까 로또 ‘번호’를 발급하는 것을 lotto의 일이 아니라고 생각했고, issuer라는 객체를 따로 만들기로 했었다. 해서 controller에서는 lotto를 직접 부르거나 하기보다 issuer에서 lotto를 불러와 만들고 만들어진 list만 controller에게 넘기는 구조가 깔끔하다고 생각한 것 같다.
또 issuer를 만들 때 문득 내가 프리코스 시작할 때 코드 한 줄 한 줄 뜯어보기로 다짐한 기억이 났다. 실제로는 바쁘다는 핑계로 제대로 안 하고 있었던 것인데, 이번에 Integer와 int를 쓸 때를 구분해 보기로 했다. 막막하다고 생각하고 있었는데 간단했다. 둘은 정수를 다루는 클래스와 자료형이고, Integer는 클래스 안에 있는 int를 가공하거나 계산하기 쉬운 메서드를 가지고 있다고 생각했다. 해서 굳이 이 정수형의 자료형을 가공할 필요가 없다? 그러면 굳이 무겁게 클래스 생성을 할 필욘 없는 것이다.
또 고민을 했던 것에 대해 말해보자면, 나는 메서드에도 급이 있다고 생각을 한다. 직접적으로 객체끼리 혹은 계층끼리 데이터를 주고 받는 public 메서드는 무조건 보기 쉬워야한다고 생각한다. 코드를 읽을 때 명시적인 이름으로 되어있어야 한다. 즉 추상화 수준이 높아야 하는 것이다. 가장 좋은 예시로는 controller의 run 메서드에는 낮은 수준의 코드가 있으면 안된다. 코드 읽는 사람에게 생각할 분기점이나 과정을 주지 않게 줄글 읽듯이 코드를 짜는 것이 좋다.
문제 발생
inputhandler 를 구현할 때 controller로 안 빼는 것이 목표였는데, 출력을 하려고 봤는데 List<Lotto>를 받아서 출력을 해야하는데 안되는 것이다. 아무래도 issuer에 랜덤 번호 생성과 lotto 발급 메서드를 따로 두어야할 것 같다. lotto에서 숫자를 접근하는 것보다는 발급된 숫자를 보여주고 그 숫자로 lotto를 발급하는 것이 맞는 것 같다.
11/3
왜인지는 모르겠지만 controller로의 의존성 주입을 줄일 수 있으면 줄이고 싶었다. 확실히 mvc 패턴을 쓰면서 아직 제대로 된 이해가 부족하다고 느끼게 된 거 같다. 다음 주차 들어가기 전에 controller 와 model 그리고 view 의 역할을 확실하게 알고, 데이터 전송이 어떤 식으로 이루어지는 것이 좋은지를 봐야겠다.
본격적으로 구현에 들어갔는데, 내가 생각한 model에는 Lotto, 로또 발급기, 통계 계산기, 로또 결과 enum 정도였다.
로또 결과를 나타내는 enum 구현과 통계 객체를 만들 때 생각을 많이 하고 시간을 쓴 것 같다.
enum
enum은 클래스이지만 비슷한 기능을 하는 상수들을 정해놓을 수 있게 만들어진 클래스이다. enum에서 나열된 상수마다 정해진 필드 값을 설정할 수 있다는 사실을 알고 출력이나 통계를 계산할 때 사용하면 좋을 필드들을 정했다.
public enum LottoResult {
FIFTH("3개 일치", 5000, 3), FOURTH("4개 일치",50000, 4),
THIRD("5개 일치",1500000, 5), SECOND("5개 일치, 보너스 볼 일치", 30000000, 5),
FIRST("6개 일치",2000000000, 6), NONE("3개 미만 일치", 0, 0);
}
이렇게 하나의 나열된 것들이 객체의 후보들이고 정해져 있는 상수같은 경우에는 이렇게 객체로 관리하면 편리하다는 것을 깨달았다. 결과를 출력할 때에도 결과 메세지도 가지고 있으면 좋을 것 같아서 필드에 넣어주었다.
통계 기능
통계 객체가 하는 일은 로또를 받아서 당첨 번호와 보너스 번호와의 비교를 통해 결과를 도출하는 것과 당첨 결과를 바탕으로 수익률을 계산하는 것이다.
🤨🤨🤨
통계에서 당첨, 보너스 번호를 가지고 있다가 결과 도출하는 것이 자연스럽다고 생각해서 그렇게 배치했다.
당첨 결과가 단지 금액을 다 더해서 나오는 것이 아니라 몇 등을 몇 개의 로또가 했는지도 관리해야했다. 아무래도 좋은 자료형은 map이라고 생각해서 map을 사용했다. 결과를 반환하는 LottoResult를 반환하는 메서드는 Lotto에 넣어줬다. 왜냐하면 통계 객체가 로또 결과에 대한 값을 접근해서 다루기 보다 로또가 관리하는 것이 낫지 않을까라는 생각이었다. 다시 생각해보니 lottoResult에서 처리해서 반환해주면 어땠을까라는 생각도 든다. 통계에서는 로또의 번호와 당첨 번호와의 비교가 주 책임이다. 근데 lotto에서 직접 number를 받아서 비교한다. 차라리 Lotto가 자신의 숫자들과 비교해서 주면 어땠을까?? 그러면 get메서드를 쓰지 않아도 됐을텐데 말이다.
아니면 당첨번호와 보너스 번호를 가진 winninglotto를 따로 만들어서 해당 객체에서 계산해주고 통계 객체와 중간 매개를 lotto로 하면 어땠을까 통계는 LottoResult만 받고 Lotto는 자신의 번호만 주고, winninglotto는 자신이 가진 당첨번호와 lotto가 준 번호를 비교해서 알맞은 lottoresult를 제공하는 식은 어땠을까. 그러면 책임을 분리하고 강제적으로 get해오는 일은 줄었을 것 같다.
제출 후에 쓰는 회고록이라 아쉬운 점이 더 많이 보인다.
11/4
프로젝트에서 대대적인 리팩토링에 들어간 날이다. 주요 리팩토링 내용은 validator였다. 예외가 자꾸 여러 객체에서 나오는 것이 깔끔하지 않아보였다. 적어도 input이 숫자인지 혹은 null 값은 아닌지에 대한 인증은 validator를 만들어서 예외를 보내주자라고 생각을 한 것 같다.
public class BonusNumberValidator implements InputValidator{
public void validate(String input){
validateNull(input);
validateNumeric(input);
}
private void validateNull(String input) {
if (input.isEmpty() | input == null){
throw new IllegalArgumentException("[ERROR] 보너스 번호를 입력하세요");
}
}
private void validateNumeric(String input) {
if (isNotNumeric(input)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호에는 숫자가 입력됩니다.");
}
}
private boolean isNotNumeric(String input) {
return !input.matches("\\d+");
}
}
입력에는 구매 금액, 당첨 번호, 보너스 번호가 있다. 그중 보너스 번호 입력에 대한 검증 클래스이다.
이와 같이 null이거나 숫자가 아닌 값이 들어왔을 때를 검증하고 컨트롤러에서 이를 받아서 제대로 된 입력이 나올 때 까지 반복하는 코드를 짰었다.
Integer bonusNumber = getBonusNumber();
private Integer getBonusNumber() {
while (true) {
String bonusNumbersInput = consoleView.getBonusNumber();
try {
bonusNumberValidator.validate(bonusNumbersInput);
return InputParser.parseIntNumber(bonusNumbersInput);
} catch (IllegalArgumentException e) {
consoleView.printErrorMessage(e.getMessage());
}
}
}
이렇게 해서 원래 view에서 기존 주차에서 하던 inputhandler 역할을 하고 있었는데 handler에 대한 역할을 inputparser와 validator로 나누었다. inputparser같은 것이 model도 아닌데 controller도 아니고 참 어렵다.
11/5
테스트 코드를 작성한 날이다.
테스트 코드를 자꾸 마지막에 작성하다보니 급하게 그리고 꼼꼼히 잘 못 짜는 것 같다는 생각이 들었다. 예외 상황을 충분히 잘 파악하고 테스트 코드에 대한 계획도 필요하다고 느꼈다. 그래도 기능별로 테스트 코드를 짜보면서 대강 어떤 식으로 테스트 코드를 짜야하는지 감을 잡았는데, 테스트 코드를 짤수록 내가 짠 객체들의 책임 분배가 필요하다는 것을 느꼈다.
해당 객체의 메서드에 대한 예외나 성공 여부를 묻는 단위 테스트로 이해를 하고 진행햇는데 코드를 짤 때마다 다른 객체의 선언이 필요하다거나, 주어져야하는 정보가 많아진다거나 하는 경우가 그런 경우였다.
또 한가지로 테스트 코드라고 해서 가독성을 개나줘버리는 것은 안 좋은다는 것을 알았다. 테스트 코드도 결국 사람이 보는 것 나중의 내가 왜 이 테스트 코드가 있고 어떻게 작동하는지를 아는 것은 정말 중요하기 때문이다.