티스토리 뷰
본 포스팅은 우아한테크코스 7기 프론트엔드 전형 지원에 대한 회고록입니다.
이번 주 과제는 로또 발매기 제작이었습니다. 구입 금액에 따라 로또를 발행하고, 당첨 내역과 수익률을 출력하는 간단한 프로그램을 구현했습니다.
이전 기수에서도 동일한 주제가 있었다는 것을 알고 있었고, 꽤 어려운 과제라는 이야기를 들어서 부담이 컸습니다. 더군다나, 리액트의 함수 기반 프로그래밍에 익숙해진 상황에서 클래스 기반 객체지향 프로그래밍이 요구사항으로 주어지니, 처음부터 큰 고민이 시작되었습니다.
else를 남긴 이유🤫
이번 과제에서도 몇 가지 추가적인 요구사항이 있었습니다. else 사용 지양, 함수 15라인 제한, 기능에 대한 단위테스트(UI 로직 제외)입니다.
대부분의 요구사항을 준수했지만, else를 완전히 배제하지는 못했습니다. 아래 코드는 2등을 판단하기 위해 보너스 번호를 확인하는 로직입니다.
if (rewardKey === RANK_KEYS.FIVE_MATCH && bonusMatch) {
rankCounts[RANK_KEYS.FIVE_WITH_BONUS_MATCH]++;
} else {
rankCounts[rewardKey]++;
}
이 로직은 로또 번호를 맞춘 결과를 순위에 따라 분류하고 기록하는 역할을 하는 LottoMatcher 클래스입니다. 처음에는 if를 세 번 이상 사용할 뻔 했지만, 2등만 보너스 번호를 필요로 한다는 점을 고려해 else를 포함한 간결한 코드로 작성했습니다. 이런 이유로 else를 제거하지 않는 방향으로 마무리했습니다.
이번 과제의 서브 목표는 모듈 설계에 대한 학습이었습니다. 이전 과제에서는 App.js에 모든 코드를 작성했지만, 점점 요구사항이 복잡해지면서 유지보수의 중요성을 느꼈습니다.
그래서 프론트엔드에서는 무슨 모듈 설계를 하는데?🤷♀️
결과적으로 Domain-Service 구조를 선택했습니다.
처음에는 많은 지원자들이 사용하는 MVC 패턴을 고려했습니다. 하지만, 이번 과제는 콘솔 기반 프로그램으로 진행되었고, 이 환경에서는 MVC의 View와 Controller의 역할이 명확하지 않다는 문제가 있었습니다.
프리코스에서 진행하는 과제는 모두 콘솔 기반 프로그램입니다. 이 관점에서 View의 역할은 자칫 잘못하다 모호해질 수 있습니다.
콘솔 프로그램에서는 UI가 아닌 단순한 입력과 출력을 다루기 때문에, MVC 패턴을 적용하여 복잡한 UI 로직을 넣을 필요가 없습니다. 만약 제가 입출력을 담당하는 로직을 구현한다고 한들, 이는 Util에 더 가깝지 않나란 생각이 들었습니다.
모듈 설계에 대한 숙련도가 극히 낮은 상황에서 콘솔 프로그램에 MVC 패턴을 적용하면 오버엔지니어링의 위험이 걱정되었습니다. View의 역할은 단순히 입력과 출력을 수행하는 데 그치기 때문에, 오히려 코드가 과도하게 나눠지는 결과로 이어질 수 있기 때문입니다.
오버엔지니어링을 걱정한 이유🫨
왜 MVC가 아닌 Domain-Service 구조인가?
- View 로직의 단순함
- Controller 역할의 축소
- 진입점 중복
콘솔 프로그램에서 View는 단순히 입력과 출력을 처리하는 역할에 그칩니다. 복잡한 UI 로직이 없기 때문에, 이를 굳이 View로 정의하기는 애매했습니다.
Controller는 보통 사용자 입력을 받아 모델을 조작하고 View로 전달하는 역할을 합니다. 하지만 콘솔 프로그램에서는 이 과정이 단순해 Controller의 역할 또한 매우 작아졌습니다.
그 와중에 App.js에서 주요 로직을 Controller로 분리하려다 보니 App.js와 index.js 모두가 진입점처럼 보여졌습니다. 이는 SRP(단일 책임 원칙)와 YAGNI(필요하지 않은 것은 만들지 말라) 원칙을 위반하는 결과를 낳았습니다.
- SRP(단일 책임 원칙) 위반
App.js와 Controller가 각각 독립적인 책임을 가져야 하지만, Controller로 로직을 이식하면 두 파일 모두 주요 로직과 관련된 책임을 나눠가지는 구조로 변합니다. 특히, index.js와 App.js가 동시에 진입점처럼 느껴지는 모호함이 가독성을 크게 떨어뜨린다고 판단했습니다.
- YAGNI(You aren't gonna need it) 위반
콘솔 프로그래메서 단순히 입력과 출력만 필요했음에도 불구하고, MVC 패턴을 사용하게 되면 View와 Controller를 불필요하게 분리하게 됩니다. 추가 기능을 구현하는게 아니라면 오히려 코드 복잡성을 초래한다고 판단했습니다.
이러한 이유로, MVC 패턴을 적용하려는 시도가 구조적으로 과도한 설계로 이어진다고 판단했습니다.
Domain-Service 구조의 타당성📣📣
결론적으로, 비즈니스 로직과 처리 로직을 분리할 수 있는 Domain-Service 구조가 더 적합하다고 판단했습니다. 콘솔 입출력은 Utils로 처리하면 비즈니스 로직과 독립적일 수 있고 향후 UI가 추가되면 MVC로 확장할 가능성도 열어둘 수 있었습니다.
프로젝트의 최종 폴더 구조는 다음과 같습니다.
src/
├── domain/
│ └── Lotto.js
├── services/
│ ├── LottoIssuer.js
│ ├── LottoMatcher.js
│ └── ProfitCalculator.js
├── utils/
│ └── constants.js
└── validation/
├── LottoValidator.js
└── PurchaseValidator.js
DDD를 향한 첫걸음👣
DDD(Domain-Driven Design)는 복잡한 비즈니스 로직을 설계하고 구현하기 위한 방법론으로, 비즈니스 요구사항을 코드로 자연스럽게 표현하는 것을 목표로 합니다.
이번 프로젝트에서 DDD를 적용했다고 말할 수는 없지만, 도메인 중심으로 클래스 설계를 고려하는 과정에서 DDD의 일부분을 이해하는 시간을 가질 수 있었습니다.
DDD의 주요 개념
Entity와 Valud Object
객체의 고유성을 기준으로 설계
Aggregate
관련 객체를 그룹화하고 한 객체를 통해서만 관리할 수 있도록 일관성 유지
Repository
데이터 접근 계층을 캡슐화하여 데이터베이스와 비즈니스 로직 분리
Domain Event
도메인 상태의 변화를 명시적으로 표현
이번 설계는 객체지향을 중심으로 데이터를 캡슐화한 정도에 그쳤지만, DDD에 대한 이해를 키우는 첫걸음이 되었다고 생각합니다.
DDD(Domain Driven Design) - 도메인 주도 설계란? 마이크로서비스의 관점에서
객체지향에서부터 도메인 주도 설계를 이해하기 위해서는 객체지향을 먼저 이해할 필요가 있습니다 객체지향에서의 핵심은 뭘까요? 객체지향에서의 핵심은 실세계의 객체(물건, 사람, 주문 ....
huisam.tistory.com
도메인 주도 설계(DDD)의 이해와 실제 적용 사례
도메인 주도 설계(DDD)의 기본 원리와 실제 적용 사례를 통해 DDD가 소프트웨어 개발에 어떻게 기여하는지 설명합니다.
f-lab.kr
도메인 주도 개발(Domain-Driven Design, DDD)이란 무엇인가?
도메인 주도 개발(Domain-Driven Design, DDD)이란 무엇인가?1. DDD의 정의도메인 주도 개발(Domain-Driven Design, DDD)은 복잡한 비즈니스 도메인을 효과적으로 모델링하고 관리하기 위한 소프트웨어 개발 방
dev-jjang.tistory.com
객체지향을 이해하는 길😮💨
뭐랄까, 이번 과제는 꾸역꾸역 해결하기 위해 숱한 밤을 샜던 것 같습니다.
클래스에 대한 지식이 거의 없던 상태에서 처음부터 클래스로 작성하는 것은 어렵다고 판단해, 2일 간 함수 기반으로 기능을 구현한 뒤 클래스로 마이그레이션하는 계획을 세웠습니다.
하지만 야심찬 계획과 달리, static 메서드로 가득 찬 클래스 묶음이 탄생하고 말았습니다. 객체지향을 고려하지 않고 함수를 클래스로 옮기기만 한 상태였던거죠.
static 메서드를 지양하라고🤫
static 메서드를 남용하면 객체지향의 장점을 잃을 수 있다.
static 메서드는 클래스 자체에 속하며, 객체의 상태와 연관이 없기 때문에 다음과 같은 문제가 발생할 수 있습니다.
- 클래스 사용의 의미 퇴색
- 유연성 약화
- 테스트와 유지보수 어려움
클래스는 일반적으로 프로퍼티(상태)와 메서드(행위)를 결합하여 특정 역할이나 개념을 캡슐화하는 것이 최종 목표입니다. static 메서드는 객체의 프로퍼티를 다루지 않으므로, 클래스를 사용하는 이유가 희미해집니다.
static 메서드는 이러한 배경 때문에 상속과 다형성(polymorphism) 활용에 제한이 있습니다. 오버라이드가 불가능하기 때문에 유연한 설계가 어려워지는 것입니다. 이 특성 때문에, static 메서드가 늘어날수록 코드는 변경에 취약해지는 결과가 나타납니다.
만약 이러한 static 메서드가 외부 상태를 조작한다고 생각해봅시다. 한번 들어간 static 메서드는 대체가 어렵기 때문에 테스트 코드 작성도 힘들어집니다.
물론, Math 같은 유틸리티 함수에서는 static 메서드가 적합할 수 있지만, 객체의 상태와 결합된 설계를 고려해야 객체지향의 이점을 살릴 수 있습니다. 그래서 저는 싱글톤 패턴을 선택해 조금씩 개선하려 노력했습니다.
class LottoIssuer {
createLottoTickets(purchaseAmount) {
const ticketCount = purchaseAmount / LOTTO.TICKET_PRICE;
const lottoTickets = [];
for (let i = GAME_SETTINGS.ZERO; i < ticketCount; i++) {
const ticketNumbers = Random.pickUniqueNumbersInRange(
// ...코드 생략
).sort((a, b) => a - b);
lottoTickets.push(new Lotto(ticketNumbers));
}
// ... 코드 생략
return lottoTickets;
}
}
export default new LottoIssuer();
객체 상태와 역할에 대한 고민🤔
처음으로 객체의 상태를 고려하며 프로그래밍을 하니 정말 어려웠습니다. 객체를 어떻게 가져오고, 독립적으로 구현할지 고민하며 클래스를 나누고 시나리오를 설계했지만 다음과 같은 질문들이 끊임없이 떠올랐습니다.
- 이 클래스는 Service로 정의하는 것이 맞을까?
- 이 로직은 Domain으로 분리하는 게 더 적합하지 않을까?
특히, Domain과 Service의 경계를 명확히 설정하는 데 익숙하지 않다 보니, 각 역할의 책임을 정의하는 데 많은 어려움을 느꼈습니다. 이 고민을 제한된 시간 내에 모두 해결하기에는 무리가 있었습니다. 그래서 초기 설계대로 진행하되 지금 할 수 있는 최선의 구현에 집중하는 전략을 취하게 되었습니다.
다음 과제 계획
이번 과제에서 가장 아쉬웠던 점은 비즈니스 로직의 중복을 대처하는 자세였습니다. 로또 프로젝트에서는 당첨 번호가 5개 맞을 경우만 보너스 번호를 체크하지만, 조건에 따라 보너스 번호로 순위를 결정하는 로직이 늘어나면 코드 복잡도가 크게 상승할 가능성을 느꼈습니다.
if (rewardKey === RANK_KEYS.FIVE_MATCH && bonusMatch) {
rankCounts[RANK_KEYS.FIVE_WITH_BONUS_MATCH]++;
} else {
rankCounts[rewardKey]++;
}
따라서 다음 과제에서는 비슷한 패턴의 비즈니스 로직을 효율적으로 처리할 수 있는 디자인 패턴을 탐구하고 적용해볼 계획입니다. 이를 통해 중복 로직을 줄이고 더 세련된 코드를 작성하는 방법을 배우고 싶습니다.
GitHub - mindaaaa/javascript-lotto-7
Contribute to mindaaaa/javascript-lotto-7 development by creating an account on GitHub.
github.com
'Oops, All Code! > 🚀 Woowacourse' 카테고리의 다른 글
[프론트엔드 7기] 최종코딩테스트 준비하기, 루틴을 적용한 점심메뉴 (0) | 2024.12.06 |
---|---|
[프론트엔드 7기] 우아한프리코스 - 전략패턴(Strategy Pattern)을 이용한 우아한 편의점 (0) | 2024.11.17 |
[프론트엔드 7기] 우아한프리코스 - 삼항연산자와 자동차 경주 회고 (0) | 2024.10.31 |
[프론트엔드 7기] 우아한테크코스 - 정규표현식과 문자열 계산기 회고 (1) | 2024.10.22 |
우아한테크코스 7기 프론트엔드 도전: 서류 접수와 개발새발 지원서 (2) | 2024.10.10 |
- Total
- Today
- Yesterday
- 서평
- 회고
- 대학생팝업스토어
- 타입좁히기
- react
- js
- typescript
- 소사벌
- 소사벌맛집
- 도서추천
- 책추천
- 코딩테스트
- 피어피드백
- 프리코스
- 네이버부스트캠프
- 카페추천
- 어른의어휘공부
- 우아한테크코스
- javascript
- 일급객체
- 안성스타필드
- 어휘력
- 프론트엔드
- 트러블슈팅
- 비즈플리마켓
- 프로토타입
- 경험플리마켓
- 대학생플리마켓
- 카드뉴스
- 도서리뷰
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |