Skip to content

[2팀 정도은] Chapter 2-1. 클린코드와 리팩토링#35

Open
nemobim wants to merge 164 commits intohanghae-plus:mainfrom
nemobim:main
Open

[2팀 정도은] Chapter 2-1. 클린코드와 리팩토링#35
nemobim wants to merge 164 commits intohanghae-plus:mainfrom
nemobim:main

Conversation

@nemobim
Copy link

@nemobim nemobim commented Jul 28, 2025

과제 체크포인트

배포링크: 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: handleUserClick not onClick
  • 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 const by default, let when reassignment needed, avoid var
  • 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 var or 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로 통일

  • 기능 단위로 리팩토링: 하나의 함수나 기능 단위로 나누어 순차적으로 개선하였습니다.
  • 단계별 테스트 실행: 리팩토링이 끝날 때마다 테스트를 실행하여 기존 기능이 잘 유지되는지 확인했습니다.
  • 의미 있는 커밋 단위 유지: 변경 사항을 명확하게 추적할 수 있도록, 커밋을 작고 의미 있는 단위로 나누어 관리했습니다.
  • 기존 기능 보존에 집중: 기능이 깨지지 않도록 수시로 확인하며 신중하게 작업을 진행했습니다.

리팩토링 우선순위 지정

무작정 고치는 것이 아니라 리스크가 낮은 부분부터 점진적으로 복잡한 영역까지 체계적으로 우선순위를 정한 뒤 단계적으로 리팩토링을 진행했습니다.

  1. 사용되지 않는 변수, 함수 먼저 정리(eslint 설정 후 no-unused-vars 에 걸리는 부분부터 정리)
// 사용되지 않는 변수들
 let initStock = 0; // 선언만 하고 실제로는 사용되지 않음
  for (let i = 0; i < prodList.length; i++) {
    initStock += prodList[i].q;
  }
  1. 한 파일 내에서 줄바꿈과 간단한 주석으로 관심사를 구분
//❌ 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';
}
  1. var → let 또는 const로 수정하여 블록 스코프 적용
 var root = document.getElementById('app'); //❌ Before
 const root = document.getElementById('app'); //✅ After
  1. 상수로 선언된 값들의 변수명을 의미가 명확하고 일관된 규칙으로 정리
//❌ 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',
};
  1. 연관된 기능들을 각각의 독립적인 모듈로 분리
// 기존: 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
  1. 반복되는 로직을 재사용 가능하게 정리
//❌ 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) { }
//✅ 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 };
};
  1. 전역 변수를 지역 변수로 변경하여 부수 효과(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
  1. 매직 넘버와 하드코딩된 문자열을 의미 있는 상수로 분리
//❌ 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 },
  },
};
  1. 중복된 코드나 과도하게 긴 함수는 기능 단위로 세분화하여 단일 책임 원칙 적용
//❌ 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) => {
  // 각각의 작은 함수들을 조합
};
  1. 명령형 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);
};
  1. 이해를 돕기 위한 주석 추가와 가독성, 논리적 흐름을 돕는 줄바꿈 적용
    마지막으로 전체적인 코드를 점검할때는 읽는 사람이 읽기 쉬운지, 가독성이 어떤지를 고려하며 주석을 작성하였습니다.
//❌ 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 설정, 권장하는 컨벤션이 있으신지 궁금합니다.
  • 레거시 코드를 리팩토링할 때 어떤 순서로 접근하는 것을 추천하시나요? 제가 적용한 우선순위(사용되지 않는 변수 정리 → 매직 넘버 제거 → 함수 분리 → 모듈화)가 적절한지 혹은 더 체계적이고 안전한 접근법이 있는지 조언을 구하고 싶습니다.

nemobim added 3 commits July 27, 2025 21:39
제거 후 테스트 진행 > 통과
- 점진적 리팩토링을 위한 관심사 분리 준비
- 기존 코드 유지하며 구조 파악 및 개선 방향 설정
nemobim added 26 commits July 29, 2025 01:33
- 'var' 선언을 'const' 또는 'let'으로 변경하여 스코프 명확화
- 미사용 변수 제거 및 주석 처리
일관성 없는 상수 명명 방식 표준화
- PRODUCT_ONE, p2, product_3 → PRODUCT_KEYBOARD, PRODUCT_MOUSE 형태로 변경
- 모든 상품 ID 상수를 SCREAMING_SNAKE_CASE로 통일
- 상수명에 의미있는 이름 적용하여 가독성 향상
- 제품 ID와 제품 목록을 main.basic.js에서 data/products.js로 이동
- main.basic.js에서 새 모듈의 제품 상수들을 import하도록 수정
- 헤더 생성 로직을 Header.js의 재사용 가능한 createHeader 컴포넌트로 이동
- 그리드 컨테이너 생성 로직을 createGridContainer 컴포넌트로 추출
- main.basic.js에서 새로운 컴포넌트를 사용하도록 수정
- 좌측 컬럼 UI 생성 로직을 LeftColumn.js의 createLeftColumn 컴포넌트로 추출
- 주문 요약 UI를 main.basic.js에서 재사용 가능한 RightColumn 컴포넌트로 추출
매뉴얼 오버레이 UI와 로직을 main.basic.js에서 ManualOverlay.js 컴포넌트로 추출
- 상품 선택 및 옵션 업데이트 로직을 ProductSelector.js 컴포넌트로 이동
장바구니 아이템 DOM 생성 및 가격 업데이트 로직을 CartItem 컴포넌트로 추출
- main.basic.js에서 createCartItem, updateCartItemPrice 함수 사용하도록 수정
AddToCartButton과 StockInfo를 별도 컴포넌트 모듈로 추출
장바구니 디스플레이 관련 함수들을 새로운 CartDisplay 컴포넌트로 추출
- main.basic.js에서 CartDisplay API를 사용하여 장바구니 아이템 추가/제거/조회
사용하지 않는 createCartItem import 및 선택기 컨테이너 관련 코드 제거
- RightColumn에서 SummaryDetails 컴포넌트 사용하도록 수정
- main.basic.js에서 updateSummaryDetails 함수로 처리
- RightColumn과 main.basic.js에서 새로운 컴포넌트를 사용하도록 수정
- 기존의 인라인 계산 및 렌더링 로직 제거
- RightColumn.js에서 DiscountInfo 컴포넌트를 사용하도록 변경
- main.basic.js에서 updateDiscountInfo 함수 호출 방식으로 리팩토링
실시간 계산으로 인해 발생하는 테스트 오류를 방지하기 위해
useFakeTimers 대신 setSystemTime을 사용하여 날짜를 고정함
- main.basic.js에서 bonusPts 관련 전역 변수 및 로직 제거
- CartDisplay에서 사용되지 않는 hasCartItems import 제거
♻️ refactor(cart): CartTotal 컴포넌트로 장바구니 합계 로직 분리
main.basic.js에서 사용되지 않는 주석, 변수, 함수 코드를 제거
Header.js의 상품 개수 표시 로직을 ItemCount.js로 분리
'header' → 'Header'로 import 경로를 수정함
main.basic.js의 타이머 기반 추천/할인 로직을 TimerService 클래스로 이동하여
책임 분리 및 유지보수성을 개선
더 이상 사용되지 않는 lastSel 변수와 관련 할당 코드를 main.basic.js에서 제거
장바구니 아이템 가격 갱신 로직을 CartPriceUpdater 서비스로 이동하여
관심사 분리 및 유지보수성을 개선

- 제품 정보 조회 및 가격 갱신 로직을 서비스 내부로 캡슐화
- main.basic.js는 CartPriceUpdater만 호출하도록 단순화
nemobim added 28 commits August 1, 2025 05:18
- CartProvider와 useCartContext 생성
- 장바구니와 제품 상태 전역 공유
- Context API를 통한 상태 관리
- 전체 앱을 CartProvider로 감싸기
- 전역 상태 관리 활성화
- 컴포넌트 간 상태 공유 가능
- 개별 hooks 대신 통합 context 사용
- 상태 공유 문제 해결
- 장바구니 추가 기능 활성화
- 개별 hooks 대신 통합 context 사용
- 수량 변경 및 삭제 기능 연결
- 상태 일관성 보장
- Header에 useCartContext 적용으로 실시간 아이템 개수 표시
- ProductPicker에 재고 확인 로직 추가
- 품절 상품 옵션 비활성화 및 시각적 표시
- 재고 부족 경고 메시지 동적 생성
- 장바구니 추가 시 재고 한도 검증
- discountCalculator 유틸리티로 복잡한 할인 로직 분리
- 개별 상품 할인 (10개↑), 대량 구매 할인 (30개↑) 적용
- 화요일 추가 할인 로직 구현
- OrderSummary 실시간 업데이트 (장바구니 아이템, 할인, 포인트)
- 할인 상세 정보 표시 및 총 할인율 계산
- useDiscountTimers hook으로 자동 할인 시스템 구현
- 번개세일 (30초마다, 20% 할인) 및 추천할인 (60초마다, 5% 할인)
- 할인 상태별 시각적 구분 (⚡번개세일, 💝추천할인, ⚡💝슈퍼세일)
- CartContext에 마지막 선택 상품 추적 기능 추가
- 상품 선택 옵션 및 장바구니에서 할인 표시 구현
- CartContext에서 실제 재고 검증 및 차감 로직 구현
- pointsCalculator로 상세 포인트 적립 로직 분리
- 키보드+마우스 세트, 풀세트, 대량구매 보너스 구현
- 화요일 포인트 2배 적용 및 포인트 상세 표시
- 재고 부족 시 select 테두리 색상 변경 (orange)
- discountCalculator에서 번개세일/추천할인 반영한 할인 계산
- ShoppingCart에서 할인 상품 시각적 표시 완성
- useProducts에 decreaseStock, increaseStock 함수 export
- CartContext에서 장바구니 추가/수량변경/삭제 시 실제 재고 차감/복구
- 수량 증가 시 재고 부족 검증 및 차감
- 수량 감소/삭제 시 재고 복구 로직
- 장바구니에서 수량 10개 이상인 상품명 font-bold 적용
- 가격 표시도 동일하게 굵은 글씨 처리
- PRD 요구사항: "할인된 상품은 굵은 글씨로 표시" 구현
- 번개세일: 재고 있는 모든 상품 중 랜덤 선택 (originalPrice 기준 20% 할인)
- 추천할인: 현재 price 기준 5% 할인으로 누적 할인 구현
- 마지막 선택 상품과 다른 상품만 추천하도록 유지
- 올바른 할인 로직으로 PRD 요구사항 준수
- 재고 5개 미만 상품 빨간색 텍스트 표시
- 재고 부족 시 ⚠️ 아이콘, 품절 시 🚫 아이콘 추가
- ProductPicker 옵션에서도 재고 상태 시각적 구분
- 장바구니에서 재고 수량 정보 표시
- PRD 요구사항: 재고 부족 시각적 경고 구현
- 재고 경고 아이콘(⚠️)을 최우선으로 앞에 배치
- 재고 부족 시 font-medium으로 추가 강조
- 아이콘 표시 순서 정리 (재고 → 할인 → 상품명)
- 실제 재고 차감을 반영하여 product.stock 기준으로 계산
- getTotalStock에서 중복 계산 제거 (실제 남은 재고만)
- 품절 조건을 실제 재고 0 기준으로 변경
- handleAddToCart 로직 단순화
- 재고 메시지도 실제 재고 기준으로 표시
- 번개세일(⚡), 추천할인(💝), 슈퍼세일(⚡💝) 할인 내역 추가
- 상품별 번개세일/추천할인 적용 시 할인율 계산 및 표시
- 할인 내역에 아이콘과 상품명으로 명확한 구분
- originalPrice 대비 실제 할인율 정확히 계산
- ShoppingGuide에 onClose props 추가
- X 버튼 클릭으로 가이드 닫기
- 오버레이 배경 클릭으로 가이드 닫기
- PRD 요구사항: "배경 클릭 또는 X 버튼으로 닫기" 구현
- setInterval을 재귀적 setTimeout으로 변경
- 원본 바닐라 JS와 동일한 타이머 로직 적용
- 한 번 실행 후 다음 타이머 설정하는 방식으로 수정
- 무한 alert 스팸 문제 해결
- eslint-disable no-unused-vars 주석 추가로 타입 정의 린터 에러 해결
- 함수 타입 정의에서 parameter 이름 사용하지 않음 경고 무시
- CartItem 컴포넌트를 별도 파일로 분리
- 복잡한 렌더링 로직을 작은 함수들로 분해
- ShoppingCart 컴포넌트 크기를 127줄에서 40줄로 단축
- 단일 책임 원칙 적용으로 가독성과 유지보수성 향상
- 'no-unused-vars' 룰을 off 처리
- '@typescript-eslint/no-unused-vars'를 error로 설정하여 TypeScript 기준으로 검사
- 타입 시스템에 맞는 불필요한 변수 감지 정확도 향상
…e 함수로 로직 분리

- 장바구니 로직의 책임을 명확히 분리
- 상품 및 카트 아이템 조회 로직을 findProductAndCartItem 함수로 추출
- 수량 증가/감소 로직을 handleStockIncrease, handleStockDecrease로 분리
- 가독성 및 재사용성 향상을 위한 구조 개선
- calculateOriginalTotal: 원가 총합 계산
- calculateDiscountedPriceTotal: 할인 적용 총합 계산
- calculateSaleDiscounts: 번개세일/추천상품 할인 총액 계산
- getItemDiscountRate: 상품별 개별 할인율 추출
- calculateItemDiscounts: 수량 기반 개별 할인 계산
- calculateBulkDiscount: 30개 이상 대량 구매 할인 계산
- calculateTuesdayDiscount: 화요일 할인 계산
- 각 함수는 단일 책임 원칙을 따르며 테스트와 재사용이 쉬운 구조로 개선
- src/advanced/types/index.ts 생성하여 타입 정의 중앙화
- Product, CartItem, DiscountInfo, DiscountResult, PointsResult 등 핵심 타입 통합
- CartItemProps, ShoppingGuideProps, UseDiscountTimersProps 등 컴포넌트 Props 타입 정의 이동
- 각 파일에서 중앙 타입 모듈을 import하도록 수정
- 타입 중복 제거 및 일관성 있는 관리 구조로 유지보수성 향상
- 114줄짜리 OrderSummary 컴포넌트를 4개의 작은 컴포넌트로 분리
- OrderItem: 개별 주문 아이템 렌더링
- DiscountSummary: 할인 요약 정보 표시
- PointsDisplay: 포인트 적립 정보 표시
- TuesdayBanner: 화요일 특별 할인 배너
- OrderSummary 본체는 65줄로 간결화
- 단일 책임 원칙(SRP)을 적용해 각 컴포넌트가 명확한 역할만 담당
- 가독성, 재사용성, 테스트 용이성 향상
- calculateDiscount 함수 테스트 추가 (빈 장바구니, 개별 할인, 대량 할인)
- calculatePoints 함수 테스트 추가 (빈 장바구니, 기본 포인트, 세트 보너스, 대량 보너스)
- PRODUCT_LIST 데이터 무결성 테스트 추가
- 세일 로직 특정 테스트 추가 (슈퍼 세일, 화요일 할인)
- 모든 핵심 비즈니스 로직이 적절히 테스트되고 검증되도록 보장
- gh-pages 패키지 추가로 GitHub Pages 자동 배포 가능
- 프로덕션 환경에 맞춘 base path 설정 완료
- 빌드 최적화 및 HTML 파일명 처리 로직 적용
- 배포 스크립트(pnpm gh-pages) 추
@heojungseok
Copy link

도은님의 클린코드 룰 덕에 덕 좀 봤습니다 감사합니다

@ckdwns9121
Copy link
Member

ckdwns9121 commented Aug 1, 2025

저도 도은님 룰 너무 잘썻어요👍👍👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants