들어가기 전에...
이 글을 쓰는 지금은 우아한 테크캠프 pro의 마지막 주차가 진행중인 시점이다.
사실 매주 작성해야 했지만, 미션을 수행하고 따라가기에도 급급했다. (사실 게을렀던 거 쪼끔 인정한다.)
우아한 테크캠프 pro -! 이름하여 우.테.캠pro 는 총 9개의 미션으로 이루어져 있는데, 내가 참가한 현재 2기는
2021-05-17 ~ 2021-07-16으로 약 9주간 진행된다. 1주당 1개의 미션을 끝내야 수료가 가능한 건데, 회사 업무를 병행하면서 참여하기에는 버거운게 사실이었다. 그래도 이제 마지막주에 들어선 필자는 대부분의 미션을 마무리하고 약 2개의 step만을 남겨두고 있다. (1개의 미션에 보통 4개 정도의 step이 존재)
여기까지 각설하고, 1주차의 회고를 진행해보고자 한다.
우테캠pro 합격
사실 이번에 우테캠 pro를 합격해서 수료할 기회를 얻었지만, 나는 우테캠 pro 재수생(?) 이다.
우테캠 pro 1기 (2020-11-30 ~ 2021-01-29) 에도 지원을 했었지만, 광탈을 했었더랬다.
그래서 이번엔 꼭 합격해야지 하는 마음으로 지원서 작성에 공을 들였다 +_+! (지난번 떨어졌을 때 떨어진 사유를 포비님께서 지원서가 다른 지원자들보다 조금 부족했던 거 같다고 하셔서 이 부분을 중점으로 두었다 -!)
프리코스라고 해서 2가지 미션을 진행하기는 했으나, 기존에 넥스트 스텝에서 TDD 수업을 들었었기에 무리 없이 진행할 수 있었다. 그리고...
강의를 들을 수 있는 자격이 된 것 뿐인데도, 그날은 기분이 무척 좋았다.
1주차 미션 - 로또
강의가 시작되었고, 1주차 미션은 TDD 수업때 이미 해봤던 로또 게임을 구현하는 것이었다.
한번 해봤던 미션이라 익숙하긴 했으나, 설계는 다듬을수록 좋아진다고 생각하기에 더 나은 설계를 하고자 열심히 고민을 해보았다. 확실히 처음 TDD 수업때에는 로또 게임에 대한 도메인 지식 자체가 부족했던 터라, 테스트 코드 먼저 작성하고 구현을 진행해 나감에 있어서 최종 꼭대기가 어디인지 모른채로 이리저리 쌓아올리다보니, 언제 넘어질지 모르는(?) 피사의 사탑을 짓고 있는 듯한 느낌이었다.
하지만, 그 이후로 몇번 더 새롭게 구현해 본 적도 있고, 도메인 지식이 확실해진 지금은 머리속에 이미 큰 그림이 있는 상태에서 테스트 코드를 짜고 그에 맞게 구현을 진행하니 훨씬 수월하게 완성을 향해갈 수 있었다.
그래서 배운점은...?
우테캠 pro 1주차를 지나면서 확실히 느낀점과 배운점들을 정리해 볼 수 있었다.
1. 강의를 통해서 말씀해주신 내용에 '도메인 지식이 부족하면 일단 한번 구현을 끝까지 해보고 다 지운 다음에 TDD로 구현을 해봐라' 라는 말이 있었다. 이 멘트가 그간 TDD로 개발하면서 목표를 잃은 듯한 느낌을 받던 부분을 말끔히 해소해주었다.
2. 프리코스 미션 진행시에 stream을 쓰지 말고 for문으로만 처리를 해보라는 요구사항이 있었는데, 로또에서도 stream을 쓰지 않고 for문으로만 구현을 해보려고 했다. 그러자, 2depth로 늘어나버리는 로직들이 속속 생겨났는데 특히 enum에서 values를 통해 특정 값을 찾아서 가지고 오는 부분은 stream 없이 2depth를 만들지 않고 구현하기가 어려운 부분이었다. 이 부분을 구현하는 두가지 방법을 나름대로 찾아보았는데,
우선 첫번째 방법으로 다음과 같이(valueOf 메소드 부분) list를 만들어서 add 시킨후 enum 값으로 정렬한 뒤 가장 첫번째 값을 뽑는 것이었다.
public enum RESULT_RANK {
RANK_NONE(0, 0),
RANK_4TH(3, 5_000),
RANK_3RD(4, 50_000),
RANK_2ND(5, 1_500_000),
RANK_1ST(6, 2_000_000_000);
private int matchCount = 0;
private int prizeMoney = 0;
private RESULT_RANK(int matchCount, int prizeMoney) {
this.matchCount = matchCount;
this.prizeMoney = prizeMoney;
}
public static RESULT_RANK valueOf(int matchCount) {
List<RESULT_RANK> results = new ArrayList<>();
for (RESULT_RANK rank : values()) {
results.add(rank.findRank(matchCount));
}
Collections.sort(results);
return results.get(0);
}
private RESULT_RANK findRank(int matchCount) {
if (this.matchCount == matchCount) {
return this;
}
return RANK_NONE;
}
나름 깔끔하게 구현한 방법이라고 생각했지만, 다시 볼 수록 허점이 많은 코드다.
우선적으로 sort를 통해 값을 가져오게 되는데, sort는 enum 에 선언된 상수들의 순서에 영향을 많이 받는다. (순서가 바뀌면 로직의 결과도 바뀔 가능성이 있음) 나중에 이 코드를 보는 사람이 sort의 기준이 뭔지 한번에 알아차릴 수 있을까? 하는 의문이 생긴다. 더불어 list에 값을 저장해두었다가 하나를 뽑아오는 방식은 애초에 하나만 선택해서 가져오는 방식에 비해 불필요한 로직이 발생하고, 비효율적으로 보인다.
그래서 다른 방법을 생각해 보았다.
public enum RESULT_RANK {
RANK_1ST(6, 2_000_000_000, matchCount -> matchCount == 6),
RANK_2ND(5, 1_500_000, matchCount -> matchCount == 5),
RANK_3RD(4, 50_000, matchCount -> matchCount == 4),
RANK_4TH(3, 5_000, matchCount -> matchCount == 3),
RANK_NONE(0, 0, matchCount -> matchCount < 3);
private final int matchCount;
private final int prizeMoney;
private final Predicate<Integer> isMatching;
private RESULT_RANK(int matchCount, int prizeMoney, Predicate<Integer> isMatching) {
this.matchCount = matchCount;
this.prizeMoney = prizeMoney;
this.isMatching = isMatching;
}
public static RESULT_RANK valueOf(int matchCount) {
RESULT_RANK result = RANK_NONE;
for (RESULT_RANK rank : values()) {
result = rank.find(matchCount, result);
}
return result;
}
private RESULT_RANK find(int matchCount, RESULT_RANK result) {
if(this.isMatching.test(matchCount)) {
result = this;
}
return result;
}
}
크게 달라진 것은 없다. 단지, 기본은 RANK_NONE으로 시작하되, 값을 찾으면 그 이후부터는 그 값으로 계속 할당해 줄테니 결과적으로 나오는 값은 찾아진 값이 return 되도록 처리한 것이 포인트다.
List사용과 같은 불필요한 부분이 제외되어서 훨씬 직관적으로 로직을 파악할 수 있고, sort등과 같은 상수 기입 순서에 의존적인 로직이 없어서 더 나은 코드로 보인다.
이러한 방법을 고민하다보니, 조금 더 깔끔한 코드를 짤 수 있게 된 거 같아 즐거웠다.
3. 리뷰받은 내용 중 자동 로또 번호 생성에 대한 요구사항은 변경될 가능성이 높은 반면에 수동 로또 번호 생성에 대한 요구사항은 변경 가능성이 낮다 라는 피드백이 있었다. 곰곰이 생각해보니 맞는 말이다. 자동 로또 번호 생성은 수동으로 2개까지 뽑고 나머지는 랜덤으로 뽑기 라든지 여러가지 변경될 가능성이 있다. 반면에 수동 로또 번호 생성은 그냥 유져가 선택을 하는 방법 외에는 달리 바뀔 가능성이 존재하지 않는다. 그렇다면, 로또번호 생성시 자동 번호 생성에 대해서만 별도의 객체를 만들면 되지 않을까? 하는게 리뷰의 요점이었다.
기존에 내가 작성한 코드는 Auto / Manual 로 클래스를 나누어서 상황에 맞게 해당 객체들을 주입받아 사용하게끔 하였는데, Manual 객체는 의미가 없었다는 것이다. 또한, Auto 클래스도 별도로 있는게 과연 필요한 것일까? 하는 부분에 있어서도 로또 자체가 번호를 생성할 책임이 있다고 보면, Auto 클래스도 불필요한 부분이 맞다. 는 점을 배울 수 있었다.
정리
1주차를 진행하면서 도메인 지식이 부족할 때의 TDD는?
Stream을 사용하지 않을때의 구현은?
설계에 대한 부분은 어떤식으로 고려해야 할까?
등의 대한 대답을 고민해볼 수 있는 유익한 시간이었다.
'우아한테크캠프pro' 카테고리의 다른 글
우아한 테크캠프 pro 회고 - 4주차 (1/2) (0) | 2021.07.23 |
---|---|
우아한 테크캠프 pro 회고 - 3주차 (0) | 2021.07.20 |
우아한 테크캠프 pro 회고 - 2주차 (0) | 2021.07.14 |