대외활동/우아한 테크코스 6기

[우아한 테크코스 6기 - 프리코스] 4주차 회고

oxdjww 2023. 11. 16. 00:32
728x90
반응형

Overview


우아한 테크코스 6기 프리코스 중 4주차 크리스마스 프로모션을 구현하고, 이에 대한 회고이다.
Github

 

UML

0. 들어가며

0.1 4주차의 의도?

‘클래스를 분리한다’ 가 객체지향의 특징 그 자체라고 생각한다. 이 목표를 지키려면 수많은 조건을 지켜야 하기 때문이다.

3주차에서 한 번 강조된 이 목표를, 4주차에서 한 번 더 강조하는 이유가 바로 이것 때문이라고 생각한다. 클래스가 정말 예쁘게 분리된 코드를 작성하기에 한 주만으로 당연히 부족했고, 여러 시행착오를 겪으며 4주차에 임하기를 바라는 것이 우테코의 의도였다면 다행히 조금은 이를 만족한 것 같다.

0.2 미션에 임하는 마음

물론 클래스를 분리한다는 것은 4주차에 와서도 어려운 일이었다.

클래스를 분리하고, 메서드를 최소단위로 쪼개며 객체를 객체답게 쓰는 방법에 대해 많은 고민을 했다.
그랬기에 구현을 시작 전, 기능 목록도 ‘내가 구현하고자 하는 프로그램의 행동 단위’라고 생각하고 작성하였다. 그 결과 많은 예외를 처리할 수 있어 만족스러웠다.

당연히 기능목록도 구현하며 변경되는 것이 존재했다. 하지만 3주간 꼼꼼하게 기능 명세를 작성하는 법을 배워서일까? 전보다는 적었다.
구현 후 테스트 케이스를 작성하며 기능을 점검할 때, 기능목록을 참고하여 많은 버그를 fix할 수 있었다. 소프트웨어 QA란 이런 것일까 라는 생각도 들었다.

4주차에선 3가지에 충실하고자 하였다.

  1. 피드백을 통해 실수를 줄이고, 이전보다 확실히 발전된 코드를 작성하자.
  2. 요구사항에 충실하며 과거보다 훨씬 객체지향적인 코드를 작성하자.
  3. 핵심 비즈니스 로직을 구별하고, 이에 대한 테스트 코드를 통해 비즈니스 로직에 신뢰성을 부여하자.

1. 피드백을 통해 더 성장하는 나

3주차의 피드백 문서와 코드리뷰 커멘트를 바탕으로 더 실수를 줄이고, readable한 코드를 작성하고자 하였다.

3주차 코드리뷰에서 입력값을 다시 받는 부분에서, 반복문 대신 재귀문을 추천받았다. 나는 여태 재입력 기능에서 반복문을 사용해왔었다. 동료의 리뷰를 받고 이런 생각이 들었다.

재귀적으로 적으면 코드가 readable하긴 하겠다.
하지만 재귀는 스택 오버플로 위험이 있는데 괜찮을까?

그래서 재입력을 재귀로 처리하는 것에 대한 레퍼런스를 찾아본 결과, 꼬리 재귀를 통해 컴파일러의 최적화 과정을 통해 내부적으로 반복문으로 처리한다는 것을 발견할 수 있었다.

사실 이것도 알고리즘 강의에서 배웠던 내용인데, 덕분에 복습할 수 있었고, 좋은 피드백을 받아들여 코드에 적용할 수 있었다. 이렇게 하나하나 지식을 습득해서 이해하고 내 것으로 만드는 과정이 너무 보람차다.

2. 모듈화된 객체지향적 코드. 근데 이제 과거보다 더 발전된..

이번 주에도 컨벤션 및 객체지향 체조원칙을 지키려 노력했다.

2.1 객체를 객체답게 사용한다.

Date 객체는 어느 요일인지 판별하기 위해 DayofWeekenum 멤버변수를 갖는다.

이 멤버변수를 date.getDay().equals(DayOfWeek.THU) 와 같이 직접 꺼내서 비교하지 않고, date.is](http://date.is/)(DayOfWeek.THU)를 사용하여 private 멤버변수의 특징을 더 잘 지키며 캡슐화를 보장할 수 있었다.

또, 이렇게 getter를 줄이며 객체지향을 지키는 구체적 예가 더 있었다.
주문하는 날짜(Date객체)와 EVENT_DATE(크리스마스당일)을 비교할 때, Date 객체 내부의 date에 접근하기 위해 getDate()를 사용하는 대신, (date.isInRange(START_OF_EVENT_MONTH.getValue(), EVENT_DATE.getValue()))를 구현하는 방안을 생각할 수 있었다.

처음에는 이런 생각을 하지 못했는데, 나중에 리팩토링하며 이렇게 객체에 메시지를 담을 수 있었다.
결과적으로 .을 줄이면서, 멤버 변수에 대한 직접적인 접근을 더 줄일 수 있었다.

2.2 다형성을 제대로 맛보다

클래스를 분리하는 과정에서, 도메인을 하나하나 정의했다.

2.2.1 Dish

이때, 처음에는 메뉴를 enum으로 구현하려 하였다. 이름, 가격 특징들이 연관된 관계를 나타내었기 때문이다. 하지만 메뉴와 주문은 다른 문제였다. 주문에는 주문 수량이 필요했고, 또 각 메뉴의 음식들은 카테고리도 가져야 했다.
그랬기에 음식이라는 범주를 Dish로 정의하고, 이를 음식들이 상속받을 수 있게끔 했고, 공통된 멤버변수 및 메서드를 정의헀다.

그리고 상속 관계 중간인 진짜 음식과 Dish사이에서 카테고리를 지정해줄 추상 클래스 Appetizer, Beverage, Dessert를 선언했다. 그 결과 주문 객체는 주문메뉴인 Dish와 주문개수인 Count만 가지게 구성할 수 있었다.
이렇게 계층적으로 도메인을 정의하였고, 이는 나중에 DishGenerator를 구현할 때 큰 힘이 되었다.
사용자로부터 입력받은 주문 목록을 갖고 주문을 생성할 때, 하나하나 매치되는 클래스를 호출하는 것은 코드를 길어지게 만들었다.

이에, 메뉴 객체를 찍어내는 DishGenerator를 구현할 수 있었고, 객체를 찍어낼 때 부모 자료형인 Dish을 적극 활용할 수 있었다. 이 부분에서, 다형성으로 코드가 이렇게 간결해질 수 있다는 점에 정말 감탄스러웠다.
그리고 enum은 메뉴판 자체에 적용하여 음식 클래스를 구현할 때 매직넘버를 줄일 수 있었다.

2.2.2 DiscountPolicy

결국 프로그램의 핵심 기능인 할인 정책을 구현할 때도 다형성이 매우 도움되었다.

기본적으로 할인 정책의 특징 갖고 있는 DiscountPolicy 인터페이스를 구현하고, 이를 모든 할인정책이 구현하도록 했다. Dish에서는 멤버변수가 필요했기에 추상 클래스를 쓰고, 이곳에서는 DiscountPolicy의 기능 자체에 초점을 맞췄기에 인터페이스를 활용했다.

DiscountPolicy를 구현할 때 DayOfWeek라는 enum을 활용하여 매직넘버또한 줄여주었다.

2.2.3 View의 제대로 된 활용 (toString()의 책임)

3주차에서는 View에게 printMessage(String)를 오버로딩하여 도메인 영역에서 폼을 만들고, 이를 단순히 출력하는 역할만을 부여하였다.
그 과정에서 각 도메인이 toString()을 구현해야 했고, 도메인이 조금 뚱뚱해졌지만 View의 책임에 충실하게 했다고 생각했다.

하지만 toString()의 진짜 용도는 디버깅을 위한 것임을 깨닫고, View에서는 단순히 도메인 객체를 받아 이를 어떠한 형식으로 보여주는 것이 진정한 View의 책임임을 알았다.
일반적인 웹 서비스의 View에서도 html문서가 문서 포맷을 다 꾸며주는 것도 그 이유임을 알 수 있었다.

2.2.4 service의 활용 부족

복잡한 비즈니스 로직을 service에서 구현하며 패키지를 나눈 효과를 보고 싶었는데 미흡했다. 기존에 각종 할인 정책을 담아서, 할인 및 혜택 정책을 일괄적으로 관리하는 BenefitForm의 기능들이 service에 들어가는 것이 맞다고 생각됐다. 그렇기에 마감 몇시간을 앞두고 급하게 EventService를 만들어 전체 구조를 리팩토링했다.

서비스는 도메인의 로직만을 갖고 있고, 뷰는 출력만 담당하므로 이를 연결해줄 매개체가 필요했다. 이 과정에서, serviceview사이에서 DTO의 필요성을 느껴 java17record를 활용해볼 수 있었다.

3. 도메인 핵심 로직 테스트

3주차에 결심했듯이, 기능을 구현하며 단위 테스트 코드를 작성했다.

Date, Order 생성 시 입력에 대한 유효성 검증을 통해, 요구사항에 적합한 신뢰성 있는 클래스를 구축했다. 테스트 코드를 통해 그 후 기능 개발에서도 디버깅이 간단해져 테스트 코드의 중요성을 체감할 수 있었다.

핵심 로직을 비즈니스 로직을 수행하기 위한 중요 조건들이라 정의하고, 이를 분리하고 각각에 대한 테스트를 작성하였다.

- 핵심로직
    - 날짜생성
    - 주문생성
    - 혜택확인
        - 메인디쉬,디저트 개수 확인
        - 증정 메뉴 조건 적용 확인
        - 정책에 따른 할인 금액 확인
        - 배지 조건 확인

이번주도 테스트 코드를 작성하며 버그를 발견할 수 있었다.
OrderItem(Dish,Count)에서, Count를 세지 않고 인덱스 중에서 Dish가 메인메뉴인 개수를 측정하는 버그였다. (e.g.티본스테이크-2,해산물파스타-1로 들어가있으면 2로 취급)

4. 마치며

나는 소프트웨어 학부생이고, 웹 개발 프로젝트토 몇 차례 해봤다.

하지만 이 프리코스를 거치며 내가 얼마나 부족한지 깨닫고 이를 채우기 위해 노력하는 값진 경험을 했고, 실제로 4주간 많이 성장했다.

MVC에 대한 이해, 다양한 디자인 패턴, 캡슐화와 다형성 등 객체지향, 일급 컬렉션, 테스트 코드 개발, 클래스 분리 등 정말 많은 키워드가 있었다.

머리로 이해했던 이론을 체화시키며 조금이나마 내 것으로 만들었다.
또한 프리코스 커뮤니티를 통해 많은 동료들과 코드 리뷰를 주고 받으며 새로운 지식을 터득했다. 또, 지식을 나누며 나도 배웠고, 나눔은 결코 시간이 아까운 일이 아니라는 것을 한 번 더 깨달을 수 있었다.

본 과정에서는 더 성장할 수 있을 것이다. 프로그래밍 기본 교육에서는 단순히 언어의 문법을 공부하는 것이 아니라, 진정하게 프로그래밍 언어를 적극 활용하며 클린코드를 작성하고 싶다. 또 이를 프로젝트에 적용하고 싶다.

붙을 수만 있다면..



나는 아직 갈길이 멀고, 부족하다. 그리고 내 자신과 싸우며 정진하며 긍정적 가치를 창출해내는 프로그래머로 성장할 것이다!

감사합니다.

728x90
반응형