Lý thuyết
30 phút
Bài 8/12

Refactoring với Copilot

Cải thiện code structure, giảm tech debt, và modernize codebase với AI assistance

Refactoring với Copilot

1. Refactoring là gì?

Refactoring = Cải thiện code structure mà không thay đổi behavior

Tại sao cần Refactoring?

BeforeAfter
Code khó đọcClear, readable
Duplicate codeDRY (Don't Repeat Yourself)
Long functionsSmall, focused functions
Complex conditionsSimple, clear logic
Hard to testEasy to test
Hard to extendEasy to extend

Khi nào nên Refactor?

Refactoring Triggers


2. Code Smells và Cách Fix

Common Code Smells

Code Smells
Bloaters
Long Method
Large Class
Long Parameter List
OO Abuse
Switch Statements
Refused Bequest
Change Preventers
Divergent Change
Shotgun Surgery
Dispensables
Dead Code
Duplicate Code

3. Copilot Refactoring Commands

3.1 Quick Refactors

CommandMô tả
/simplifyĐơn giản hóa code phức tạp
/fixSửa issues và improve
Inline Chat"Refactor this to..."

3.2 Using /simplify

Python
1# Original - Complex nested conditions
2def get_discount(user, cart):
3 if user.is_premium:
4 if cart.total > 100:
5 if cart.has_coupon:
6 return 0.25
7 else:
8 return 0.15
9 else:
10 if cart.has_coupon:
11 return 0.10
12 else:
13 return 0.05
14 else:
15 if cart.total > 100:
16 if cart.has_coupon:
17 return 0.10
18 else:
19 return 0.05
20 else:
21 if cart.has_coupon:
22 return 0.05
23 else:
24 return 0
25
26# Select và dùng /simplify

Copilot Output:

Python
1def get_discount(user, cart):
2 base_discount = 0.05 if cart.total > 100 else 0
3 premium_bonus = 0.10 if user.is_premium else 0
4 coupon_bonus = 0.05 if cart.has_coupon else 0
5
6 return base_discount + premium_bonus + coupon_bonus

4. Extract Method Refactoring

When to Extract

  • Function > 20 lines
  • Code block có specific purpose
  • Duplicate code blocks

Example

Before:

JavaScript
1async function processOrder(order) {
2 // Validate order
3 if (!order.items || order.items.length === 0) {
4 throw new Error('Empty order');
5 }
6 if (!order.customer || !order.customer.email) {
7 throw new Error('Invalid customer');
8 }
9 if (!order.shippingAddress || !order.shippingAddress.city) {
10 throw new Error('Invalid shipping address');
11 }
12
13 // Calculate totals
14 let subtotal = 0;
15 for (const item of order.items) {
16 subtotal += item.price * item.quantity;
17 }
18 const tax = subtotal * 0.1;
19 const shipping = subtotal > 100 ? 0 : 10;
20 const total = subtotal + tax + shipping;
21
22 // Save to database
23 const savedOrder = await db.orders.create({
24 ...order,
25 subtotal,
26 tax,
27 shipping,
28 total,
29 status: 'pending'
30 });
31
32 // Send confirmation email
33 await emailService.send({
34 to: order.customer.email,
35 subject: 'Order Confirmation',
36 body: `Your order #${savedOrder.id} has been placed.`
37 });
38
39 return savedOrder;
40}

Prompt:

Text
1Refactor this function by extracting separate methods for:
2- Validation
3- Calculating totals
4- Saving to database
5- Sending email
6Keep the main function as an orchestrator.

After:

JavaScript
1function validateOrder(order) {
2 if (!order.items?.length) {
3 throw new Error('Empty order');
4 }
5 if (!order.customer?.email) {
6 throw new Error('Invalid customer');
7 }
8 if (!order.shippingAddress?.city) {
9 throw new Error('Invalid shipping address');
10 }
11}
12
13function calculateOrderTotals(items) {
14 const subtotal = items.reduce(
15 (sum, item) => sum + item.price * item.quantity,
16 0
17 );
18 const tax = subtotal * TAX_RATE;
19 const shipping = subtotal > FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_COST;
20
21 return { subtotal, tax, shipping, total: subtotal + tax + shipping };
22}
23
24async function saveOrder(order, totals) {
25 return db.orders.create({
26 ...order,
27 ...totals,
28 status: 'pending'
29 });
30}
31
32async function sendOrderConfirmation(email, orderId) {
33 await emailService.send({
34 to: email,
35 subject: 'Order Confirmation',
36 body: `Your order #${orderId} has been placed.`
37 });
38}
39
40// Main orchestrator - clean and readable
41async function processOrder(order) {
42 validateOrder(order);
43 const totals = calculateOrderTotals(order.items);
44 const savedOrder = await saveOrder(order, totals);
45 await sendOrderConfirmation(order.customer.email, savedOrder.id);
46 return savedOrder;
47}

5. Replace Conditional with Polymorphism

Before - Switch Statement

Python
1def calculate_shipping(order_type, weight):
2 if order_type == 'standard':
3 return weight * 2.5
4 elif order_type == 'express':
5 return weight * 5.0 + 10
6 elif order_type == 'overnight':
7 return weight * 8.0 + 25
8 elif order_type == 'international':
9 return weight * 15.0 + 50
10 else:
11 raise ValueError(f"Unknown order type: {order_type}")

Prompt

Text
1Refactor this to use polymorphism with a ShippingStrategy pattern

After - Strategy Pattern

Python
1from abc import ABC, abstractmethod
2
3class ShippingStrategy(ABC):
4 @abstractmethod
5 def calculate(self, weight: float) -> float:
6 pass
7
8class StandardShipping(ShippingStrategy):
9 def calculate(self, weight: float) -> float:
10 return weight * 2.5
11
12class ExpressShipping(ShippingStrategy):
13 def calculate(self, weight: float) -> float:
14 return weight * 5.0 + 10
15
16class OvernightShipping(ShippingStrategy):
17 def calculate(self, weight: float) -> float:
18 return weight * 8.0 + 25
19
20class InternationalShipping(ShippingStrategy):
21 def calculate(self, weight: float) -> float:
22 return weight * 15.0 + 50
23
24# Factory
25SHIPPING_STRATEGIES = {
26 'standard': StandardShipping,
27 'express': ExpressShipping,
28 'overnight': OvernightShipping,
29 'international': InternationalShipping,
30}
31
32def calculate_shipping(order_type: str, weight: float) -> float:
33 strategy_class = SHIPPING_STRATEGIES.get(order_type)
34 if not strategy_class:
35 raise ValueError(f"Unknown order type: {order_type}")
36 return strategy_class().calculate(weight)

6. Remove Duplicate Code

Identify Duplicates

Text
1@workspace find duplicate code patterns in src/

Example

Before - Duplicate validation:

Python
1# In user_service.py
2def create_user(data):
3 if not data.get('email'):
4 raise ValueError('Email required')
5 if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', data['email']):
6 raise ValueError('Invalid email format')
7 # ... create user
8
9# In order_service.py
10def create_order(data):
11 if not data.get('email'):
12 raise ValueError('Email required')
13 if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', data['email']):
14 raise ValueError('Invalid email format')
15 # ... create order

Prompt:

Text
1Extract duplicate email validation into a reusable utility

After:

Python
1# validators.py
2import re
3
4class ValidationError(Exception):
5 pass
6
7def validate_email(email: str) -> str:
8 """Validate email format and return cleaned email."""
9 if not email:
10 raise ValidationError('Email required')
11
12 email = email.strip().lower()
13 if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):
14 raise ValidationError('Invalid email format')
15
16 return email
17
18# user_service.py
19from validators import validate_email
20
21def create_user(data):
22 data['email'] = validate_email(data.get('email'))
23 # ... create user
24
25# order_service.py
26from validators import validate_email
27
28def create_order(data):
29 data['email'] = validate_email(data.get('email'))
30 # ... create order

7. Modernize Code

Update Syntax

Prompt:

Text
1Modernize this code to use ES2023+ features:
2- Optional chaining
3- Nullish coalescing
4- Array methods
5- Template literals

Before:

JavaScript
1function getUserInfo(user) {
2 var name = 'Anonymous';
3 if (user && user.profile && user.profile.name) {
4 name = user.profile.name;
5 }
6
7 var roles = [];
8 if (user && user.roles) {
9 for (var i = 0; i < user.roles.length; i++) {
10 roles.push(user.roles[i].toUpperCase());
11 }
12 }
13
14 return 'User: ' + name + ', Roles: ' + roles.join(', ');
15}

After:

JavaScript
1const getUserInfo = (user) => {
2 const name = user?.profile?.name ?? 'Anonymous';
3 const roles = user?.roles?.map(role => role.toUpperCase()) ?? [];
4
5 return `User: ${name}, Roles: ${roles.join(', ')}`;
6};

Convert Callbacks to Async/Await

Prompt:

Text
1Convert this callback-based code to async/await

Before:

JavaScript
1function loadUserData(userId, callback) {
2 getUser(userId, (err, user) => {
3 if (err) return callback(err);
4
5 getOrders(user.id, (err, orders) => {
6 if (err) return callback(err);
7
8 getPayments(user.id, (err, payments) => {
9 if (err) return callback(err);
10
11 callback(null, { user, orders, payments });
12 });
13 });
14 });
15}

After:

JavaScript
1async function loadUserData(userId) {
2 const user = await getUser(userId);
3 const [orders, payments] = await Promise.all([
4 getOrders(user.id),
5 getPayments(user.id)
6 ]);
7
8 return { user, orders, payments };
9}

8. Safe Refactoring Workflow

Step-by-Step Process

markdown
11. **Ensure Test Coverage**
2 - Run existing tests
3 - Add tests if missing
4 - Tests = Safety net
5
62. **Make Small Changes**
7 - One refactor at a time
8 - Commit after each
9 - Easy to rollback
10
113. **Run Tests After Each Change**
12 - All tests must pass
13 - No behavior change
14
154. **Code Review**
16 - Use Copilot to review
17 - Check for edge cases

Rollback Strategy

Bash
1# If something breaks
2git stash # Save current changes
3git checkout . # Restore last commit
4# OR
5git revert HEAD # Revert last commit

9. Large-Scale Refactoring

Using Agent Mode

Với refactoring lớn, sử dụng Agent mode:

Text
1Refactor the authentication system:
2
3Current structure:
4- auth.py (500 lines, does everything)
5
6Target structure:
7- auth/
8 - __init__.py
9 - models.py (User, Session, Token models)
10 - services.py (AuthService class)
11 - handlers.py (Route handlers)
12 - utils.py (Helper functions)
13 - exceptions.py (Custom exceptions)
14
15Requirements:
16- Split the monolithic file
17- Keep all functionality working
18- Maintain backward compatibility for imports
19- Update all files that import from auth.py

Migration Pattern

Prompt cho migration:

Text
1I want to migrate from [OLD_PATTERN] to [NEW_PATTERN].
2
3Current usage in codebase:
4- File A uses OLD_PATTERN at lines X, Y, Z
5- File B uses OLD_PATTERN at lines ...
6
7Create a migration plan:
81. Create NEW_PATTERN
92. Update usages one by one
103. Deprecate OLD_PATTERN
114. Remove OLD_PATTERN

10. Refactoring Exercises

Exercise 1: Long Method

Refactor this function:

Python
1def process_data(data):
2 # Validate
3 if not data:
4 return None
5 if not isinstance(data, list):
6 data = [data]
7 valid_items = []
8 for item in data:
9 if item and isinstance(item, dict) and 'value' in item:
10 valid_items.append(item)
11 if not valid_items:
12 return None
13
14 # Transform
15 transformed = []
16 for item in valid_items:
17 new_item = {}
18 new_item['id'] = item.get('id', str(uuid.uuid4()))
19 new_item['value'] = float(item['value'])
20 new_item['timestamp'] = item.get('timestamp', datetime.now().isoformat())
21 new_item['category'] = item.get('category', 'default').lower()
22 transformed.append(new_item)
23
24 # Aggregate
25 total = sum(item['value'] for item in transformed)
26 avg = total / len(transformed)
27 by_category = {}
28 for item in transformed:
29 cat = item['category']
30 if cat not in by_category:
31 by_category[cat] = []
32 by_category[cat].append(item)
33
34 return {
35 'items': transformed,
36 'total': total,
37 'average': avg,
38 'by_category': by_category,
39 'count': len(transformed)
40 }

Goal: Extract into validate(), transform(), aggregate() functions.

Exercise 2: Duplicate Code

Tìm và loại bỏ duplicate:

JavaScript
1// In productService.js
2async function searchProducts(query) {
3 const results = await db.products.find({ name: new RegExp(query, 'i') });
4 return results.map(p => ({
5 id: p._id,
6 name: p.name,
7 price: p.price,
8 image: p.images[0] || '/placeholder.jpg'
9 }));
10}
11
12// In categoryService.js
13async function getProductsByCategory(categoryId) {
14 const results = await db.products.find({ categoryId });
15 return results.map(p => ({
16 id: p._id,
17 name: p.name,
18 price: p.price,
19 image: p.images[0] || '/placeholder.jpg'
20 }));
21}
22
23// In recommendationService.js
24async function getRecommendations(userId) {
25 const results = await getRecommendedProducts(userId);
26 return results.map(p => ({
27 id: p._id,
28 name: p.name,
29 price: p.price,
30 image: p.images[0] || '/placeholder.jpg'
31 }));
32}

Goal: Extract formatProduct() function.


11. Refactoring Checklist

markdown
1## Pre-Refactoring
2- [ ] Understand the code fully
3- [ ] Have tests covering current behavior
4- [ ] Create a branch for refactoring
5- [ ] Define the goal clearly
6
7## During Refactoring
8- [ ] Make one change at a time
9- [ ] Run tests after each change
10- [ ] Commit frequently
11- [ ] Use meaningful commit messages
12
13## Post-Refactoring
14- [ ] All tests pass
15- [ ] No functionality changed
16- [ ] Code is cleaner/simpler
17- [ ] Code review completed
18- [ ] Documentation updated if needed

Tiếp theo

Bài tiếp theo: Testing với Copilot - generate unit tests, integration tests, và improve test coverage với AI!