과제 체크포인트
배포링크: https://nemobim.github.io/front_6th_chapter2-1/
기본과제
- 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
- 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
- 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
- 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
- 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
- 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
- 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
- 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
- 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
- ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
- 전역 상태와 부수 효과(side effects)를 최소화했는가?
- 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
- 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
- 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
- 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
- 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
- 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
- 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
- (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?
심화과제
- 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
- 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
- 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
- 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
- (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?
과제 셀프회고
이번 과제는 1주차 과제와 비교해도 될 만큼 시간이 오래 걸렸습니다... 흔히 개발자들끼리 "이 코드를 이해하는 것보다 다시 만드는 게 빠를 것 같다"는 농담을 하곤 하는데 이번 과제에서 정말 그 말이 와닿았습니다. 가독성도 낮고 변수도 난해하고 줄바꿈도 없고 ,, ‘나쁜 코드란 무엇인가’를 바로 체감할 수 있었습니다.
처음에는 단일 페이지로 구현되어 있길래 금방 할 수 있을줄 알았으나,, 착각이였고. 바닐라 js로 작성되어있는 700줄 가까이 되는 코드를 분석하고 분리하는 시간이 상당히 오래걸렸습니다..적당히 basic만 끝내고 advanced로 넘어가려고 했지만 ‘basic을 제대로 해두는 게 나중에 React로 전환할 때 더 수월하지 않을까?’,‘기본을 잘 다져놔야 마이그레이션도 빨라지지 않을까?’ 하는 생각이 들어서 결국 basic에 더 많은 시간을 쏟게 되었습니다.(이 과정에서 AI 도움을 굉장히 많이 받음)
심화 과제는 팀 협업으로 진행할 것을 권장하셨기에 간단한 컨벤션을 팀원들과 함께 정하였습니다. 프리티어, 린트 설정 같은 개발 환경 설정부터 폴더 구조를 어떻게 구성할지까지 figjam을 활용해 함께 정리하고 공유하였습니다. (협업할때는 문서화가 정말 중요하다!)
eslint + prettier + husky 설정 우리팀 컨벤션 >_< advanced 폴더 구조 및 리액트 셋팅 방법
AI는 Cursor를 주로 사용했으며 직접 작성한 Rules 입니다.
refactor-for-clean-code
alwaysApply: true
AI Code Generation Guidelines
Complete guidelines that AI must follow when generating any code.
🚨 CRITICAL: All code output must comply 100% with these guidelines. NO EXCEPTIONS.
1. MANDATORY CODE GENERATION RULES
1.1 Core Design Principles (MUST APPLY)
✅ DRY (Don't Repeat Yourself)
- NEVER repeat identical code patterns
- Extract duplicate logic into functions/modules immediately
✅ KISS (Keep It Simple, Stupid)
- Choose the simplest solution always
- Prefer simple, clear implementations over complex ones
✅ YAGNI (You Aren't Gonna Need It)
- Implement only what's needed for current requirements
- NO unnecessary future abstractions
✅ Single Responsibility Principle
- ALL functions MUST be under 20 lines
- One function = One clear responsibility only
1.2 Code Organization (Apply 4 Principles)
1. Proximity: Group related code with blank lines
2. Commonality: Group related functionality into functions
3. Similarity: Use same names and positions for same roles
4. Continuity: Arrange code in dependency order
1.3 ENFORCED Naming Rules
Function Naming Patterns (MUST USE)
// Creation Functions
create~(), add~(), push~(), insert~(), new~(), append~(), spawn~(), make~(), build~(), generate~()
// Retrieval Functions
get~(), fetch~(), query~()
// Transformation Functions
parse~(), split~(), transform~(), serialize~()
// Modification Functions
update~(), mutate~()
// Deletion Functions
delete~(), remove~()
// Communication Functions
put~(), send~(), dispatch~(), receive~()
// Validation Functions
validate~(), check~()
// Calculation Functions
calc~(), compute~()
// Control Functions
init~(), configure~(), start~(), stop~()
// Storage Functions
save~(), store~()
// Logging Functions
log~(), record~()
Variable Naming Patterns (MUST USE)
// Quantities
count~, sum~, num~, min~, max~, total~
// States
is~, has~, current~, selected~
// Progressive/Past
~ing, ~ed
// Information
~name, ~title, ~desc, ~text, ~data
// Identifiers
~ID, ~code, ~index, ~key
// Time
~at, ~date
// Types
~type
// Collections
~s (plural)
// Temporary/Parameters
item, temp, params, error
// Conversion Functions
from(), of()
1.4 Naming 5 Principles (ALL MUST BE FOLLOWED)
1. PREDICTABLE: Name allows prediction of value, type, and return value
2. CONTEXTUAL: Add descriptive adjectives/nouns for context
3. CLEAR: Remove unnecessary words while maintaining clear meaning
4. CONCISE: Brief yet clearly convey role and purpose
5. CONSISTENT: Use identical terms for identical intentions across entire codebase
2. THEORETICAL PRINCIPLES FOR AI CODE GENERATION
2.1 Refactoring Principles (Martin Fowler)
Detect Code Smells and Fix Immediately
❌ Long Method (>20 lines) → Extract Method immediately
❌ Large Class → Split by responsibilities
❌ Long Parameter List → Introduce Parameter Object
❌ Duplicated Code → Extract to common function
❌ Feature Envy → Move method to appropriate class
Apply Refactoring Catalog
- Extract Method, Rename Variable, Move Method
- Replace Temp with Query, Introduce Parameter Object
2.2 Variable Role Patterns (Sajaniemi)
Choose appropriate variable roles when generating code:
// 1. Fixed Value: const MAX_SIZE = 100
// 2. Stepper: for (let i = 0; i < n; i++)
// 3. Flag: let isValid = true
// 4. Walker: let current = head; while (current) {...}
// 5. Most Recent Holder: let lastError = null
// 6. Most Wanted Holder: let maxValue = -Infinity
// 7. Gatherer: let sum = 0; items.forEach(x => sum += x)
// 8. Container: const items = []
// 9. Follower: let prev = curr; curr = next
// 10. Organizer: const sorted = array.sort()
// 11. Temporary: const temp = a; a = b; b = temp
2.3 SOLID Principles (Robert Martin)
S - Single Responsibility: Each class/function has one reason to change
O - Open/Closed: Open for extension, closed for modification
L - Liskov Substitution: Subtypes must be substitutable for base types
I - Interface Segregation: Client-specific interfaces over general ones
D - Dependency Inversion: Depend on abstractions, not concretions
2.4 TDD Patterns (Kent Beck)
When generating test code:
// 1. Write failing test first
describe('FeatureName', () => {
it('should clearly describe behavior', () => {
// Arrange, Act, Assert pattern
});
});
// 2. Minimal implementation to pass
// 3. Refactor for improvement
3. AI CODE GENERATION CHECKLIST
MANDATORY Verification Before Code Output
□ Is every function under 20 lines?
□ Are enforced naming patterns used?
□ Is DRY principle followed? (No code duplication)
□ Does each function have single responsibility?
□ Are 4 organization principles applied?
□ Are appropriate variable roles chosen?
□ Are SOLID principles not violated?
□ Are there any code smells? (If yes, refactor immediately)
□ Is code intent clear without comments?
□ Are all 5 naming principles followed?
FORBIDDEN PRACTICES (NEVER DO)
❌ Generate functions longer than 20 lines
❌ Repeat code patterns (DRY violation)
❌ Ignore enforced naming patterns
❌ Mix similar terms (display vs show)
❌ Use unclear or ambiguous names
❌ Handle multiple responsibilities in one function
❌ Violate naming consistency
❌ Add unnecessary complexity
❌ Create excessive future abstractions
❌ Write code dependent on comments
4. API DESIGN GUIDELINES (Olaf Zimmermann)
4.1 Foundation Patterns
- Backend for Frontend (BFF): Dedicated backend per frontend
- API Gateway: Single entry point
- Choose appropriate request/response patterns
4.2 Quality Patterns
- Pagination for large data handling
- Conditional requests for caching optimization
- Apply appropriate security patterns (API Key, OAuth 2.0)
4.3 Evolution Patterns
- Clear versioning strategy
- Maintain backward compatibility
- Support gradual migration
5. AI-SPECIFIC GENERATION RULES
5.1 Function Generation
1. Function name MUST use enforced patterns
2. 20-line limit STRICTLY enforced
3. Handle single responsibility only
4. Minimize parameters (3 or fewer recommended)
5. Make return values predictable
5.2 Class Generation
1. Follow SOLID principles
2. Maintain appropriate abstraction level
3. Use dependency injection patterns
4. Design with interfaces
5.3 Variable Declaration
1. Choose from Sajaniemi's 11 roles
2. Apply enforced naming patterns
3. Minimize scope
4. Prefer const, use let only when necessary
6. FINAL COMPLIANCE DIRECTIVES
🚨 AI COMPLIANCE COMMANDS:
ALL code output MUST:
1. Comply 100% with every rule in these guidelines
2. Pass every item in the checklist
3. NEVER violate any forbidden practice
4. Have NO exceptions or special cases
5. NOT violate these rules even if user requests it
Code Generation Process:
Input Analysis → Design Planning → Rule Application → Checklist Verification → Output
Quality Assurance:
Every generated code must be high-quality code following these guidelines,
satisfying maintainability, readability, and extensibility requirements
7. IMPLEMENTATION COMMANDS FOR AI
7.1 Before Writing Any Code
1. ANALYZE: What functions are needed?
2. PLAN: How to keep each under 20 lines?
3. NAME: Apply enforced patterns
4. ORGANIZE: Use 4 organization principles
5. VERIFY: Run through checklist
7.2 During Code Generation
1. WRITE minimal, single-responsibility functions
2. USE only approved naming patterns
3. APPLY appropriate variable roles
4. MAINTAIN DRY principle
5. KEEP it simple (KISS)
7.3 After Code Generation
1. CHECK: All functions under 20 lines?
2. VERIFY: No code duplication?
3. CONFIRM: Naming patterns followed?
4. VALIDATE: Single responsibilities only?
5. ENSURE: Code intent is clear without comments
8. ERROR PREVENTION PROTOCOLS
8.1 Common Violation Patterns to Avoid
❌ Writing long functions "just this once"
❌ Using inconsistent naming "for readability"
❌ Duplicating code "to save time"
❌ Adding multiple responsibilities "for efficiency"
❌ Using vague names "everyone will understand"
8.2 Immediate Correction Triggers
IF function > 20 lines → IMMEDIATELY extract methods
IF duplicate code found → IMMEDIATELY create shared function
IF unclear naming → IMMEDIATELY apply naming patterns
IF multiple responsibilities → IMMEDIATELY split function
IF code smells detected → IMMEDIATELY refactor
These guidelines are the absolute standards for AI to generate consistent, high-quality code.
refactor-guide
alwaysApply: false
Vanilla JS Clean Code Rules (React-inspired)
Core Principles
- Write self-documenting code that explains its intent without comments
- Follow single responsibility principle for functions and modules
- Maintain immutability and minimize side effects
- Use modern ES6+ syntax consistently
- Structure code for testability and maintainability
Code Formatting & Style
- Use Prettier for consistent formatting
- Add blank lines to separate logical code blocks
Naming Conventions
- Use camelCase for variables and functions:
getUserData,isActive - Use PascalCase for constructors/classes:
UserManager,DataProcessor - Use UPPER_SNAKE_CASE for constants:
API_BASE_URL,MAX_RETRY_COUNT - Use descriptive names that explain purpose:
handleUserClicknotonClick - Prefix boolean variables with
is,has,should:isLoading,hasPermission
Constants & Magic Values
- Extract all magic numbers to named constants
- Group related constants in configuration objects
- Use enums for related constant groups
const CONFIG = {
MAX_RETRIES: 3,
TIMEOUT: 5000,
API_VERSION: 'v1',
};
const STATUS = {
LOADING: 'loading',
SUCCESS: 'success',
ERROR: 'error',
};
Function Design
- Keep functions small (< 20 lines ideally)
- One function, one responsibility
- Use pure functions when possible (same input → same output)
- Extract complex conditions into descriptive function names
- Use early returns to reduce nesting
// Good
function validateUser(user) {
if (!user) return { valid: false, error: 'User required' };
if (!user.email) return { valid: false, error: 'Email required' };
return { valid: true };
}
Modern JavaScript Features
- Use
constby default,letwhen reassignment needed, avoidvar - Use arrow functions for short operations and callbacks
- Use destructuring for object/array access
- Use template literals instead of string concatenation
- Use optional chaining (
?.) and nullish coalescing (??) - Use array methods (
map,filter,reduce) over traditional loops
State Management (React-inspired)
- Create state management utilities that mimic React hooks
- Keep state updates immutable
- Use computed values instead of storing derived state
// State utility (React useState-like)
function createState(initialValue) {
let value = initialValue;
const listeners = new Set();
const setState = (newValue) => {
const nextValue = typeof newValue === 'function' ? newValue(value) : newValue;
if (nextValue !== value) {
value = nextValue;
listeners.forEach((listener) => listener(value));
}
};
const getState = () => value;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
return [getState, setState, subscribe];
}
Component-like Structure
- Organize UI logic into reusable component functions
- Separate creation, update, and cleanup logic
- Use factory patterns for component instances
function createButton({ text, onClick, className = '' }) {
const element = document.createElement('button');
element.textContent = text;
element.className = `btn ${className}`;
element.addEventListener('click', onClick);
return {
element,
update: ({ text: newText, className: newClassName }) => {
if (newText) element.textContent = newText;
if (newClassName) element.className = `btn ${newClassName}`;
},
destroy: () => {
element.removeEventListener('click', onClick);
element.remove();
},
};
}
Error Handling
- Use try-catch for async operations
- Create custom error types for different scenarios
- Always handle promise rejections
- Provide meaningful error messages
class ValidationError extends Error {
constructor(field, message) {
super(`Validation failed for ${field}: ${message}`);
this.name = 'ValidationError';
this.field = field;
}
}
async function fetchUserData(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
Module Organization
- Use ES6 modules for code organization
- Export only what's necessary (prefer named exports)
- Group related functionality in single files
- Keep modules focused on single responsibility
// userService.js
export const userService = {
async getUser(id) {
/* ... */
},
async updateUser(id, data) {
/* ... */
},
async deleteUser(id) {
/* ... */
},
};
// utils/validation.js
export const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
export const isValidPassword = (password) => password.length >= 8;
Performance Considerations
- Use debouncing/throttling for frequent events
- Implement virtual scrolling for large lists
- Use document fragments for multiple DOM insertions
- Cache expensive computations
- Remove event listeners on cleanup
// Debounce utility
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
Testing Structure
- Write functions that are easy to test (pure functions)
- Separate DOM manipulation from business logic
- Use dependency injection for external services
- Mock external dependencies in tests
// Testable structure
function calculateTotal(items, taxRate = 0.1) {
return items.reduce((sum, item) => sum + item.price, 0) * (1 + taxRate);
}
function updateTotalDisplay(total, element = document.getElementById('total')) {
element.textContent = `$${total.toFixed(2)}`;
}
Refactoring Guidelines
- Make small, incremental changes
- Maintain existing functionality while improving structure
- Extract reusable utilities gradually
- Update tests alongside refactoring
- Use feature flags for major changes
- Review changes thoroughly before merging
Code Review Checklist
- Functions follow single responsibility principle
- Variables and functions have descriptive names
- Magic numbers/strings extracted to constants
- No code duplication
- Error handling implemented
- Performance considerations addressed
- Code is self-documenting
- Separation of concerns maintained
- Tests are comprehensive and pass
- Refactoring preserves existing functionality
Anti-Patterns to Avoid
- ❌ Deeply nested conditions (max 3 levels)
- ❌ Functions longer than 20 lines
- ❌ Global state mutations
- ❌ Mixing DOM manipulation with business logic
- ❌ Using
varor implicit globals - ❌ Callback hell (use async/await)
- ❌ Modifying function parameters directly
- ❌ Ignoring error cases
AI를 활용한 리팩토링 과정에서 느낀 점
- 컨텍스트 처리 한계 한 번에 전체 파일(800줄 이상)을 처리하도록 요청했을 때 일관성 있는 결과를 얻기 어려웠습니다. AI가 앞부분에서 적용한 패턴을 뒷부분에서 잊어버리거나 다른 방식으로 처리하는 경우가 빈번했습니다. 복잡한 상호 의존성을 가진 함수들을 동시에 리팩토링할 때 예상치 못한 사이드 이펙트가 발생하기도 했습니다.
// ❌ 비효과적인 요청
"전체 main() 함수를 리팩토링해주세요"
// ✅ 효과적인 요청
"calculateTotal 함수만 순수 함수로 변환해주세요"
"DOM 생성 로직만 별도 함수로 분리해주세요"
-
중복 로직 생성 문제 전체 프로젝트 구조를 설명하고 기존 유틸리티 함수들을 알려줬음에도 불구하고, 비슷한 기능을 하는 새로운 함수를 만들어내는 경우가 있었습니다. 예를 들어, 이미 formatPrice 함수가 있는데 priceFormatter라는 유사한 함수를 추가로 생성하는 식이었습니다.
-
과도한 추상화 경향 간단한 기능에 대해서도 불필요하게 복잡한 디자인 패턴을 적용하려는 경향이 있었습니다. 예를 들어, 단순한 함수에서 과한 에러처리를 작성하거나 불필요하게 함수를 분리하기도 했습니다.
-
기능 유지의 어려움 "기존 기능을 유지하면서 리팩토링해달라"고 명확히 지시했음에도 불구하고 미묘한 동작 방식이 변경되는 경우가 있었습니다. 작업이 하나 끝나면 테스트를 진행하여 일관성을 확인했습니다.
방대한 작업을 한번에 입력하면 성능이 크게 떨어지기때문에 우선순위를 먼저 분석한후 작은 범위부터 시작해 처리해달라고 명령하였습니다. 한번에 너무 많은 작업을 처리하지 말것, 한 스텝씩 점진적으로 진행할것, 기능 단위로 작업할 것그리고 전체적인 리팩토링이 끝나도 마무리 하는게 아닌, 지침을 활용해서 반복적으로 검토하는 작업을 걸쳤습니다.. 이러한 경험을 통해 AI는 막연한 더티코드를 리팩토링할때는 지침을 주는 강력한 도구이지만, 적절한 가이드와 인간의 판단이 결합되어야 최선의 결과를 얻을 수 있다는 것을 깨달았습니다.
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
(핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?
이번 과제에서 가장 중요하게 생각한 부분은 기존 기능을 유지하면서 점진적으로 리팩토링하는 것이었습니다. 한 번에 전체 코드를 바꾸기보다는 작고 명확한 단위로 나누어 개선하는 방식으로 접근했습니다.
각 기능 단위별로 커밋하며 상세 작업내용 기재 ex) ♻️ refactor: 상품 ID 상수 명명 규칙 통일 -일관성 없는 상수 명명 방식 표준화 -PRODUCT_ONE, p2, product_3 → PRODUCT_KEYBOARD, PRODUCT_MOUSE 형태로 변경 -모든 상품 ID 상수를 SCREAMING_SNAKE_CASE로 통일
- 기능 단위로 리팩토링: 하나의 함수나 기능 단위로 나누어 순차적으로 개선하였습니다.
- 단계별 테스트 실행: 리팩토링이 끝날 때마다 테스트를 실행하여 기존 기능이 잘 유지되는지 확인했습니다.
- 의미 있는 커밋 단위 유지: 변경 사항을 명확하게 추적할 수 있도록, 커밋을 작고 의미 있는 단위로 나누어 관리했습니다.
- 기존 기능 보존에 집중: 기능이 깨지지 않도록 수시로 확인하며 신중하게 작업을 진행했습니다.
리팩토링 우선순위 지정
무작정 고치는 것이 아니라 리스크가 낮은 부분부터 점진적으로 복잡한 영역까지 체계적으로 우선순위를 정한 뒤 단계적으로 리팩토링을 진행했습니다.
- 사용되지 않는 변수, 함수 먼저 정리(eslint 설정 후 no-unused-vars 에 걸리는 부분부터 정리)
// 사용되지 않는 변수들
let initStock = 0; // 선언만 하고 실제로는 사용되지 않음
for (let i = 0; i < prodList.length; i++) {
initStock += prodList[i].q;
}
- 한 파일 내에서 줄바꿈과 간단한 주석으로 관심사를 구분
//❌ Before
function main() {
// 모든 로직이 하나의 함수에 섞여있음
var root;
var header;
...
}
//✅ After
function main() {
...
// DOM 구조 조립
selectorContainer.appendChild(sel);
selectorContainer.appendChild(addBtn);
selectorContainer.appendChild(stockInfo);
leftColumn.appendChild(selectorContainer);
// 왼쪽 컬럼 생성
leftColumn = document.createElement('div');
leftColumn['className'] = 'bg-white border border-gray-200 p-8 overflow-y-auto';
// 카트 디스플레이 생성
cartDisp = document.createElement('div');
leftColumn.appendChild(cartDisp);
cartDisp.id = 'cart-items';
}
- var → let 또는 const로 수정하여 블록 스코프 적용
var root = document.getElementById('app'); //❌ Before
const root = document.getElementById('app'); //✅ After
- 상수로 선언된 값들의 변수명을 의미가 명확하고 일관된 규칙으로 정리
//❌ Before
var PRODUCT_ONE = 'p1'
var p2 = 'p2'
var product_3 = 'p3'
var p4 = "p4"
var PRODUCT_5 = `p5`
//✅ After
/**상품 ID */
export const PRODUCT_IDS = {
KEYBOARD: 'p1',
MOUSE: 'p2',
MONITOR_ARM: 'p3',
POUCH: 'p4',
SPEAKER: 'p5',
};
- 연관된 기능들을 각각의 독립적인 모듈로 분리
// 기존: 787줄의 단일 파일에 모든 기능이 섞여있음
function main() {
// DOM 생성
// 이벤트 처리
// 할인 계산
// 포인트 계산
// UI 업데이트
// 타이머 관리
// ... 모든 기능이 하나의 함수에
}
//리팩토링
src/basic/
├── hooks/ # 커스텀 훅 패턴
│ ├── useAppInitialization.js
│ ├── useCart.js
│ └── useServiceInitialization.js
├── services/ # 비즈니스 로직
│ ├── DiscountCalculator.js
│ ├── CartEventHandler.js
│ └── TimerService.js
├── state/ # 상태 관리
│ └── appState.js
├── components/ # UI 컴포넌트
│ ├── cart/
│ ├── layout/
│ └── order/
└── utils/ # 유틸리티
├── discountUtils.js
└── formatUtils.js
- 반복되는 로직을 재사용 가능하게 정리
//❌ Before
// 포인트 계산이 여러 곳에서 중복
var pts = Math.floor(totalAmt/1000);
if(new Date().getDay() === 2) pts *= 2;
// 또 다른 곳에서
var basePoints = Math.floor(totalAmt/1000)
if(new Date().getDay() === 2) finalPoints *= 2;
// 또 다른 곳에서
if(new Date().getDay() === 2) { }
- 전역 변수를 지역 변수로 변경하여 부수 효과(side effects) 최소화
```js
//✅ After(src/basic/utils/pointUtils.js)
//** 화요일 체크 */
export const isTuesday = () => new Date().getDay() === DAYS_OF_WEEK.TUESDAY;
/** 기본 포인트 계산 */
const calculateBasePoints = (totalAmount) => {
return Math.floor(totalAmount * POINTS_POLICY.BASE_RATE);
};
/** 총 포인트 계산 */
export const calculateTotalPoints = (cartItems, productList, totalAmount, itemCount) => {
const basePoints = calculateBasePoints(totalAmount);
let finalPoints = basePoints;
// 화요일 2배 보너스
if (isTuesday()) {
finalPoints = basePoints * POINTS_POLICY.TUESDAY_MULTIPLIER;
}
return { totalPoints: finalPoints, pointsDetails };
};
- 전역 변수를 지역 변수로 변경하여 부수 효과(side effects) 최소화
//❌ Before
var totalAmount = 0; // 전역 변수
function calculateTotal() {
totalAmount = 0; // 전역 변수 직접 수정
// ...
//✅ After
// src/basic/state/appState.js
const [getState, setState] = createState();
export function updateTotalAmount(newAmount) {
setState
- 매직 넘버와 하드코딩된 문자열을 의미 있는 상수로 분리
//❌ Before
if(q >= 10) {
if(curItem.id === PRODUCT_ONE) {
disc = 10 / 100;
} else if(curItem.id === p2) {
disc = 15 / 100;
} else if(curItem.id === product_3) {
disc = 20 / 100;
}
}
if(itemCnt >= 30) {
finalPoints = finalPoints + 100;
} else if(itemCnt >= 20) {
finalPoints = finalPoints + 50;
} else if(itemCnt >= 10) {
finalPoints = finalPoints + 20;
}
//✅ After
/** 포인트 정책*/
export const POINTS_POLICY = {
BASE_RATE: 0.001, // 1000원당 1포인트
TUESDAY_MULTIPLIER: 2,
SET_BONUS: {
KEYBOARD_MOUSE: 50,
FULL_SET: 100,
},
BULK_PURCHASE_BONUS: {
SMALL: { minQuantity: 10, points: 20 },
MEDIUM: { minQuantity: 20, points: 50 },
LARGE: { minQuantity: 30, points: 100 },
},
};
- 중복된 코드나 과도하게 긴 함수는 기능 단위로 세분화하여 단일 책임 원칙 적용
//❌ Before
// 209줄의 거대한 함수
function calcCart() {
// 1. 초기화 (52줄)
// 2. 재고 체크 (20줄)
// 3. 가격 계산 (50줄)
// 4. 할인 적용 (40줄)
// 5. 포인트 계산 (30줄)
// 6. UI 업데이트 (17줄)
}
//✅ After
// src/basic/services/DiscountCalculator.js
const calculateItemDiscount = (productId, quantity) => {
if (quantity < DISCOUNT_THRESHOLDS.ITEM) return 0;
return DISCOUNT_RATES[productId] ?? 0;
};
const calculateBulkDiscount = (totalCount) => {
return totalCount >= DISCOUNT_THRESHOLDS.BULK ? 0.25 : 0;
};
const calculateTotalDiscount = (cartItems, products) => {
// 각각의 작은 함수들을 조합
};
- 명령형 for 문을 선언형 배열 메서드(map, filter, find, reduce 등)로 변환하여 함수형 프로그래밍 패러다임 적용
//❌ Before
// 명령형 for 문
function calculateTotal() {
totalAmt = 0; // 전역 변수 직접 수정
for (let i = 0; i < prodList.length; i++) { // 전역 변수 사용
totalAmt += prodList[i].val;
}
}
//✅ After
export const calculateTotal = (products) => {
return products.reduce((total, product) => total + product.price, 0);
};
- 이해를 돕기 위한 주석 추가와 가독성, 논리적 흐름을 돕는 줄바꿈 적용 마지막으로 전체적인 코드를 점검할때는 읽는 사람이 읽기 쉬운지, 가독성이 어떤지를 고려하며 주석을 작성하였습니다.
//❌ Before
// 복잡한 로직에 대한 설명 부재
discRate = discRate + 0.1 * (1 - discRate);
/**
* 할인율 계산
* @param {number} baseRate - 기본 할인율
* @param {number} additionalRate - 추가 할인율
* @returns {number} 최종 할인율
*/
const calculateDiscountRate = (baseRate, additionalRate) => {
// 중복 할인 방지를 위한 계산
return baseRate + additionalRate * (1 - baseRate);
};
이러한 단계별 접근으로 점진적인 리팩토링을 진행할 수 있었습니다.
마이그레이션을 염두에 둔 리팩토링 방향
이후 advanced 단계에서 React로 마이그레이션할 것을 고려하여 리액트와 친화적인 구조가 되도록 리팩토링하는게 신경을 썼습니다.
- 함수형 프로그래밍 적용: 기존에 클래스 형태나 절차적으로 작성된 함수들을 순수 함수(Pure Function) 형태로 전환
- Props 중심의 데이터 흐름 설계: 전역 변수에 의존하는 바닐라 JS 방식 대신, 함수 매개변수를 통해 데이터를 주고받는 형태로 리팩토링하여 React의 props 개념과 유사한 구조로 리팩토링
- 관심사 분리를 통한 컴포넌트화 준비: UI 로직과 비즈니스 로직을 명확히 분리하여 React 컴포넌트로의 전환 시 재작성 부담을 최소화하는 방향으로 구조화
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
변수명 설계의 중요성
리팩토링을 시작할 때 구조 분리에만 집중하고 변수명에 대해서는 충분히 고민하지 않았던 것이 가장 큰 실수였습니다. 전체 구조를 다시 살펴보는 과정에서 변수명이 모호하다는 점을 뒤늦게 깨달았고 결국 하나하나 수정해야 했습니다. 특히 클래스명이나 아이디가 컴포넌트명이나 변수명에 많이 의존하고 있었기 때문에 하나의 네이밍을 수정하면 연관된 모든 부분을 찾아서 함께 바꿔야 했습니다. 이 작업에서 변수명을 변경 > 테스트 진행 > 변경 > 테스트 진행하며 제대로 변경되었는지 확인하느라 예상보다 많은 시간이 걸렸습니다...
한 파일로 구성되어 있던 초기 상태에서 네이밍 규칙을 정하고 구조를 조금 더 체계적으로 정리해뒀더라면 이후 모듈 분리나 import 작업이 훨씬 수월했을 것이라 생각합니다. 급하게 파일을 나누고 리팩토링을 시작하기보다는 전체 구조를 먼저 설계하고 정리한 뒤 작업을 진행하는 것이 오히려 더 효율적인 접근법이라는 점을 이번 경험을 통해 다시 한 번 느꼈습니다.
시간 관리
basic에 시간을 많이 투자하게 되면서 advanced 단계 구현에 충분한 시간을 투자하지 못한 점도 아쉽습니다. 지금보다 함수 로직을 더 간결하게 리팩토링하고 반복되는 패턴을 체계적으로 분석하여 개선할 수 있었을 것 같은데..구현에 급급하여 최종 검토 부분이 미흡한거 같습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
- 추천하시는 ESLint, Prettier 설정, 권장하는 컨벤션이 있으신지 궁금합니다.
- 레거시 코드를 리팩토링할 때 어떤 순서로 접근하는 것을 추천하시나요? 제가 적용한 우선순위(사용되지 않는 변수 정리 → 매직 넘버 제거 → 함수 분리 → 모듈화)가 적절한지 혹은 더 체계적이고 안전한 접근법이 있는지 조언을 구하고 싶습니다.
과제 피드백
오 과제를 아주 알차게 진행해주셨네요! 여러가지 시도도 많이 하신것도 보이고 그 과정에서 저도 인사이트를 얻을 수 있었네요. 다른 분들도 많이 참고가 될 수 있는 자료가 되지 않을까 싶습니다 :+1 과제 부분에 있어서도 크게 피드백을 드릴게 없네요 ㅎㅎ 관련해서 테스트도 작성해주셨고요. 테스트에 대한 방식이나 작성방식은 개선이 필요한 부분도 있지만 지금 단계에서는 충분하지 않을까! 싶습니다 ㅎㅎ
다만, 이미 아시는것처럼 아직 코드 내에 있는 복잡한 로직들이나 분리가 가능한 부분도 조금은 남아있어 보여요. 클래스 이름을 정하는 로직이라거나, 조건에 맞는 텍스트를 보여주는 부분도 분리가 가능할 것 같구요 ㅎㅎ 지금 refactor 가이드라고 되어있는 룰도 추후에 코드를 생성하는데 사용할 수 있도록 만들 수 있을 것 같아서 이 부분도 수정해서 재사용하시면 좋을 것 같습니다.
추천하시는 ESLint, Prettier 설정, 권장하는 컨벤션이 있으신지 궁금합니다.
따로 추천드리고 싶은 규칙은 대신 방향은 있는 것 같아요 ㅎㅎ 느슨한 방향에서 빡빡한 규칙을 생성하는 방향으로 나아가는게 좋을것 같아요. 너무 처음부터 지키기 어려운 규칙을 적용하고 규칙을 풀어나가는 방향은 좋은 접근 법은 아닌것 같더라구요. 지금 단계에서 프로젝트에 필요한 명확한 규칙을 적용하고 점점더 안티패턴을 작성하지 않는 방향으로 필요한 부분들을 채워나가는 방향으로 운영하는게 좋아보여요.
레거시 코드를 리팩토링할 때 어떤 순서로 접근하는 것을 추천하시나요? 제가 적용한 우선순위(사용되지 않는 변수 정리 → 매직 넘버 제거 → 함수 분리 → 모듈화)가 적절한지 혹은 더 체계적이고 안전한 접근법이 있는지 조언을 구하고 싶습니다.
코드, 그리고 모듈간에 영향범위가 적은 부분부터 수정하려고 하는 것 같아요. 말씀해주신 우선순위는 사실 그런 관점에서 좋은 접근이죠 ㅎㅎ 중요한건 이번 과제를 진행해주시면서 하셨던 것처럼 작은단위로 작업을 나눠 진행하고 반복하는것 같아요. 이부분만 유념하시면 어떤 관점이든 순서든 상관없을 것 같아요!
고생하셨고 다음주도 화이팅입니다~