[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
---
|
||||
id: quality-refactoring
|
||||
title: Refactoring — 작은 step / 안전 변경
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [quality, refactoring, vibe-coding]
|
||||
tech_stack: { language: "Various", applicable_to: ["Engineering"] }
|
||||
applied_in: []
|
||||
aliases: [refactoring, extract function, rename, move, code smell, inline, parallel change]
|
||||
---
|
||||
|
||||
# Refactoring
|
||||
|
||||
> **작은 reversible step**. 매 step 후 test. Behavior 변경 X — 단지 structure. Martin Fowler 의 카탈로그 + IDE refactoring tools.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Code smell: 변경 신호.
|
||||
- Extract / Inline / Move / Rename.
|
||||
- Test first — 안전망.
|
||||
- Parallel change — 큰 변경 점진.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### 가장 일반 refactoring
|
||||
```
|
||||
1. Extract function/method: 긴 function → 작은 function
|
||||
2. Inline: 과도한 indirection 제거
|
||||
3. Rename: 의미 명확
|
||||
4. Move: 적절 위치
|
||||
5. Extract variable: 복잡 expression 명명
|
||||
6. Replace conditional with polymorphism
|
||||
7. Replace magic number with constant
|
||||
8. Encapsulate field
|
||||
```
|
||||
|
||||
### Extract function (가장 자주)
|
||||
```ts
|
||||
// Before
|
||||
function processOrder(order: Order) {
|
||||
// Calculate tax (10 lines)
|
||||
let tax = 0;
|
||||
for (const item of order.items) {
|
||||
tax += item.price * 0.08;
|
||||
}
|
||||
|
||||
// Calculate shipping (15 lines)
|
||||
let shipping = 0;
|
||||
if (order.weight > 10) shipping = 20;
|
||||
else if (order.weight > 5) shipping = 10;
|
||||
else shipping = 5;
|
||||
|
||||
// Apply discount (8 lines)
|
||||
// ...
|
||||
|
||||
return order.subtotal + tax + shipping - discount;
|
||||
}
|
||||
|
||||
// After
|
||||
function processOrder(order: Order) {
|
||||
const tax = calculateTax(order.items);
|
||||
const shipping = calculateShipping(order.weight);
|
||||
const discount = applyDiscount(order);
|
||||
return order.subtotal + tax + shipping - discount;
|
||||
}
|
||||
|
||||
function calculateTax(items: Item[]): number {
|
||||
return items.reduce((sum, i) => sum + i.price * 0.08, 0);
|
||||
}
|
||||
|
||||
function calculateShipping(weight: number): number {
|
||||
if (weight > 10) return 20;
|
||||
if (weight > 5) return 10;
|
||||
return 5;
|
||||
}
|
||||
```
|
||||
|
||||
→ 의미 명명 + test 가능.
|
||||
|
||||
### Replace conditional with polymorphism
|
||||
```ts
|
||||
// Before
|
||||
class Animal {
|
||||
sound(): string {
|
||||
if (this.type === 'dog') return 'Woof';
|
||||
if (this.type === 'cat') return 'Meow';
|
||||
if (this.type === 'cow') return 'Moo';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// After
|
||||
abstract class Animal {
|
||||
abstract sound(): string;
|
||||
}
|
||||
class Dog extends Animal { sound() { return 'Woof'; } }
|
||||
class Cat extends Animal { sound() { return 'Meow'; } }
|
||||
class Cow extends Animal { sound() { return 'Moo'; } }
|
||||
```
|
||||
|
||||
또는 lookup table:
|
||||
```ts
|
||||
const sounds: Record<AnimalType, string> = {
|
||||
dog: 'Woof', cat: 'Meow', cow: 'Moo',
|
||||
};
|
||||
function sound(type: AnimalType): string {
|
||||
return sounds[type];
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel change (큰 변경)
|
||||
```
|
||||
3 phase:
|
||||
1. Expand: New 가 추가됨 (old 도 keep)
|
||||
2. Migrate: 사용자가 new 로 이동
|
||||
3. Contract: Old 제거
|
||||
|
||||
각 phase = deployable.
|
||||
```
|
||||
|
||||
```ts
|
||||
// Phase 1: Add new method
|
||||
class API {
|
||||
oldMethod(x: number) { return x * 2; }
|
||||
newMethod(x: number, y: number) { return x * 2 + y; }
|
||||
}
|
||||
|
||||
// Phase 2: Migrate callers (PR by PR)
|
||||
api.oldMethod(5) → api.newMethod(5, 0)
|
||||
|
||||
// Phase 3: Remove old method
|
||||
class API {
|
||||
newMethod(x: number, y: number) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
→ Big bang 보다 안전.
|
||||
|
||||
### Strangler fig
|
||||
```
|
||||
큰 system 의 일부 점진 교체:
|
||||
|
||||
Phase 1: Old system 그대로.
|
||||
Phase 2: New system + facade — 일부 traffic.
|
||||
Phase 3: 점진 traffic 이동.
|
||||
Phase 4: Old system 제거.
|
||||
```
|
||||
|
||||
```ts
|
||||
// Facade
|
||||
class OrderService {
|
||||
async create(order: Order) {
|
||||
if (featureFlag('use-new-system')) {
|
||||
return newSystem.create(order);
|
||||
}
|
||||
return oldSystem.create(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Code smells (refactor 신호)
|
||||
```
|
||||
1. Long method (50+ lines): extract function
|
||||
2. Large class (500+ lines): extract / split
|
||||
3. Long parameter list (5+): parameter object
|
||||
4. Duplicate code: extract + reuse
|
||||
5. Switch statements: polymorphism / lookup
|
||||
6. Feature envy (다른 class 의 데이터): move method
|
||||
7. Data clumps (같은 그룹 매번): data class
|
||||
8. Primitive obsession: value object
|
||||
9. Comments explaining what: extract function (이름 붙임)
|
||||
10. Dead code: delete
|
||||
```
|
||||
|
||||
### Test first
|
||||
```ts
|
||||
// 1. Test 작성 (현재 behavior)
|
||||
test('processOrder calculates correctly', () => {
|
||||
const order = makeOrder(...);
|
||||
expect(processOrder(order)).toBe(95.20);
|
||||
});
|
||||
|
||||
// 2. Refactor
|
||||
function processOrder(order: Order) {
|
||||
const tax = calculateTax(order.items);
|
||||
// ...
|
||||
}
|
||||
|
||||
// 3. Test 통과 = behavior 같음
|
||||
```
|
||||
|
||||
### IDE refactoring (안전)
|
||||
```
|
||||
VS Code / WebStorm:
|
||||
- F2: Rename (모든 reference)
|
||||
- Ctrl+Shift+R: Extract method
|
||||
- Ctrl+Alt+M: Extract variable
|
||||
- Move file (drag)
|
||||
- Auto-import organize
|
||||
|
||||
→ Manual 보다 안전 + 빠름.
|
||||
```
|
||||
|
||||
### TS 의 점진 typing
|
||||
```ts
|
||||
// Phase 1: any 만
|
||||
function process(input: any): any { ... }
|
||||
|
||||
// Phase 2: 입력 type
|
||||
function process(input: Order): any { ... }
|
||||
|
||||
// Phase 3: 출력 type
|
||||
function process(input: Order): Result { ... }
|
||||
|
||||
// Phase 4: 내부 변수 type
|
||||
```
|
||||
|
||||
→ 점진 strict.
|
||||
|
||||
### Inline (과도 indirection 제거)
|
||||
```ts
|
||||
// Before
|
||||
function getEmail(user: User): string {
|
||||
return user.email;
|
||||
}
|
||||
const email = getEmail(user); // 의미 없는 wrapping
|
||||
|
||||
// After
|
||||
const email = user.email;
|
||||
```
|
||||
|
||||
### Rename (semantic)
|
||||
```ts
|
||||
// Before
|
||||
function calc(x: number, y: number) { ... }
|
||||
|
||||
// After
|
||||
function calculateTotalPrice(quantity: number, unitPrice: number) { ... }
|
||||
```
|
||||
|
||||
→ 의미 명확.
|
||||
|
||||
### Extract variable
|
||||
```ts
|
||||
// Before
|
||||
if (order.items.length > 10 && order.totalAmount > 1000 && order.user.tier === 'gold') {
|
||||
// ...
|
||||
}
|
||||
|
||||
// After
|
||||
const isLargeOrder = order.items.length > 10;
|
||||
const isHighValue = order.totalAmount > 1000;
|
||||
const isVipUser = order.user.tier === 'gold';
|
||||
|
||||
if (isLargeOrder && isHighValue && isVipUser) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Encapsulate field
|
||||
```ts
|
||||
// Before
|
||||
class User {
|
||||
email: string; // 누가나 변경 가능
|
||||
}
|
||||
user.email = '...'; // validation 없음
|
||||
|
||||
// After
|
||||
class User {
|
||||
#email: string;
|
||||
|
||||
get email(): string { return this.#email; }
|
||||
|
||||
setEmail(value: string) {
|
||||
if (!isValid(value)) throw new Error('invalid');
|
||||
this.#email = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Replace magic number
|
||||
```ts
|
||||
// Before
|
||||
if (statusCode === 429) { ... }
|
||||
|
||||
// After
|
||||
const TOO_MANY_REQUESTS = 429;
|
||||
if (statusCode === TOO_MANY_REQUESTS) { ... }
|
||||
|
||||
// 또는
|
||||
import { StatusCode } from './http-status';
|
||||
if (statusCode === StatusCode.TooManyRequests) { ... }
|
||||
```
|
||||
|
||||
### Move method to better class
|
||||
```ts
|
||||
// Before — Order class 가 customer 데이터 사용
|
||||
class Order {
|
||||
getCustomerDiscount() {
|
||||
return this.customer.tier === 'gold' ? 0.1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// After — Customer class 안
|
||||
class Customer {
|
||||
getDiscount() {
|
||||
return this.tier === 'gold' ? 0.1 : 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ Feature envy 제거.
|
||||
|
||||
### Replace inheritance with composition
|
||||
```ts
|
||||
// Before
|
||||
class Reader extends FileSystem { ... }
|
||||
|
||||
// After
|
||||
class Reader {
|
||||
constructor(private fs: FileSystem) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Simplifying conditionals
|
||||
```ts
|
||||
// Before
|
||||
if (x) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// After
|
||||
return Boolean(x);
|
||||
|
||||
// Before
|
||||
if (x === null || x === undefined) return defaultVal;
|
||||
return x;
|
||||
|
||||
// After
|
||||
return x ?? defaultVal;
|
||||
```
|
||||
|
||||
### Refactor in sprint
|
||||
```
|
||||
Friday: refactor day.
|
||||
또는 매 PR 의 작은 cleanup.
|
||||
|
||||
큰 refactor = 별 sprint 또는 분기.
|
||||
```
|
||||
|
||||
### Continuous refactoring
|
||||
```
|
||||
Boy scout rule + 매 PR + IDE 도구 = 코드 점차 개선.
|
||||
|
||||
Big-bang refactor 거의 안 함.
|
||||
```
|
||||
|
||||
### When NOT to refactor
|
||||
```
|
||||
- 작동하는 코드 + 안 변경할 영역
|
||||
- Critical path + test 없음
|
||||
- Deadline 임박
|
||||
- 이해 안 됐을 때 (먼저 이해)
|
||||
- 명확 owner X
|
||||
```
|
||||
|
||||
→ Refactor = 변경 시 우선.
|
||||
|
||||
### Communication
|
||||
```
|
||||
PR description:
|
||||
"This PR is pure refactor — no behavior change.
|
||||
Reduces complexity score from 25 → 12."
|
||||
|
||||
→ Reviewer 가 변경 검사.
|
||||
```
|
||||
|
||||
### Test gap (refactor 전)
|
||||
```
|
||||
Test 없으면:
|
||||
1. 작은 black-box test (입력/출력)
|
||||
2. Refactor
|
||||
3. Test 가 behavior lock
|
||||
```
|
||||
|
||||
### Tools
|
||||
```
|
||||
- VS Code / WebStorm refactoring
|
||||
- ESLint --fix
|
||||
- ts-prune (unused exports)
|
||||
- knip (unused files / deps)
|
||||
- prettier (style — refactor 의 friend)
|
||||
- AI assist (Cursor, Copilot)
|
||||
```
|
||||
|
||||
### Refactor categories (Fowler)
|
||||
```
|
||||
1. Composing methods
|
||||
2. Moving features between objects
|
||||
3. Organizing data
|
||||
4. Simplifying conditional expressions
|
||||
5. Making method calls simpler
|
||||
6. Dealing with generalization
|
||||
7. Big refactorings
|
||||
```
|
||||
|
||||
→ refactoring.com 무료.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| 매 PR | Boy scout (작은 cleanup) |
|
||||
| Code review 발견 | 즉시 또는 follow-up issue |
|
||||
| Hot spot (자주 변경) | 우선 refactor |
|
||||
| 큰 architecture | Strangler fig + parallel change |
|
||||
| Library version | Phased migration |
|
||||
| Critical path | Test 먼저 |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Big bang refactor**: 깨짐 위험.
|
||||
- **Test 없는 refactor**: behavior 변경 모름.
|
||||
- **Refactor + new feature 같은 PR**: review 어려움.
|
||||
- **Style only refactor**: lint / formatter 가 처리.
|
||||
- **모든 코드 refactor 시도**: focus 잃음.
|
||||
- **이해 못 한 코드 refactor**: bug 가능성.
|
||||
- **Refactor 미공지 (대규모)**: surprise.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- 작은 reversible step.
|
||||
- IDE refactoring 도구 활용.
|
||||
- Test first 안전.
|
||||
- Parallel change / strangler 큰 변경.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Quality_Tech_Debt]]
|
||||
- [[Productivity_Code_Review]]
|
||||
- [[Testing_Test_Pyramid]]
|
||||
Reference in New Issue
Block a user