과제 체크포인트
배포 링크
https://lieblichoi.github.io/front_6th_chapter2-1/
기본과제
- 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
- 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
- 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
- 매직 넘버와 문자열을 의미 있는 상수로 추출했는가? (
constants.js) - 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가? (
calculator.js) - 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가? (관심사 분리)
- 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
- 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가? (
app.js에서 모듈 조립) - 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가? (
state,view,events,services등) - ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가? (ES 모듈 시스템)
- 전역 상태와 부수 효과(side effects)를 최소화했는가? (
state.js로 상태 중앙화) - 에러 처리와 예외 상황을 명확히 고려하고 처리했는가? (재고 부족 알림 등)
- 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
- 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가? (
calculator.jsvsview.js) - 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가? (순수 함수 분리)
- 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
- 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가? (모듈 구조)
- 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가? (가상)
- (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?
심화과제
- 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가? (React 컴포넌트 구조)
- 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가? (커스텀 훅, Reducer)
- 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가? (
@testing-library/react) - 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
- (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가? (Vanilla JS 리팩토링 -> React 마이그레이션)
과제 셀프회고
이번 과제는 주어진 코드가 워낙에 잘(?) 꼬여있어서 그런지,, 처음엔 막막했습니다. 그래도 점차 개선해보면서 리액트로 넘어가는 과정이 재미있기도 했습니다. 단순히 코드를 작성하는 걸 넘어서 왜 이렇게 설계해야 하는가에 대한 고민을 해볼 수 있었습니다.
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
관심사별로 분리하기
솔직히 처음 main.basic.js 파일을 열었을 땐 한숨부터 나왔습니다. 한 번에 갈아엎고 싶다는 생각이 굴뚝같았는데 일단 참고 기존 코드를 기능별로 하나씩 분리하는 작업부터 시작했습니다. constants.js, state.js, calculator.js 등으로 코드를 옮기는 과정이 답답했는데 단순한 파일 나누기가 아니라 뒤죽박죽 섞여 있는 코드를 일일히 직접 정리하려고 하다보니 더 쉽지 않게 느껴졌던 것 같습니다.
그래도 처음에 잘 나눠 놓으니 나중에 리액트로 넘어갈 때 도움이 많이 됐습니다. state.js는 useReducer로, calculator.js는 useMemo로, services.js는 useSpecialSales 커스텀 훅으로 거의 1:1 대응되듯이 자연스럽게 바꿀 수 있었습니다. 덕분에 생각보다는 큰 부침없이 안정적으로 과제를 끝낼 수 있었던 것 같습니다.
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
테스트 코드...
기본 과제 진행할 때 수량 감소를 검증하는 테스트가 계속 실패해서 정말 쉽지 않았습니다. 분명 브라우저에서는 잘 동작하는데 테스트만 실패해서 난감했습니다. 좀 파보다가 범인은 코드가 아니라 리팩토링된 코드와 테스트 코드 사이의 구조적인 간극이었던 것 같다는 결론을 내렸습니다. 개선한 코드는 '사용자 이벤트 발생 -> 동기적으로 상태 변경 -> 변경된 상태에 따라 동기적으로 뷰 렌더링'이라는 흐름을 가지도록 작성되었는데, 기존 테스트 코드는 비동기로 이벤트를 발생시킨 다음에 결과가 즉시 DOM에 반영되는 걸 그리고 있었습니다. 이 미묘한 차이가 계속 실패를 만들었던 것 같습니다. 결국 갈아 엎는 대신에 테스트 코드를 개선 코드에 맞게 수정하는 쪽을 택했습니다. 당연한 이야기지만 리팩토링 때는 아키텍처의 변화에 맞춰 테스트도 함께 수정해야 할 수도 있다는 걸 새삼 느낀 순간이었습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
-
기본 과제의 방향성
심화 과제를 좀 더 수월하게 하려고 기본 과제 코드를 리팩토링할 때 리액트와 비슷한 구조를 만들어보려 노력했습니다. 상태는
state.js에, 계산은calculator.js에, 뷰 렌더링은view.js에 모으고,stateManager.subscribe를 이용해 상태가 변하면 화면 전체를 다시 그리도록 연결했습니다.// src/basic/app.js (요약) function main() { // ... const render = () => { const currentState = stateManager.getState(); const { cart, products } = currentState; // 계산 로직 호출 const subtotal = calculateSubtotal(cart, products); // ... 수많은 계산 ... // 뷰 업데이트 함수들을 하나씩 호출 updateItemCount(totalQuantity); updateCartSummary({ /* ... */ }); updateDiscountInfo(/* ... */); updateLoyaltyPoints(/* ... */); renderCart(cartItemsContainer, cart, products); }; stateManager.subscribe(render); // ... }그런데 과제를 다 하고 나서 보니 이게 최선이었는가 싶었습니다. 각 파일의 책임은 명확해졌지만서도 오히려 전체적인 동작 흐름은 한눈에 파악하기 더 어려워진 것 같았습니다. 결국
app.js의render함수가 모든 계산과 모든 DOM 업데이트의 순서를 다 일일이 지시하는 구조가 되었는데, 분업을 시켜놓고render함수가 모든 일을 하나부터 열까지 다 챙겨야 하는 느낌,, 테오가 발제 때 경고했던게 이런 모습인 것 같아서 리뷰 받고 싶은 부분이라기 보다는,, 개인적인 아쉬움이 남는 부분이라 공유해봤습니다. 다음 과제에서는 좀 더 신경써서 개선해보려 합니다.
과제 피드백
재환님 수고많으셨습니다~
구독, 펍섭등 이벤트 기반의 구조는 장점이 정말 많은 아키텍처가 될 수도 있지만 말씀하신대로 흐름을 파악하기 힘든 단점도 있습니다. 차라리 state.js 요녀석을 펍섭 기반의 상태관리도구로 더 확장해서 액션을 주입하는 형태로 구현했으면 어떨까 싶어요. 마치 리덕스의 리듀서 , 액션처럼 뭔가 stateManager에 addAction() 해서 콜백을 전달하고 stateManager는 콜백에 필요한 state 메타적인 함수를 전달하는 것이죵.. 그래서 calculator의 코드들은 stateManager의 확장 기능들이 되는 형태를 생각해볼 수도 있을 것 같고욥. 초기화도 render가 해주는게 아니라 stateManger가 필요한 정보를 받아서 stateManger.init()하면 내부에서 초기화하는 것이죵.
그러면 view단도 조금 개선이 필요한데 흠. 이야기하자면 많이 길어질 것 같네요. 일단 지금구조에서만 보면 updateItemCount, updateCartSummary 이런 함수들이 직접적으로 구독하는 형태가 되면 어떨까 싶어요. 좀더 상태를 구분해서 직접 관심있는 주제나 데이터 이름으로 구독을 할 수 있으면 성능적으로 개선도 가능할 것 같고요.. 컴포넌트별로 렌더함수를 별도로 구현해서 컴포넌트단위로 구독을 걸어도 좋을 것 같은데 그러면 더무 큰변화가 될 것 같아요. state 에 proxy를 걸어서 누가 "나를" 사용하는가를 감지해고 데이터 별로 실행되는 컴포넌트 렌더러를 분리하면 조금더 프레임웍 수준으로 끌어올릴 수도 있을 것 같아요. 기본적으로 vue가 이렇게 반응형시스템을 구현하죵...
수고많으셨습니다 대환님!