우아한 테크코스에 마지막주차 후기이다.
길다면 길고 짧다면 짧았던 여정이 끝났다.
하면서 굉장히 재미있었고 알게 된 것과 느끼게 된 것들이 많아 뿌듯했다.
얻는 것들이 많았다 결과가 좋지 않더라도 지원을 후회하진 않는다.
이번과제에서 느낀 점들을 써보려고 한다.
공통 피드백
이번주차는 공통피드백이 없다.
그 대신 12월 11일까지 진행했으면 하는 활동에 대한 생각을 전하셨다.
프로코스 회고하면서 다음 단계 목표 설정해 보기
4주 동안 프리코스 미션을 구현하면서 집중하느라 그 주차 과제를 돌아볼 시간이 없었는데
지금이 시간이 4주를 돌아볼 수 있는 가장 좋은 시간이다. 지난 4주 동안 내가 무엇이 부족했고 다음단계로 집중해야 할 것이 무엇인지 찾아보는 시간을 가지는 걸 추천하셨다.
그렇기에 나는 먼저 1주 차 과제부터 4주 차 과제까지 모든 과제들을 훑어보면서 어떻게 성장해 왔는지 찾아보았다.
1. mvc 패턴
첫쨰주 과제인 야구게임부터 mvc패턴을 적용시켜서 과제를 만들었다.
왜 mvc패턴을 적용했는지는 큰 이유는 없고 내가 학원에서부터 자주 쓰던 디자인 패턴이었기에 별생각 없이 mvc패턴을 강요해서 과제를 진행했다.
사실 mvc패턴에 대해 제대로 알지도 못했고 이렇게 쓰는 거다라는 것만 알고 있는 상태에서 만들었다.
mvc패턴에 대해 제대로 공부하지 않고 만들었기 때문에 만드는 도중에도 이게 맞는 건가?라는 생각이 들었다.
그렇게 완성된 작품은 이도저도 아닌 과제가 만들어졌다. 다른 사람들 코드들을 보면서 내가 만든 건 mvc패턴이 아니구나 라는 생각을 하게 되었다. 그래서 다음과제를 하기 전에 mvc패턴에 대해 다시 공부하고 제대로 적용시키자 라는 생각을 가지고 다음과제를 준비했다.
before
public class BaseballController {
private int strikeCount;
private int ballCount;
Number number = new Number();
Random random = new Random();
OutputView outputView = new OutputView();
public void start() {
boolean state = true;
OutputView.startMessge();
while (state) {
setComputerNumbers();
repeatGame();
state = restartGame();
}
}
public void repeatGame() {
while (true) {
setPlayerNumbers();
compareNumber();
hint();
if (strikeCount == BASEBALL_STRIKE_COUNT) {
OutputView.endMessage();
return;
}
}
}
public void setPlayerNumbers() {
number.setPlayerNumber(InputView.inputNumber());
List<Integer> playerNumbers = new ArrayList<>();
for (int i = 0; i < number.getPlayerNumber().length(); i++) {
int num = Character.getNumericValue(number.getPlayerNumber().charAt(i));
playerNumbers.add(num);
}
number.setPlayerNumbers(playerNumbers);
}
public void setComputerNumbers() {
number.setComputerNumbers(random.createRandomNumbers());
}
public void compareNumber() {
List<Integer> computer = number.getComputerNumbers();
List<Integer> player = number.getPlayerNumbers();
strikeCount = ZERO;
ballCount = ZERO;
for (int i = 0; i < BASEBALL_LENGTH; i++) {
strikeCount(computer.get(i), player.get(i));
ballCount(computer, player.get(i), i);
}
}
public void strikeCount(int computer, int player) {
if (computer == player) {
strikeCount++;
}
}
public void ballCount(List<Integer> computer, int player, int index) {
if (computer.get(index) != player && computer.contains(player)) {
ballCount++;
}
}
public void hint() {
String message = "";
message += ballHint();
message += strikeHint();
message += noting();
outputView.baseballHint(message);
위에 코드는 야구게임의 Controller의 일부분이다.
Controller에서부터 잘못된 거를 볼 수가 있다. mvc패턴에 Controller에 정의는 Controller는 Model과 View 사이에서 데이터 흐름을 제어한다. 결과를 Model에 저장하여 View에게 전달하는 역할을 수행한다. 결국 Controller는 Model과 View의 역할을 분리하는 중요한 요소이다.
하지만 위에 코드에서는 controller에서 strikeCount와 ballCount의 결과를 저장하고 있다.
여기서부터 이미 mvc패턴에 정의에 벗어났다. Controler은 결과를 Model에 저장하여 VIew에 전달해야 하지만 위에코 드는
Controoler에서 Model에 저장하지 않고 바로 View로 보내는 코드이다.
after)
public void start() {
Cars cars = new Cars(InputView.inputName());
TryCount tryCount = new TryCount(InputView.inputCnt());
int cnt = Integer.parseInt(tryCount.getTryCount());
race(cars, cnt);
winners();
}
public void race(Cars cars, int tryCount) {
for (int i = 0; i < tryCount; i++) {
cars.move();
OutputView.carsStatus(cars.getCars());
}
}
public void winners() {
OutputView.winner(Winner.findWinners(Car.getCarMap()));
}
}
이 코드는 다음과제인 레이싱게임의 Controller의 코드전체이다. Controller의 정의에 맞게 View에서 받은 내용을 Model에 저장하고 Model의 저장된 결과를 다시 View로 보내 결과를 출력하는 기능이다.
Controller의 정의에맞 Model과 View에 데이터 흐름을 제어하고 분리하는 역할 즉 중간다리역할을 수행하고 있다.
이렇게 분리하니 각가 자신이 맡 역할만 수행하고 다른 곳으로 결과만 보내주면 돼 서로 간의 결합도를 낮출 수 있었다.
2. set
1주 차 과제를 진행하고 다른 사람들 코드를 살펴보던 중 set를 안 쓰는 코드를 발견하였다.
그래서 set을 안 쓰고 어떻게 Model에 데이터를 저장하지?라는 생각이 들었다. 왜냐하면 나는 학원에서 set을 사용해서 계속데이터를 저장했기 때문에 항상 set/get은 세트로 사용하곤 했다. 이 부분도 궁금했기에 찾아본 결과 이런 이유였다.
많은 블로그들이 모두 get/set을 지양하라 라는 제목의 글들이 많다.
그 글들을 읽어본 결과 이런 내용들이었다. 필드를 private으로 숨겨놓고 Getter와 Setter를 모두 public으로 열어서 사용하는 것은 정보 은닉의 효과를 볼 수 없다. 필드 자체는 잘 숨겨놓고 그 값을 Getter로 조회할 수 있고 Setter로 수정할 수 있으면 이 정보가 정말로 숨겨졌다고 할 수 있을까?
객체에 요청을 하지 않고 객체의 정보를 조회하여 직접 비즈니스 로직을 수행하거나 객체의 속성을 다짜고짜 바꾸라고 한다면 객체지향의 장점이 퇴색된다.
그렇기 때문에 Getter와 Setter을 지양하라는 것이다.
그렇다면 Getter와 Setter 없이 어떻게 데이터를 저장하고 가져오는 거지?
먼저 Setter부터 예를 들어보겠다.
public void setPlayerNumbers() {
number.setPlayerNumber(InputView.inputNumber());
List<Integer> playerNumbers = new ArrayList<>();
for (int i = 0; i < number.getPlayerNumber().length(); i++) {
int num = Character.getNumericValue(number.getPlayerNumber().charAt(i));
playerNumbers.add(num);
}
number.setPlayerNumbers(playerNumbers);
}
위코드는 야구게임에서 사용자가 입력한 숫자를 number라는 객체에 저장하고 다시 그 값을 가져와서 int 숫자로 변경 후
다시 List에 담아 number에 있는 List에 저장하는 코드이다.
이 메서드만 봐도 하나의 메서드에서 여러 가지 일을 하고 있고 무분별한 set와 get을 사용하여 객체지향의 장정을 무색하고 하고 있다.
부끄럽지만 나의 1주 차과제의 발췌이다.
아래는 이걸 리팩터링 해서 만든 코드이다.
//Controller
public List<Numer> setPlayerNumbers() {
Number numbers = new NUmber(InputView.inputName());
return numbers;
----------------------------------------------------------------------
//Model
private List<Number> numbers;
public Nuumber(String number) {
this.number =changeNumber(number);
}
private List<Number> changeNumber(String number){
List<Integer> playerNumbers = new ArrayList<>();
for (int i = 0; i < number.length(); i++) {
int num = Character.getNumericValue(number.charAt(i));
playerNumbers.add(num);
}
return playerNumbers;
}
생성자에 직접 사용자가 입력한 수를 넣어 set를 사용하지 않고 Model에 데이터를 저장할 수가 있다.
그리고 getter도 지양하기는 하지만 그렇다고 무조건 사용하지 말라는 말은 아니다.
당연히 getter를 무조건 사용하지 않고는 기능을 구현하기 힘들 것이다. 출력을 위한 값 등 순수 값 프로퍼티를 가져오기 위해서라면 어느 정도 getter는 허용된다.
대신 객체를 무분별하게 사용하거나 값을 가지고 로직을 처리하거나 하지 말고 그럴 때는 객체의 메시지를 보내서 사용하자
더욱 자세한 이야기는 밑에 링크에 나와있다. 이글이 무분별한 set/get사용에 도움을 많이 줬다.
https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/
getter를 사용하는 대신 객체에 메시지를 보내자
getter는 멤버변수의 값을 호출하는 메소드이고, setter는 멤버변수의 값을 변경시키는 메소드이다. 자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근제어자는 private
tecoble.techcourse.co.kr
3. Test코드
과제를 시작하기 위해 프로젝트를 보는 중 Test라는 걸 보았다. 처음에는 이게 무슨 클래스지?
라는 생각이 들었다. 그래서 찾아보던 중 TDD라는 걸 알게 되었다. 그래서 첫 주과제에 test를 적용시켰지만
많은 테스트를 할 수는 없었다. 과제를 만드는데 시간이 많이 잡아먹혓기떄문에 테스트코드를 많이 작성하지 못했다.
나중에 알고 보니 원래는 테스트를 먼저 만든 후에 기능을 구현하는 거라는 걸 알게 되었다.
@Test
void 길이_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> InputView.validationNumber("12"))
.isInstanceOf(IllegalArgumentException.class)
);
}
@Test
void 입력_예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> InputView.validationNumber("as1"))
.isInstanceOf(IllegalArgumentException.class)
);
}
@Test
void 중복_값_예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> InputView.validationNumber("112"))
.isInstanceOf(IllegalArgumentException.class)
);
}
위에코 들어가 1주 차과제인 야구게임 테스트코드이다. 이땐 test에 대해 아무것도 몰라
예시로 있던 코드에 입력값 예외테스트만 만들어서 테스트를 했다. 객체에 대한 테스트는 따로 만들지 않았다.
그렇게 test도 중요하다는 걸 알게 되었고 더욱 공부하게 되었다.
테스트코드에 중요한 점 중 하나인 내가 작성한 코드에 빠르게 피드백을 받는다라는 점을 생각하여
작은 단위부터 큰 단위까지 객체에 대해 하나하나 테스트를 구현했다.
그 결과 1주 차 때는 하나만 있던 테스트클래스가 여러 개로 많아졌고
입력테스트부터 객체생성 테스트 등등 여러 가지 테스트들을 만들어서 확인할 수가 있었다.
@DisplayName("잘못된 유형 날짜 입력 테스트")
@ParameterizedTest
@ValueSource(strings = {"0", "32", "77", "asd", "-3", "!@#"})
void testOrderDateType(String input) {
assertThatThrownBy(() -> new Date(input))
.isInstanceOf(IllegalArgumentException.class);
}
}
@DisplayName("총혜택 금액에 따른 이벤트 배지 테스트")
@ParameterizedTest
@CsvSource({
"5, 해산물파스타-1,초코케이크-1",
"24, 양송이수프-2,타파스-1,아이스크림-1,해산물파스타-1",
"31, 아이스크림-1",
"27, 타파스-1,제로콜라-3"
})
void eventBadgeTest(String orderDate, String orderItems) {
// given
Order order = new Order(orderItems);
Date date = new Date(orderDate);
Event event = new Event(order, date);
Discount discount = new Discount(event);
int benefitsTotal = discount.calculateTotalBenefits();
// when
EventBadge eventBadge = new EventBadge(benefitsTotal);
String badge = eventBadge.getEventBadge();
// then
assertThat(badge).isEqualTo(calculateBadge(benefitsTotal));
}
public static String calculateBadge(int totalBenefit) {
if (totalBenefit >= EVENT_BADGE_PRICE_SANTA) return SANTA;
if (totalBenefit >= EVENT_BADGE_PRICE_TREE) return TREE;
if (totalBenefit >= EVENT_BADGE_PRICE_STARS) return STARS;
return EMPTY;
}
}
@DisplayName("이벤트 기간내 할인 전 총주문 금액이 12만원 이상일 때 증정 테스트")
@ParameterizedTest
@ValueSource(strings = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"})
void giftMenuTest(String orderDate) {
// given
Order order = new Order("해산물파스타-2,레드와인-1,초코케이크-1");
Date date = new Date(orderDate);
Event event = new Event(order, date);
// when
GiftMenu giftMenu = event.giftMenu();
// then
assertThat(giftMenu).isNotNull();
}
@DisplayName("하나만 주문했을 때 메뉴의 가격이 총 주문 금액과 일치 테스트")
@Test
void testTotalPriceWithSingleMenu() {
// given
Order order = new Order("양송이수프-1");
// when
int totalPrice = order.calculateTotalPrice();
// then
assertThat(totalPrice).isEqualTo(Menu.APPETIZER_YANG_SONGI_SOUP.getPrice());
}
@DisplayName("여러 메뉴를 주문했을 때 총 주문 금액이 예상한 값과 일치 테스트")
@Test
void testTotalPriceWithMultipleMenus() {
// given
Order order = new Order("양송이수프-1,레드와인-2,초코케이크-1");
// when
int totalPrice = order.calculateTotalPrice();
// then
int expectedTotalPrice = Menu.APPETIZER_YANG_SONGI_SOUP.getPrice()
+ Menu.BEVERAGE_RED_WINE.getPrice() * 2
+ Menu.DESSERT_CHOCO_CAKE.getPrice();
assertThat(totalPrice).isEqualTo(expectedTotalPrice);
}
@DisplayName("D_DAY 이벤트 할인 테스트")
@Test
void dDayEventTest() {
// given
for (int i = 1; i <= 31; i++) {
Order order = new Order("티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1");
Date date = new Date(String.valueOf(i));
Event event = new Event(order, date);
// when
Discount discount = new Discount(event);
int dDay = discount.calculateDday(event);
// then
int increaseAmount = 0;
if (i <= 25) {
increaseAmount = (i - 1) * 100 + 1000;
}
assertThat(dDay).isEqualTo(increaseAmount);
}
}
이렇게 4주 차까지 과제를 진행하면서 이것 말고도 바뀐 것들이 굉장히 많지만 모두설명하기에는 글의 길이가 커지기 때문에
대표적인 것들만 설명했다. 과제를 진행하면서 많은 것들을 배울 수 있어서 좋았다.
이 경험을 바탕으로 내가 부족한 점이 무엇인지 알게 되었고 다음으로 향하기 위해 어떤 것들을 해야 하는지 방향성을 잡게 되었다.
프리코스 커뮤니티 참여 및 코드 리뷰 경험해 보기
프리코스를 진행하면서 디스코드 채널에 들어가게 되었는데 여기서는 프리코스참여자들끼리 모여서 자유롭게 채팅을 하거나 정보를 공유하거나 코드리뷰 등등 여러 가지 활동을 할 수 있는 디스코드채널이 있다.
하지만 나는 커뮤니티 참여를 많이 하지 못했다. 과제를 항상 기한이 다돼서 만들기도 했고 과제를 끝마치면 바로 다음과제가 주어졌기에 커뮤니티 채널을 이용을 하지 못했다. 그래서 프리코스 마지막주차인 4주가 지나고 난 후에야 겨우 디스코드
커뮤니티를 이용할 수가 있었고 코드리뷰라는 걸 해볼 수가 있었다. 코드리뷰를 하면서 다른 사람코드들을 보고 나와는 다른 코드들을 보면서 이렇게 만들 수도 있구나 하고 감탄하기도 하였고 좋은 코드가 있다면 클론코딩을 통해 배울 점도 수없이 많았다. 그리고 내 코드들을 리뷰 부탁하면서 내 코드들을 보면서 다른 사람들의 의견을 들을 수도 있었다. 그러면서 내가 작성하면서 알지 못했던 빠진 부분 등등 알게 되었고 그리 뷰들을 바탕으로 클린코드들을 만들 수가 있었다.
마지막 주차는 비공개 저장소였기 때문에 코드리뷰를 하는 방법을 몰랐지만 코수 타 방송을 통해 알게 되었고 review 브런치를 만들어 main에서 review로 request를 보내서 review전용 request를 만들어서 코드리뷰를 할 수 있게 하였다.
아래는 review전용 requeset다
https://github.com/inhoru/java-christmas-6-inhoru/pull/2
크리스마스 프로모션 by inhoru · Pull Request #2 · inhoru/java-christmas-6-inhoru
크리스마스 프로모션 1. 디렉토리 구조 ├── cristmas │├── Application.java │├── controller ││├── OrderController // 프로젝트의 진행을 맡는 메인 컨트롤러 클래스 │├── domain ││├─
github.com
이런 식으로 코드리뷰라는 걸 경험해 보니 내 코드들의 문제점들과 개선점들을 알 수가 있었고 내가 리뷰를 달면서
아는 것이 있어야 리뷰를 달수 있었기에 리뷰를 달기 위해 공부를 할 수밖에 없었다. 코드리뷰를 받으려면 코드리뷰를 적어야 하고 리뷰를 적기 위해서 공부를 해야 한다. 그렇게 자연스럽게 아는 것들이 점점 많아졌다.
코드리뷰란 리뷰를 다는 사람과 받는 사람 둘 다 성장할 수 있는 좋은 시너지를 보여주는 거 같다.
1주 차 ~ 4주 차 미션 다시 구현해 보기
만약에 프리코스를 통과한다면 최종코딩테스트를 보게 되는데 최종코딩테스트는 1주 차 ~ 4주 차 와 같은 유형의 문제가 출제된다. 최종코딩테스트는 5시간 내로 만들어야 한다. 그렇기 때문에 프리코스 문제들을 5시간 내에 만들어야 한다는 건데
지금의 나로서는 불가능하다 모든 과제들을 3~4일 정도 소유됐기 때문이다. 그렇기 때문에 굳이 새로운 문제를 도전하기보다는 지금까지 문제들을 다시 구현해 보고 모두 5시간 내에 만들 수 있게 된 후에야 새로운 문제를 풀어보는 게 좋다고 생각이 된다. 현재 역량에서 새로운 것을 하는 것도 좋지만 같은 문제를 다른 방식으로 만들어보는 것도 좋은 연습이다.
후기
이렇게 캡틴이 보내주신 마지막메일의 내용들을 정리했다.
결과가 어떻게 될지는 모르지만 최선을 다했고 배운 것들도 많기에 떨어지더라도 후회는 없다.
학원을 수료하고 취업길에 섰던 나는 우연히 우테코가 지원을 받고 있다는 걸 알게 되었고 나의 부족함을 잘 알고 있었고
겨우 6개 월배 운 걸로 취업을 할 수 있나?라는 생각에 사로잡혀있었다 그렇게 우테코에 도망치듯 지원하게 된 거지만
우테코를 진행하면서 성장하는 걸 느낄 수 있었고 학원에서 배운 6개월만큼은 아니지만 4주 동안 많은 걸 배우고 꺠달을수 있었다. 만약에 떨어지게 되더라도 시간을 낭비했다는 생각은 안들 거 같다. 그만큼 의미가 있던 4주였다.
'우테코' 카테고리의 다른 글
우아한테크코스 프리코스 3주차 후기 (0) | 2023.11.11 |
---|---|
우아한테크코스 프리코스 2주차 후기 (0) | 2023.11.10 |
우아한테크코스 프리코스 1주차 후기 (0) | 2023.10.28 |