

4주차 과제가 끝난지 3주 가까이 지나서야 작성하기 시작하는 4주차 개발 회고글이다. 일과 병행하면서 남는 시간에 잠 줄여가며 참여했던 프리코스인지라 끝난 뒤 휴식도 필요했고 밀린 일을 처리하기도 바빴었다. 아무튼...나에게 멘붕을 선사해주었던 4주차 편의점 과제...
슬프게도 4주차 과제는 테스트 통과만 겨우하고 기능만 겨우 구현해서 제출했다. 일이 겹쳐서 절대적인 시간이 부족하기도 했고, 기능요구사항이 많은 과제에 아직 익숙치않은 TDD, 객체지향 프로그래밍을 적용하며 풀어가는 과정에서 시간 분배에 완전히 실패했기 때문이었다.
기능을 개발해가는 과정에서 계속 혼자 해석이 달라지고 헷갈려서 기능 개발이 끝난 줄 알았는데 계속 또 수정해가며 시간을 빼앗긴 문제도 있었다.
결국 핵심 메서드 찢기에도 실패해서 함수 길이가 100줄이 되는 것에 더해 단일 책임 원칙도 지키지 못했고, 문제가 되는 저 메서드의 들여쓰기도 2를 초과하고 말았다... 프로그래밍 요구사항을 전혀 지키지 못한 것이다...ㅠㅠ 제출 직전까지 해서 내느라 기본적인 예외 케이스 검증 로직도 처리하지 못했던 것은 덤이다... 정말 돌아가는 쓰레기를 제출해버리고 말아서 제출하고나서 허무하고 우울해했던 기억이 난다.
하지만 많은 것을 배울 수 있었던 과제이고 일단 4/4로 제출할 수 있었음에 위안을 삼아본다🥹
해당 레포를 공개로 돌려놓기 상당히 부끄럽지만...
절망의 내 4주차 프리코스 제출 링크: https://github.com/dev-dino22/javascript-convenience-store-7-dev-dino22
그나저나, 4주차 과제를 진행하는 1주일동안 새로 배웠던 것과 깨닫고 느낀 점이 너무 많았는데, 이것들을 이 포스팅에 글로 정리하려면 정말 오래 걸릴 것 같다...일단 아직 2주차 회고도 작성 중인 상태고 그 와중에 최종 코테 준비로 5시간 시간제한과 AI 사용제한을 두고 1주차부터 과제를 다시 풀어보고 있어서 이 것에 대한 회고 작성도 병행해야하므로... 과제사항만 일단 백업해두고 조금씩 글을 작성해나가야겠다.
4주차 프리코스 과제의 요구사항은 다음과 같았다.
편의점
📌 과제 진행 요구 사항
- 미션은 편의점 저장소를 생성하는 것으로 시작한다.
- 기능을 구현하기 전
README.md에 구현할 기능 목록을 정리해 추가한다.- Git의 커밋 단위는 앞 단계에서
README.md에 정리한 기능 목록 단위로 추가한다.- AngularJS Git Commit Message Conventions을 참고해 커밋 메시지를 작성한다.
- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다.
🔨 기능 요구 사항
구매자의 할인 혜택과 재고 상황을 고려하여 최종 결제 금액을 계산하고 안내하는 결제 시스템을 구현한다.
- 사용자가 입력한 상품의 가격과 수량을 기반으로 최종 결제 금액을 계산한다.
- 총구매액은 상품별 가격과 수량을 곱하여 계산하며, 프로모션 및 멤버십 할인 정책을 반영하여 최종 결제 금액을 산출한다.
- 구매 내역과 산출한 금액 정보를 영수증으로 출력한다.
- 영수증 출력 후 추가 구매를 진행할지 또는 종료할지를 선택할 수 있다.
- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
재고 관리
- 각 상품의 재고 수량을 고려하여 결제 가능 여부를 확인한다.
- 고객이 상품을 구매할 때마다, 결제된 수량만큼 해당 상품의 재고에서 차감하여 수량을 관리한다.
- 재고를 차감함으로써 시스템은 최신 재고 상태를 유지하며, 다음 고객이 구매할 때 정확한 재고 정보를 제공한다.
프로모션 할인
- 오늘 날짜가 프로모션 기간 내에 포함된 경우에만 할인을 적용한다.
- 프로모션은 N개 구매 시 1개 무료 증정(Buy N Get 1 Free)의 형태로 진행된다.
- 1+1 또는 2+1 프로모션이 각각 지정된 상품에 적용되며, 동일 상품에 여러 프로모션이 적용되지 않는다.
- 프로모션 혜택은 프로모션 재고 내에서만 적용할 수 있다.
- 프로모션 기간 중이라면 프로모션 재고를 우선적으로 차감하며, 프로모션 재고가 부족할 경우에는 일반 재고를 사용한다.
- 프로모션 적용이 가능한 상품에 대해 고객이 해당 수량보다 적게 가져온 경우, 필요한 수량을 추가로 가져오면 혜택을 받을 수 있음을 안내한다.
- 프로모션 재고가 부족하여 일부 수량을 프로모션 혜택 없이 결제해야 하는 경우, 일부 수량에 대해 정가로 결제하게 됨을 안내한다.
멤버십 할인
- 멤버십 회원은 프로모션 미적용 금액의 30%를 할인받는다.
- 프로모션 적용 후 남은 금액에 대해 멤버십 할인을 적용한다.
- 멤버십 할인의 최대 한도는 8,000원이다.
영수증 출력
- 영수증은 고객의 구매 내역과 할인을 요약하여 출력한다.
- 영수증 항목은 아래와 같다.
구매 상품 내역: 구매한 상품명, 수량, 가격
증정 상품 내역: 프로모션에 따라 무료로 제공된 증정 상품의 목록
금액 정보
- 총구매액: 구매한 상품의 총 수량과 총 금액
- 행사할인: 프로모션에 의해 할인된 금액
- 멤버십할인: 멤버십에 의해 추가로 할인된 금액
- 내실돈: 최종 결제 금액- 영수증의 구성 요소를 보기 좋게 정렬하여 고객이 쉽게 금액과 수량을 확인할 수 있게 한다.
products.md과 promotions.md 파일을 이용한다.[콜라-10],[사이다-3]
Y
Y
Y
Y
재고 없음을 출력한다.안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 10개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 5개
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
현재 {상품명}은(는) 1개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)
현재 {상품명} {수량}개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)
멤버십 할인을 받으시겠습니까? (Y/N)
===========W 편의점=============
상품명 수량 금액
콜라 3 3,000
에너지바 5 10,000
===========증 정=============
콜라 1
==============================
총구매액 8 13,000
행사할인 -1,000
멤버십할인 -3,000
내실돈 9,000
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 10개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 5개
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[콜라-3],[에너지바-5]
멤버십 할인을 받으시겠습니까? (Y/N)
Y
===========W 편의점=============
상품명 수량 금액
콜라 3 3,000
에너지바 5 10,000
===========증 정=============
콜라 1
==============================
총구매액 8 13,000
행사할인 -1,000
멤버십할인 -3,000
내실돈 9,000
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
Y
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 7개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 재고 없음
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[콜라-10]
현재 콜라 4개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)
Y
멤버십 할인을 받으시겠습니까? (Y/N)
N
===========W 편의점=============
상품명 수량 금액
콜라 10 10,000
===========증 정=============
콜라 2
==============================
총구매액 10 10,000
행사할인 -2,000
멤버십할인 -0
내실돈 8,000
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
Y
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 재고 없음 탄산2+1
- 콜라 1,000원 7개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 재고 없음
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[오렌지주스-1]
현재 오렌지주스은(는) 1개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)
Y
멤버십 할인을 받으시겠습니까? (Y/N)
Y
===========W 편의점=============
상품명 수량 금액
오렌지주스 2 3,600
===========증 정=============
오렌지주스 1
==============================
총구매액 2 3,600
행사할인 -1,800
멤버십할인 -0
내실돈 1,800
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
N
🛠️ 프로그래밍 요구 사항 1
- Node.js 20.17.0 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 App.js의 run()이다.
- package.json 파일은 변경할 수 없으며, 제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 process.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 JavaScript Style Guide를 원칙으로 한다.
🛠️ 프로그래밍 요구 사항 2
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
🛠️ 프로그래밍 요구 사항 3
- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- else 예약어를 쓰지 않는다.
힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.- 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다.
🛠️ 프로그래밍 요구 사항 4
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- 입출력을 담당하는 클래스를 별도로 구현한다.
- 아래 InputView, OutputView 클래스를 참고하여 입출력 클래스를 구현한다.
- 클래스 이름, 메소드 반환 유형, 시그니처 등은 자유롭게 수정할 수 있다.class InputView { fun readItem(): String { println("구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])") val input = Console.readLine() // ... } // ... } class OutputView { fun printProducts() { println("- 콜라 1,000원 10개 탄산2+1") // ... } // ... }
라이브러리
Missionutils에서 제공하는DateTimes및ConsoleAPI를 사용하여 구현해야 한다.
- 현재 날짜와 시간을 가져오려면DateTimes의now()를 활용한다.
MainController (controllers/)
- ✅ 현재 재고 출력
- ✅ 구매할 상품 장바구니 추가
- ✅ 프로모션 확인 및 처리
- ✅ 재고 차감
- ✅ 멤버십 할인 적용 여부 확인 후 영수증 출력
- ✅ 추가 구매 여부 확인
InputView (views/)
- ✅ 구매할 상품명과 수량 입력 받기
- ✅ 추가 구매 입력 선택 (Y/N) 받기
- ✅ 멤버십 할인 적용 여부 (Y/N) 받기
- ✅ 프로모션 무료 상품 추가 여부 (Y/N) 받기
- ✅ 프로모션 포기 여부 (Y/N) 받기
OutputView (views/)
- ✅ 현재 상품 목록과 프로모션 정보 출력
- ✅ 구매 내역, 할인 내역, 최종 결제 금액이 포함된 영수증 정렬 출력
- ✅ 오류 발생 시 적절한 메시지 출력
ProductManager (models/)
- ✅ 초기 상품 목록 필드에 세팅
- ✅ 구매하려는 상품의 재고가 충분한지 확인
- ✅ 구매 후 재고 차감
- ✅ 현재 상품 목록 출력용 메세지 생성
Cart (models/)
- ✅ 장바구니에 상품과 수량 추가하고 할인금액 누적
- ✅ 상품별 가격과 수량을 바탕으로 프로모션 적용 총 구매액 계산
- ✅ 멤버십 할인을 적용해 최종 금액 계산
- ✅ 프로모션 재고가 부족하여 일부 수량을 프로모션 혜택 없이 결제해야 하는 경우, 일부 수량에 대해 정가로 결제할지 여부에 대한 안내 메시지를 출력
- ✅ 프로모션 적용이 가능한 상품에 대해 고객이 해당 수량만큼 가져오지 않았을 경우, 입력 (Y/N)에 따라 추가증정
PromotionManager (models/)
- ✅ N+1 프로모션(예: 1+1, 2+1) 혜택 적용
- ✅ 프로모션에 따른 할인 금액 계산
MembershipManager (models/)
- ✅ 멤버십 회원 할인 적용 (최대 8,000원까지)
loadProductData() (utils/)
- ✅ 초기 상품 목록 객체 데이터로 가져오기
loadPromotionData() (utils/)
- ✅ 초기 프로모션 목록 객체 데이터로 가져오기
✅ Node.js 20.17.0 버전에서 실행 가능
✅ 프로그램 실행의 시작점
- App.js의 run()
✅ package.json 파일 변경 금지
- eslint 포맷팅 안잡히도록 gitignore 등록하고 개발 시작
✅ 프로그램 종료 시 process.exit()를 호출 금지
✅ 파일, 패키지 등의 이름을 바꾸거나 이동 금지
✅ 자바스크립트 코드 컨벤션 Airbnb 스타일 가이드 준수
들여쓰기
- 최대 depth는 2까지만 허용. (e.g., while문 안에 if문 포함)
✅ 삼항 연산자 사용 금지
함수 길이 10줄 이내
- 함수(또는 메서드) 길이는 최대 10줄을 넘지 않아야 함.
else 사용을 지양하고, if 조건절에서 바로 return하는 방식으로 작성
함수는 한 가지 역할만 수행
- 함수는 단일 책임 원칙에 따라 한 가지 역할만 수행해야 함.
✅ UI 관련 로직을 제외한 모든 기능에 대해 단위 테스트 작성
- Jest로 모든 기능의 테스트를 작성하고, 테스트가 통과되는지 확인.
✅ @woowacourse/mission-utils 라이브러리 사용
- Console 및 DateTimes API 활용:
Console.readLineAsync(): 사용자 입력을 비동기로 받을 때 사용.
Console.print(): 출력을 담당.
DateTimes.now(): 현재 날짜와 시간을 가져오는 데 사용.✅ 입출력을 담당하는 클래스를 별도로 작성 (InputView, OutputView).
하드코딩 사용 지양
- 문자열 같은 데이터를 하드코딩하지 말고 상수로 모아서 관리하기
🛠️ [ERROR] 를 반환해야 하는 경우
공통
📍 ✅ 입력값이 비었을 경우 (trim() 적용 후)
📍 ✅ 형식이 잘못된 경우 (예: [사이다-3] 대신 [사이다3] 입력)
📍 ✅ Y/N Input은 Y, N 외의 다른 값을 입력했을 경우
📍 ✅ 숫자가 아닌 값을 입력했을 경우
📍 음수나 0과 같은 자연수(양의 정수)가 아닌 수를 입력했을 경우상품명과 수량 입력
📍 상품명과 수량 형식이 올바르지 않을 경우 (예: [사이다-3] 형식을 사용하지 않았을 때)
📍 상품명이 존재하지 않는 경우 (재고에 없는 상품 입력)
📍 재고보다 많은 수량을 입력했을 경우
📍 수량에 숫자가 아닌 값을 입력했을 경우
📍 수량이 음수이거나 0일 경우프로모션 적용 기간 확인
📍 유효하지 않은 프로모션 상품을 입력했을 경우 (예: 프로모션 기간이 지난 상품)
유효한 입력
공통
📍 Y/N 입력에서 y/n 소문자로 입력했을 경우
- 프로모션 정보가 존재하고 프로모션 기간 내일 때 프로모션 조건을 적용
- 상품의 프로모션 재고가 존재하는지 확인
buy와 get이 각각 1인 경우
최대 증정 가능한 수량(maxBonusQuantity)과 실제 가능한 증정 수량(actualBonusQuantity)으로 충분한 프로모션 재고 여부 처리:
프로모션 재고가 부족한 경우 (actualBonusQuantity < maxBonusQuantity)
추가 구매 수량을 정가로 결제할지 사용자에게 확인
정가 결제를 원하지 않으면 추가 구매 수량을 제거
증정 가능한 수량과 할인 금액을 계산하여 최종 결제 처리프로모션 재고가 충분한 경우 (actualBonusQuantity === maxBonusQuantity)
구매 수량이 짝수일 경우:
증정 수량을 구매 수량의 절반으로 설정하고, 할인 금액을 계산
구매 수량이 홀수일 경우:
추가 증정 여부를 사용자에게 묻기
추가 증정에 동의한 경우 구매 수량을 1 증가시키고 증정 수량을 전체 가능 수량으로 설정
추가 증정에 동의하지 않으면 1개의 상품만 구매 처리되며 할인 금액을 계산
buy와 get이 다른 값인 경우
최대 증정 가능한 수량(maxBonusQuantity)과 실제 가능한 증정 수량(actualBonusQuantity)으로 충분한 프로모션 재고 여부 처리:
프로모션 재고가 부족한 경우 (actualBonusQuantity < maxBonusQuantity)
정가 결제를 할지 사용자에게 확인하고, 정가 결제를 원하지 않으면 추가 구매 수량을 제외
정가 결제 시, 추가 구매 수량을 포함하여 결제를 진행
증정 가능한 수량과 할인 금액을 최종 결제 금액에 반영프로모션 재고가 충분한 경우 (actualBonusQuantity >= maxBonusQuantity)
구매 수량이 프로모션 조건(buy)을 정확히 맞추지 않을 때는 프로모션 증정 여부를 사용자에게 확인
추가 증정 여부에 따라 구매 수량을 증가시키거나 증정 수량과 할인 금액을 최종 결제에 반영
- 총 구매 수량과 증정 수량을 합산하여 결제에 반영
- 할인 금액을 적용하고, 최종 결제 금액을 계산하여 장바구니에 추가
안녕하세요! 4주차까지 과제를 마치게 되어 매우 기쁩니다!😄
...사실 뿌듯한 한 편 많이 아쉽기도 합니다. 왜냐하면 마지막 과제를 진행하면서 많은 문제와 부딪히고 벽을 느끼면서도 밤을 새 최선을 다하여 구현해보았지만 결국 프로그래밍 요구사항을 준수하지 못한 채 제출하게 되었기 때문입니다.
4주동안 프리코스 과제를 진행하면서 기대보다도 훨씬 더 많은 것을 배웠습니다. 다양한 문제에 부딪히며 스스로의 부족함을 크게 느끼기도 하였지만 이를 끈기있게 극복해나가는 과정에서 희열을 느끼기도 했습니다.
4주차 프리코스 과제는 독학으로 취미개발을 하던 저의 실력에는 상당히 어려운 과제였습니다.
나름 3주차까지는 MVC패턴, 단일 책임 원칙, 프로그래밍 요구사항, 객체지향프로그래밍 등등의 규칙들과 피드백 사항을 지키며 개발하려고 노력할 수 있었지만 이번 마지막 과제는 기능만 구현하기에도 굉장히 어려웠고, 결국 프로그래밍 요구사항까지 어겨가며 스파게티 코드로 기능만을 겨우 완성할 수 있었습니다.
그 결과, 말그대로 돌아가는 쓰레기를 만들어내게 되었습니다...🥹 너무도 아쉬움과 미련이 많이 남는 마지막 과제였지만 그만큼 많은 것을 배울 수 있었던 과제였기 때문에 느낀 점도 많습니다. 이를 돌아보며 회고를 작성해보려고 합니다.
물론, 마지막 과제를 완전히 준수하지 못한 아쉬움은 남아 있지만, 한 단계씩 기능을 구현하고 피드백을 반영하며 기초를 단단히 하는 과정에서 제 실력이 눈에 띄게 향상되는 것을 느낄 수 있었습니다. 특히, MVC 패턴, 단일 책임 원칙 같은 개념들을 이해하고, 직접 코드에 반영하면서도 한층 성장할 수 있었고, 더 나아가 생소한 객체지향적 사고를 적용하려는 노력이 스스로 매우 뿌듯했습니다.
하지만 결과적으로 이 중간 목표는 아직 이루지는 못한 것 같습니다. 오히려 어렵고 복잡한 기능을 구현하면서 기능 구현에 급급해 요구사항을 어겨가며 스파게티 코드를 만들어냈기 때문에 이 때의 목표를 돌아보며 반성도 됩니다.
하지만 비록 실제로는 돌아가는 쓰레기에 걸맞는 코드를 제출하게 됐지만, 중간회고의 목표를 저렇게 설정했던 것은 바람직했던 것 같습니다! 중간 목표에 다가가기 위해 TDD로 코드를 작성하는 도전을 해보았는데, 디버깅과 테스트가 훨씬 수월해지고 코드의 안정성이 높아지는 경험을 할 수 있었습니다. 테스트 코드 작성이 완전히 처음이라, 처음엔 어려웠지만, TDD 개발 방식을 습관처럼 따라가면서 객체의 역할과 책임을 고민하게 되었고, 전체적인 코드의 품질이 향상되는 것을 경험할 수 있었습니다.
각 미션의 목표를 달성하기 위해 세운 계획을 잘 이행했나요? 그 과정에서 어떤 전략이 효과가 있었나요?
미션을 수행하며 세운 계획은, 각 기능을 단계적으로 구현하고 주어진 요구사항을 최대한 유지하며 개발하는 것이었습니다. 이를 위해 TDD 방식을 도입해 테스트 단위부터 각 기능을 세밀하게 검증하는 전략을 택했습니다. 이렇게 코드를 짜다 보니 자연스럽게 디버깅 시간이 줄어들고, 오류가 발생하더라도 빠르게 찾아낼 수 있었습니다. 특히 프로모션 증정 기능을 구현할 때, 다양한 예외 상황을 고려해야 했는데, 이때 if문이 꼬리에 꼬리를 물며 늘어나는 형태로 작성되어 프로그래밍 요구사항을 위반하게 되었습니다... 비록 최종 코드는 스파게티처럼 꼬여버렸지만, 이 경험 덕분에 알고리즘과 코딩 로직에 대한 더 깊은 고민을 하게 되었습니다. 개발자들이 알고리즘을 공부해야하는 이유를 실감하게 된 순간이기도 했습니다.
몰입하고 함께 성장하는 과정을 통해 인상 깊었던 경험이나 변화가 있었나요?
마지막 과제에서 어려움이 컸지만, 그만큼 새로운 점을 배운 것들이 많아 뿌듯합니다. 특히 TDD 방식으로 개발할 때 얻는 안정감은 큰 변화였고, 코드 작성 전에 검증하는 습관이 디버깅에도 큰 도움이 되었습니다. 또한 평소에는 생각지 못했던 터미널 출력에서의 정렬 기능을 활용하기 위해 padStart() 같은 메소드를 찾으면서 터미널에서도 정렬을 유지하는 방법을 알게 된 점이 흥미로웠습니다. 예전에는 단순히 기능 구현에 집중했다면, 이제는 코드의 정돈된 출력을 고민하게 되었고, 이를 통해 조금씩 더 견고하고 가독성 좋은 코드 작성으로 나아가는 변화가 있었던 것 같습니다.
한 편, 인상깊으면서도 슬펐던 경험은 객체지향적 사고와 TDD 개발에 익숙치 않은 상태에서 무턱대고 테스트코드를 작성하며 각 객체의 독립적인 작은 기능부터 구현하다보니 기능 하나하나는 굉장히 빨리 만들어졌는데, 이를 서로 유기적으로 동작하게 하는 부분에서 예상치 못하게 너무 많은 시간을 소모했던 것입니다.
각 객체의 기능은 독립적으로 완벽하게 작동했고 그래서 각 객체가 모두 완성되었을 때 모든 테스트코드를 합격하는 모습을 보며 이제 서로 협력시켜 돌아가게하는 것 쯤이야 금방 할 수 있을 줄 알았습니다.
하지만 객체의 캡슐화를 깨지 않도록 프라이빗 필드의 정보를 밖으로 꺼내는 getter 사용을 지양하려고하면서부터 부딪힌 수많은 문제들이 단순 기능 구현에 시간을 많이 할애하게 하였습니다...
결국 점점 나름대로 깔끔하게 작성 중이었던 객체 내부 메소드들도 하나씩 스파게티가 되어가기 시작했고 그럼에도 막상 객체끼리 협력을 시키려니 끊임없이 튀어나오는 크고작은 버그들과 사투하게 되었습니다.
getter의 개념도 프리코스를 시작하고 객체에 대해 공부하며 처음 알았던 저는, 방금 배운 getter를 쓰지말라는 피드백에 많이 절망스러웠습니다 ㅠ_ㅠ
하지만 왜 getter 나 setter를 쓰면 안되는지, 객체끼리 데이터를 꺼내(get)지 말고 "메세지"를 던지라는 의미는 무엇인지 피드백을 꼼꼼히 읽고 참고 자료들을 찾아보면서 객체의 캡슐화에 대해 깊게 고민하고 공부할 수 있었습니다.
이 또한 혼자 공부할 땐 알지도, 고민해보지도 못했던 핵심적인 개념이라 어렵고 이해가 안돼서 식은 땀을 흘리면서도 즐겁고 설레서 두근거리는 값진 경험을 할 수 있었습니다😊
마지막 과제에서 그동안 잘 지켜왔던 프로그래밍 요구사항 마저도 지키지 못했다니 끝까지 많은 아쉬움이 남지만,
그 과정에서 기대하고 목표했던 바보다 정말 많이 배웠고, 더 큰 성장을 향해 나아갈 수 있는 이정표를 본 느낌이라 너무 즐거웠습니다! 더 배울 기회가 있었으면 좋겠습니다...!
감사합니다!
(작성 중 . . .)
(작성 중 . . .)
함수(메서드) 라인에 대한 기준도 적용한다
- 프로그래밍 요구사항에는 함수의 길이를 15라인으로 제한하는 규칙이 포함되어 있다. 이 규칙은 main() 함수도 동일하게 적용되며, 공백 라인도 한 라인으로 간주한다. 만약 함수가 15라인을 초과한다면, 역할을 더 명확하게 나누고, 코드의 가독성과 유지보수성을 높일 수 있는 신호로 인식하고 함수 분리 또는 클래스 분리를 고려해야 한다.
예외 상황에 대한 고민한다
- 정상적인 상황을 구현하는 것보다 예외 상황을 모두 고려하여 프로그래밍하는 것이 훨씬 어렵다. 하지만, 이러한 예외 상황을 처리하는 습관을 들이는 것이 중요하다. 코드를 작성할 때는 예상되는 예외를 미리 고려하여, 프로그램이 비정상적으로 종료되거나 잘못된 결과를 내지 않도록 한다.
예를 들어, 로또 미션에서 고려할 수 있는 예외 상황은 다음과 같다.
로또 구입 금액에 1000 이하의 숫자를 입력
당첨 번호에 중복된 숫자를 입력
당첨 번호에 1~45 범위를 벗어나는 숫자를 입력
당첨 번호와 중복된 보너스 번호를 입력
비즈니스 로직과 UI 로직의 분리한다
- 비즈니스 로직과 UI 로직을 한 클래스에서 처리하는 것은 단일 책임 원칙(SRP)에 위배된다. 비즈니스 로직은 데이터 처리 및 도메인 규칙을 담당하고, UI 로직은 화면에 데이터를 표시하거나 입력을 받는 역할로 분리한다. 아래는 비즈니스 로직과 UI 로직이 혼재되어 있다. 비즈니스 로직은 그대로 유지하고, UI 관련 코드는 별도 View 클래스로 분리하는 것이 좋다. 현재 객체의 상태를 보기 위한 로그 메시지 성격이 강하다면, toString() 메서드를 통해 상태를 표현한다. 만약 UI에서 사용할 데이터가 필요하다면 getter 메서드를 통해 View 계층으로 데이터를 전달한다.
class Lotto {
#numbers
// 로또 숫자가 포함되어 있는지 확인하는 비즈니스 로직
contains(numbers) {
...
}
// UI 로직
print() {
...
}
}
객체의 상태 접근을 제한한다
- 필드는 private class 필드로 구현한다.
class WinningLotto {
#lotto
#bonusNumber
객체는 객체답게 사용한다
- Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다. 이처럼 Lotto 객체에서 데이터를 꺼내(get) 사용하기보다는, 데이터가 가지고 있는 객체가 스스로 처리할 수 있도록 구조를 변경해야 한다. 아래와 같이 데이터를 외부에서 가져와(get) 처리하지 말고, 객체가 자신의 데이터를 스스로 처리하도록 메시지를 던지게 한다.
참고자료: getter를 사용하는 대신 객체에 메시지를 보내자
필드(인스턴스 변수)의 수를 줄이기 위해 노력한다
- 필드의 수가 많아지면 객체의 복잡도가 증가하고, 관리가 어려워지며, 버그가 발생할 가능성도 높아진다. 따라서 필드에 중복이 있거나 불필요한 필드가 없는지 확인하고, 이를 최소화한다.
성공하는 케이스 뿐만 아니라 예외 케이스도 테스트한다
테스트 코드도 코드다
- 테스트 코드 역시 코드의 일환이므로, 리팩터링을 통해 지속적으로 개선해 나가는 것이 중요하다. 특히, 반복적으로 수행하는 부분이 있다면 중복을 제거하여 유지보수성을 높이고 가독성을 향상시켜야 한다. 단순히 파라미터 값만 바뀌는 경우라면, 파라미터화된 테스트를 통해 중복을 줄일 수 있다.
단위 테스트하기 어려운 코드를 단위 테스트하기
class InputController {
static async getValidPurchaseAmount() {
try {
const input = await InputView.inputPurchaseAmount();
const purchaseAmount = Parser.parseNumber(input);
LottoValidator.validatePurchaseAmount(purchaseAmount);
return purchaseAmount;
} catch (error) {
Console.print(error.message);
return InputController.getValidPurchaseAmount(); // 이 부분을 await 으로