과제 체크포인트
배포링크
기본과제
- 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
- 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
- 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
- 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
- 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
- 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
- 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
- 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
- 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
- ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
- 전역 상태와 부수 효과(side effects)를 최소화했는가?
- 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
- 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
- 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
- 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
- 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
- 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
- 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
- (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?
심화과제
- 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
- 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
- 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
- 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
- (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?
바닐라 JS → React TypeScript 마이그레이션
커밋 요약 보기
마이그레이션 규모가 커지다 보니 예상했던 커밋 단위를 지키지 못한 부분이 많았습니다.
리뷰하시는 분들의 피로도를 줄이기 위해 커밋 요약을 따로 문서로 정리해두었습니다.
조금이나마 리뷰에 도움이 되었으면 좋겠습니다!
시니어 프론트엔드 개발자 Juno의 피드백
Juno는 Bmad method에서 제공해준 시니어 개발자 + 아키텍터의 페르소나가 입혀진 AI Agent입니다..! 최대한 이번 과제를 일관성 있게 진행하도록 하기위해 Juno와 함께 과제를 진행했으며, 클린코드가 무엇인지 제가 지양하는 바에대해서 자주 전달했습니다.
변경 사항
아키텍처 개선
- 바닐라 JS에서 React TypeScript로 전환
- MVVM 패턴 도입으로 비즈니스 로직과 UI 분리
- 타입 안전성 확보
주요 리팩토링
// Before: 전역 상태 + DOM 조작
cartState.updateQuantity(productId, quantity);
document.querySelector('.cart-total').textContent = total;
// After: ViewModel + React
const { updateQuantity, cartSummary } = useCartViewModel();
updateQuantity(productId, quantity);
구조 변경
src/
├── components/ # UI 컴포넌트
├── viewmodels/ # 비즈니스 로직
├── types/ # 타입 정의
└── context/ # 상태 관리
핵심 개선점
1. 상태 관리 체계화
// CartViewModel.ts - 장바구니 로직 분리
export const useCartViewModel = () => {
const [state, dispatch] = useReducer(cartReducer, initialState);
const addToCart = (product: Product, quantity: number) => {
if (quantity > product.stock) {
throw new Error('재고 부족');
}
dispatch(cartActions.addToCart(product, quantity));
};
return { cartItems: state.items, addToCart };
};
2. 타입 안전성
interface CartItem {
product: Product;
quantity: number;
subtotal: number;
}
interface CartState {
items: CartItem[];
totalPrice: number;
}
3. 컴포넌트 단순화
// CartItem.tsx - 순수 UI 컴포넌트
const CartItem: React.FC<CartItemProps> = ({ item, onUpdateQuantity }) => {
return (
<div className="cart-item">
<ProductInfo product={item.product} />
<QuantityControl onQuantityChange={onUpdateQuantity} />
</div>
);
};
테스트 개선
Before
- DOM 조작 테스트 위주
- 통합 테스트만 가능
After
- ViewModel 단위 테스트 가능
- 컴포넌트 격리 테스트
- 타입 체크로 런타임 에러 감소
성능 최적화
- 불필요한 DOM 조작 제거
- React의 효율적인 렌더링 활용
- 메모이제이션을 통한 재계산 방지
유지보수성 향상
- 관심사 분리로 코드 이해도 증가
- 타입 시스템으로 리팩토링 안전성 확보
- 컴포넌트 재사용성 증가
마이그레이션 방식
점진적 마이그레이션으로 기존 기능 유지:
- 타입 정의 먼저 생성
- ViewModel 계층 구현
- React 컴포넌트로 단계별 전환
- 기존 로직 제거
과제 셀프회고
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
MVVM 아키텍처 도입과 페르소나 기반 시나리오 설계에 가장 많은 시간과 신경을 썼습니다. 클린 아키텍처 원칙을 적용하고, View와 상태 간의 결합을 최소화하려고 했습니다.
MVVM 패턴 적용에서 배운 점
이전에 MVVM 디자인 패턴이란을 작성하면서 React 환경에서 MVVM 패턴의 이론적 배경을 정리했었는데, 이번 프로젝트를 통해 실제 바닐라 JS → React TypeScript 마이그레이션에서 MVVM 패턴을 적용해보며 다음과 같은 새로운 인사이트를 얻었습니다:
이전 글에서 다룬 내용:
- ViewModel과 View의 1:N 관계
- 컴포넌트 구현부와 리턴부의 관계 분리
- 단방향 데이터 바인딩의 이해
이번 프로젝트에서 새롭게 알게된 점:
- 점진적 마이그레이션에서 MVVM 적용의 실용성: 기존 바닐라 JS의 전역 상태와 DOM 조작 코드를 React의 ViewModel로 점진적으로 전환할 때, 기존 로직을 유지하면서 새로운 패턴을 도입하는 구체적인 방법론
- useReducer와 MVVM의 궁합: 이전에는 useState 중심으로 생각했는데, useReducer가 ViewModel에서 복잡한 상태 관리에 더 적합함을 실제로 경험
- Context와 ViewModel의 결합: 전역 상태 관리에서 Context Provider와 ViewModel의 조합이 어떻게 작동하는지 실제 구현을 통해 체험
- 타입 시스템과 MVVM: TypeScript 환경에서 Model, View, ViewModel 각 계층의 타입 안전성을 보장하는 구체적인 방법들
실제 적용에서 느낀 MVVM의 장점:
- 바닐라 JS의 명령형 DOM 조작을 선언적 UI로 자연스럽게 전환
- 비즈니스 로직과 UI 로직의 명확한 분리로 테스트 용이성 증가
- 기존 기능을 유지하면서 점진적 개선이 가능한 구조적 안정성
추가된 내용
BMAD Method 관련 링크
- BMAD Method 사용 경험기 - 페르소나 기반 협업 경험 상세 기록
프로젝트 관리 링크
- Brownfield 아키텍처 - 레거시 시스템 개선 전략
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
MVVM 패턴 적용과 페르소나 협업을 더 체계적으로 진행했으면 좋았을 것 같습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
1. MVVM 패턴 적용에 대한 방향성 피드백 요청
MVVM 패턴을 적용할 때 ViewModel의 관여 범위에 대해 고민이 많았습니다. ViewModel이 View의 상태를 외부 세계와 연결해주는 계층이라 정의하고 작업했는데, 이 정의와 역할 분리가 적절했는지 궁금합니다. 특히, React + TypeScript 환경에서 MVVM 패턴이 적합한 선택인지, 아니면 다른 아키텍처 패턴(e.g., MVI, MVC, Clean Architecture)이 더 나았을지도 알고 싶습니다.
2. 페르소나 기반 협업 전략 피드백 요청
현재 3개의 페르소나(예: 일반 사용자, 관리자, 비로그인 사용자)를 기반으로 기능 시나리오를 구체화했는데, 이 조합이 협업과 사용자 중심 기능 정의에 충분했는지 피드백 받고 싶습니다. **다른 페르소나(예: QA, CS팀, 장애 대응 담당자 등)**를 고려하는 것이 향후 협업이나 기능 설계에 더 유리했을지도 궁금합니다.
3. 마이그레이션 시점과 전략 피드백 요청
바닐라 JS에서 React + TypeScript로 마이그레이션할 때 MVVM 패턴을 함께 도입했는데, 아키텍처 도입 시점이 적절했는지, 더 작은 단위에서 점진적으로 도입했어야 했는지 판단이 어렵습니다. 실제로 View와 ViewModel을 처음부터 분리하면서 진입장벽이 높아졌다는 느낌도 있었는데, 이에 대한 피드백을 듣고 싶습니다.
4. 테스트 전략 및 ViewModel 단위 테스트 관련
ViewModel 단위 테스트를 일정 비율 이상 작성했는데, 이 비중(예: ViewModel 기준 80% 이상 테스트 작성)이 현실적인 테스트 전략인지, 혹은 ViewModel의 역할을 더 축소하거나 확장함으로써 더 나은 테스트 전략이 있었는지도 궁금합니다.
5. 성능 최적화 및 상태 관리 전략
ViewModel 내부에서는 상태 관리를 위해 Context + useReducer 조합을 사용했습니다. 이 구조가 성능 측면에서 적절했는지, 혹은 Recoil, Zustand, Valtio 등 더 나은 상태 관리 대안이 있었는지도 궁금합니다. 특히, Context+Reducer 기반의 리렌더링 구조가 MVVM에서 자연스러운 선택인지, View와의 결합도 관점에서 개선할 수 있는 여지가 있는지도 피드백 받고 싶습니다.
6. 확장성과 유지보수 관점에서의 구조 피드백
현재 구현한 MVVM 구조가 향후 기능 확장(예: 페르소나 추가, 멀티뷰 대응, 서버 API 변경 등)에 얼마나 유연할지 확신이 없습니다. ViewModel이 가진 역할의 적절성과 도메인 계층을 분리하지 않은 점이 장기적인 유지보수에 어떤 영향을 줄 수 있는지 의견이 궁금합니다.
7. 추상화 레벨
평탄화되어있는 폴더구조로인해 관심사의 레벨이 올바르게 정렬되어있지 못한 구조로 보입니다. FSD말고 평탄화되어있는 폴더구조를 개선할수있는 방법이 있을까요?
과제 피드백
안녕하세요 준형님! 역시 믿고 보는 준형님의 과제네요 ㅎㅎ
1. MVVM 패턴 적용에 대한 방향성
저는 react 자체가 mvvm 이라고 생각했는데요, 제가 생각하는 MVVM은 이렇습니다.
- Model: 데이터
- View: UI(=HTML)
- ViewModel: UI를 본따서 만든 데이터
react에서의 view model은 jsx 인거죠 ㅎㅎ 그리고 이 jsx에 변화를 가하면 ui에도 변화가 생기는 모습이 mvvm 이라고 생각했어요.
여튼, 각설하고
현재 작성해주신 패턴에 대해서는 큰 이견이 없는 상태입니다. 제가 생각하기에 View Model 보다는 VAC 에 조금 더 가깝지 않나!? 라는 생각이 들어요! VAC는 component로 분리하는 모습인데, 준형님께서는 hook으로 분리해주신거죠 ㅎㅎ
https://wit.nts-corp.com/2021/08/11/6461
그리고 적합함을 판단하는 기준이 필요할 것 같은데요, 저는 "요구사항에 잘 대응할 수 있는가"를 기준으로 살펴볼 것 같아요.
가령, 할인율에 대한 요구사항이 추가된다고 했을 때 이를 잘 대응하는가!? 에 대한 부분인데요 컴포넌트는 수정하지 않아도 될 것 같고 ViewModel 내부의 로직들을 수정하면 되기 때문에 나쁘지 않다고 생각해요 ㅎㅎ
그런데 제가 코드를 쭉 살펴봤는데... 안쓰이는 코드가 너무 많네요 ㅠㅠ
심화과제에서 어디서 어떻게 할인이 이루어지는지 파악하는게 지금... 너무 힘들어요 다른 이유 때문이 아니라 가짜 코드 때문에 힘들달까.. 이름은 할인율에 대한 코드인데 살펴보면 이 코드를 쓰는 곳이 없어요. 테스트는 작성되어있지만...?
- UI가 수정 될 때 -> 핵심 로직을 그대로 사용하면 됨.
- 비즈니스 로직이 수정될 때 -> MVVM 때문이라기보단... 현재 정리되지 않은 코드들 때문에 애매한 부분이 있음
이렇게 요약할 수 있겠네요!
2. 페르소나 기반 협업 전략
개인적으로 "아키텍쳐 전문가" 라는 페르소나도 있으면 좋을 것 같아요 ㅎㅎ 현재 설계에 대한 장단점, 확작성에 대해 평가하고 개선점을 제안하는 그런 역할이 있으면 어땠을까 싶네요!
이 외에 부분은 무척 좋은 방향이라고 생각합니다.
다만, "사용자 시나리오"를 페르소나 단위 보다는 엣지케이스 단위로 정의하면 어땠을까 싶습니다.
최대한 많은 엣지케이스를 뽑아내고 이 엣지케이스에 대응하기 적절한 전략을 가져오는거죠.
지금은 "이 설계대로 만들꺼야!"를 정의 하여 진행하는 방식인데, 저는 제품과 제품의 도메인 대한 설계 전략이 있어야 한다고 생각해요.
목표가 설계가 되기보단, 목표가 제품이 되고 이 제품에 들어맞는 설계를 찾아내는 방식으로 시도해보는거죠
3. 마이그레이션 시점과 전략
앞선 내용과 이어지는 부분인데요, 꼭 MVVM을 선택해야 했는가? 에 대한 부분이 의문이랄까... MVVM으로 했어야 하는 이유가 있으면 좋았을 것 같아요. 필요한 이유를 산출하는 과정 같은게 필요하지 않을까요!?
진입장벽이 높아졌다는게 아마 코드를 수정할 때 알아야할 것들이 많아졌다 라고 제가 이해했는데요,
제가 아마 이 과제를 수행했다면,
기본과제와 심화과제의 코드를 공유하기 위한 domain package 를 하나 더 추가했을 것 같아요 ㅎㅎ 그리고 이 코드를 양쪽에서 연결하기 위한 장치가 있으면 좋아겠죠? 아니면 아예 그냥 양쪽 모두 reducer로 개선한다거나.
4. 테스트 전략 및 ViewModel 단위 테스트
저는 ViewModel에 대해서는 100%의 커버리지를 채워주면 어떨까!? 라는 생각입니다. 충분히 현실적인 테스트 전략이라고 생각해요.
저는 component의 비즈니스 로직을 훅으로 분리하고 이 훅에 대해서는 커버리지를 100% 채우려고 하는 편입니다. 그게 저희 팀에서 PR이 머지되어야 하는 조건 중에 하나이기도 해요.
다만 꼭 훅이 알 필요 없는 UI와 관련된 로직은 그냥 Component에 잔류시키는 편이랍니다 ㅎㅎ
5. 성능 최적화 및 상태 관리 전략
성능에 대한 관점으로 보자면.. 최악이라고 할 수 있죠 ㅋㅋ 모든 값이 다 context를 의존하고 있고 context가 최상위 계층을 의존하고 있어요.
MVVM 구조에서 꼭 context를 선택해야 했는가? 라고 했을 때.. 굳이? 라는 생각이 있어요. 말씀해주신 것 처럼 zustand를 사용하는 방법도 있고!?
무엇보다 모든 값을 다 최상위에서 관리할 필요는 없다고 생각합니다 ㅎㅎ 더 정확히는, 도메인에 대한 로직은 한 폴더에 묶어서 관리하면 좋지만 이 값을 아예 최상위에서 다 내려주는건 다른 문제라고 생각해요.
6. 확장성과 유지보수 관점
말씀해주신 것처럼 가령 멀티뷰나 서버 API 변경 등에 대응하기가 지금 당장은 어려울 수 있겠지만 그런데 또 그렇게 어려운가? 라고 생각해보면 아니라고 생각해요 ㅎㅎ 현재 구조에서 외부 의존성을 받아들일 수 있는 구멍을 하나 만들어준다거나 하는 방식으로 확장하면 어떨까 싶어요.
이번 과제를 아예 MVVM으로 만들꺼야! 라는 목표로 진행해주신 것 같아요. 다만 MVVM을 꼭 써야만하는 이유나 의사결정 과정이 눈에 보이지 않은게 제일 아쉽달까.. 특히 기본과제와의 연결성이 약해요. 기본과제에서 작성한 코드가 재활용되면 좋겠는데 구조가 완전히 바뀌면서 연결되는 모습이 흐려졌네요 ㅠㅠ
현재 할인율 기능이 누락되어서, 테오코치님과 상의한 결과 "그러면 아쉽지만 탈락이라고 생각합니다. 리팩토링에서 제일 중요한건 기존 로직을 누락시키지 않는다는 것인데... 기존 로직이 누락된 리팩토링을 구조가 좋다고 잘했다고 할수는 없다고 생각합니다" 라는 결론이 내려졌어요.
열심히해주셨는데.. 아쉽지만 불합격입니다 ㅠㅠ