Refactoring codebase lớn với Claude Code — Từ legacy đến modern architecture
Điểm nổi bật
Nhấn để đến mục tương ứng
- 1 Zero-downtime database migration # Prompt cho zero-downtime migration Tôi cần thêm cột "status" vào bảng orders (PostgreSQL, 5 triệu rows).
- 2 Bước 6: Deploy và verify Triển khai theo quy trình: 1.
- 3 # Prompt cho file-by-file refactoring Refactor file /src/routes/users.js theo kế hoạch: 1.
- 4 Switch 10% traffic sang NestJS (canary) 4.
- 5 Sử dụng Plan Mode để lên chiến lược, file-by-file approach để giảm risk, characterization tests để đảm bảo safety, và incremental deployment để kiểm soát impact.
Mọi codebase đủ tuổi đều tích lũy technical debt. Những quyết định hợp lý ở thời điểm startup trở thành gánh nặng khi team mở rộng và sản phẩm scale. Refactoring codebase lớn luôn là công việc đáng sợ nhất với developer: rủi ro break production cao, khó estimate thời gian, và thường bị deprioritize cho đến khi quá muộn. Claude Code thay đổi phương trình này bằng khả năng đọc hiểu codebase rộng, lên chiến lược refactoring có cấu trúc, và thực hiện thay đổi an toàn với test coverage.
Khi nào cần refactor?
Không phải mọi code cũ đều cần refactor. Nhận diện các tín hiệu sau để quyết định khi nào refactoring là cần thiết:
Code smells -- dấu hiệu code cần refactor
-
God objects/files: File trên 500 dòng, class làm quá nhiều thứ. Ví dụ:
UserController.jsvừa xử lý authentication, vừa quản lý profile, vừa handle payments. - Duplicate code: Copy-paste logic xuất hiện ở 3+ nơi. Mỗi lần sửa bug phải sửa ở nhiều chỗ.
- Tight coupling: Thay đổi ở module A bắt buộc phải sửa module B, C, D. Deploy một feature đơn giản cần test toàn bộ hệ thống.
- Outdated patterns: Callback hell, var thay vì const/let, class components thay vì hooks, string SQL queries thay vì ORM.
- Missing abstractions: Business logic nằm trực tiếp trong route handlers, database queries rải rác khắp nơi thay vì tập trung trong repository layer.
- Test difficulty: Không thể viết unit test cho một function vì nó phụ thuộc trực tiếp vào database, file system, hoặc external API.
Technical debt signals
# Yêu cầu Claude Code đánh giá technical debt
Đọc toàn bộ codebase trong thư mục /src và phân tích:
1. Files nào có LOC cao nhất? (potential god objects)
2. Có patterns nào bị duplicate nhiều lần?
3. Dependency graph: modules nào coupled chặt nhất?
4. Có outdated patterns nào cần modernize?
5. Test coverage hiện tại? Files nào thiếu tests?
6. Có circular dependencies không?
Tạo báo cáo technical debt với severity levels
(Critical / High / Medium / Low) và effort estimate.
Claude Code Plan Mode cho chiến lược refactoring
Trước khi sửa bất kỳ dòng code nào, dùng Plan Mode để lên chiến lược:
# Trong Claude Code, bật Plan Mode
Tôi muốn refactor codebase Express.js hiện tại sang NestJS.
Codebase hiện tại:
- 45 route files trong /routes
- 30 middleware files
- 15 database models (Mongoose)
- 0% test coverage
- Authentication dùng passport.js
- File upload dùng multer
- Caching dùng node-cache (in-memory)
Yêu cầu:
- Không downtime trong quá trình migration
- Cả Express và NestJS cần chạy song song trong transition period
- Migration phải incremental, không big-bang
- Mỗi phase phải có rollback plan
Lên kế hoạch refactoring chi tiết với phases,
timeline estimate, và risk assessment.
Claude Code Plan Mode sẽ phân tích codebase và đưa ra kế hoạch nhiều phase:
Phase 1: Foundation (Tuần 1-2)
- Setup NestJS project cạnh Express project
- Cấu hình shared database connections
- Setup proxy để route traffic giữa Express và NestJS
- Viết integration tests cho critical paths hiện tại (trước khi thay đổi code)
Phase 2: Migrate theo feature (Tuần 3-8)
- Migrate từng nhóm routes: auth -> users -> products -> orders
- Mỗi nhóm: viết NestJS module -> test -> switch proxy -> verify -> remove Express code
- Rollback: switch proxy ngược lại nếu có issue
Phase 3: Modernize patterns (Tuần 9-10)
- Thay node-cache bằng Redis
- Thêm validation pipes
- Implement proper error handling
- Add comprehensive test coverage
Phase 4: Cleanup (Tuần 11-12)
- Remove Express code
- Remove proxy layer
- Final testing
- Documentation update
File-by-file vs Big-bang approach
File-by-file (Strangler Fig Pattern)
Refactor từng file/module một, giữ hệ thống chạy liên tục. Ưu tiên cho production systems.
# Prompt cho file-by-file refactoring
Refactor file /src/routes/users.js theo kế hoạch:
1. Đọc file hiện tại và liệt kê tất cả responsibilities
2. Tách thành:
- /src/modules/users/users.controller.ts (route handlers)
- /src/modules/users/users.service.ts (business logic)
- /src/modules/users/users.repository.ts (database queries)
- /src/modules/users/dto/create-user.dto.ts (validation)
3. Viết tests cho users.service.ts
4. Đảm bảo tất cả existing API endpoints vẫn hoạt động
Làm từng bước, chạy tests sau mỗi bước.
Big-bang (chỉ khi không có production traffic)
Viết lại toàn bộ rồi switch. Chỉ phù hợp cho internal tools hoặc pre-launch projects.
# Prompt cho big-bang refactoring
Viết lại toàn bộ /src/api theo clean architecture:
Cấu trúc mới:
/src
/domain (entities, value objects, interfaces)
/application (use cases, DTOs)
/infrastructure (database, external APIs)
/presentation (controllers, middleware)
Mapping từ code cũ:
- Mỗi route handler -> controller + use case
- Database queries -> repository implementations
- Business rules -> domain entities
Viết tất cả tests trước, rồi implement.
Maintaining tests during refactoring
Tests là safety net quan trọng nhất khi refactoring. Có hai chiến lược:
Chiến lược 1: Viết characterization tests trước khi refactor
Characterization tests ghi lại behavior hiện tại, kể cả bugs. Mục đích là đảm bảo refactoring không thay đổi behavior, chỉ thay đổi structure.
# Prompt viết characterization tests
Đọc file /src/routes/orders.js và viết characterization tests:
1. Với mỗi route handler, viết test cho:
- Happy path (request hợp lệ, response đúng format)
- Error cases (validation errors, not found, unauthorized)
- Edge cases đặc biệt trong code hiện tại
2. Dùng supertest cho API testing
3. Mock database calls
4. Mục đích: capture behavior HIỆN TẠI, kể cả quirks
KHÔNG sửa code. KHÔNG fix bugs. Chỉ viết tests mô tả
chính xác code hiện tại hoạt động như thế nào.
Chiến lược 2: Adapter pattern -- giữ interface, đổi implementation
# Prompt cho adapter pattern
Tôi đang chuyển từ Mongoose sang Prisma.
Viết adapter để giữ interface không đổi:
1. Tạo interface IUserRepository với methods hiện tại
2. Implement MongooseUserRepository (code hiện tại)
3. Implement PrismaUserRepository (code mới)
4. Dùng dependency injection để switch giữa hai implementations
5. Chạy cùng tests với cả hai implementations
Khi PrismaUserRepository pass tất cả tests,
switch default implementation sang Prisma.
Database migration trong refactoring
Database migration là phần rủi ro cao nhất của refactoring. Claude Code giúp lên kế hoạch migration an toàn:
Schema migration
# Prompt cho database migration
Tôi cần migrate database schema:
Schema hiện tại (MongoDB):
users: { name, email, address: String, phone: String }
orders: { userId, items: Array, total: Number, status: String }
Schema mới (PostgreSQL):
users: { id, name, email }
user_profiles: { user_id, address, phone }
orders: { id, user_id, status, total }
order_items: { id, order_id, product_id, quantity, price }
Lên kế hoạch migration:
1. Migration script đọc từ MongoDB, ghi vào PostgreSQL
2. Xử lý data transformation (embedded -> normalized)
3. Validation: so sánh data sau migration
4. Dual-write period: ghi cả hai DB
5. Cutover plan: switch reads sang PostgreSQL
6. Rollback plan nếu có issues
Viết migration script với error handling và progress logging.
Zero-downtime database migration
# Prompt cho zero-downtime migration
Tôi cần thêm cột "status" vào bảng orders (PostgreSQL,
5 triệu rows). Yêu cầu zero-downtime.
Kế hoạch 4 bước:
1. ALTER TABLE ADD COLUMN với DEFAULT value (nhanh, không lock)
2. Backfill data cho existing rows (batch processing)
3. Deploy code mới sử dụng cột "status"
4. Cleanup: remove old column nếu cần
Viết migration scripts cho mỗi bước.
Estimate thời gian cho bước 2 với batch size 10,000.
API versioning trong refactoring
Khi refactoring API, cần đảm bảo clients hiện tại không bị break:
# Prompt cho API versioning
Tôi đang refactor API và cần thay đổi response format:
Hiện tại (v1):
GET /api/users/123
Response: { "id": 123, "fullName": "Nguyen Van A", "addr": "..." }
Mới (v2):
GET /api/v2/users/123
Response: {
"data": {
"id": 123,
"name": { "first": "Van A", "last": "Nguyen" },
"address": { "street": "...", "city": "...", "country": "..." }
},
"meta": { "version": "2.0" }
}
Yêu cầu:
1. Cả v1 và v2 cùng chạy trong transition period (3 tháng)
2. v1 response được tạo từ cùng data source với v2
3. Deprecation headers cho v1
4. Migration guide cho clients
5. Monitoring: track usage v1 vs v2
Implement NestJS versioning với cả hai versions.
Ví dụ thực tế: Migration Express sang NestJS
Dưới đây là walkthrough chi tiết cho một module cụ thể:
Bước 1: Đọc và phân tích Express code hiện tại
Đọc file /src/routes/products.js và phân tích:
1. Liệt kê tất cả endpoints (method, path, handler)
2. Dependencies (database, external services, middleware)
3. Business logic embedded trong route handlers
4. Error handling patterns
5. Authentication/authorization checks
Bước 2: Viết tests cho Express code
Viết integration tests cho /src/routes/products.js:
- Cover tất cả endpoints
- Test authentication (authorized vs unauthorized)
- Test validation (valid vs invalid input)
- Test error responses
- Dùng supertest, mock database
Mục đích: tests này sẽ được reuse cho NestJS version.
Bước 3: Scaffold NestJS module
Tạo NestJS products module:
- products.module.ts
- products.controller.ts (exact same endpoints as Express)
- products.service.ts (business logic extracted from handlers)
- products.repository.ts (database queries)
- dto/create-product.dto.ts
- dto/update-product.dto.ts
Implement đầy đủ dựa trên logic trong Express code.
Endpoints phải trả về exact same response format.
Bước 4: Chạy tests trên NestJS
Adapt integration tests để chạy trên NestJS TestingModule.
Tất cả tests phải pass với NestJS implementation.
Fix bất kỳ discrepancy nào giữa Express và NestJS behavior.
Bước 5: Setup proxy routing
Cấu hình nginx/API gateway để route:
- /api/products/* -> NestJS (new)
- /api/* -> Express (old, remaining routes)
Implement health check cho cả hai services.
Setup monitoring để so sánh response times và error rates.
Bước 6: Deploy và verify
Triển khai theo quy trình:
1. Deploy NestJS service (không nhận traffic)
2. Run smoke tests trực tiếp vào NestJS
3. Switch 10% traffic sang NestJS (canary)
4. Monitor 24h: error rate, latency, response correctness
5. Nếu OK, switch 100% traffic
6. Keep Express code 1 tuần cho rollback nếu cần
7. Remove Express products routes
Refactoring patterns phổ biến
Extract Service pattern
# Prompt
File /src/controllers/orderController.js có 800 dòng.
Extract business logic thành OrderService:
1. Identify tất cả business logic (validation, calculation,
state transitions) trong controller
2. Tạo OrderService class với methods cho mỗi business operation
3. Controller chỉ còn: parse request -> call service -> format response
4. Service nhận plain objects, không biết về req/res
5. Viết unit tests cho service (không cần HTTP)
6. Chạy existing integration tests để verify
Replace Callbacks with Async/Await
# Prompt
Refactor file /src/services/paymentService.js:
Chuyển tất cả callback patterns sang async/await.
Ví dụ hiện tại:
function processPayment(orderId, callback) {
db.findOrder(orderId, function(err, order) {
if (err) return callback(err);
gateway.charge(order.total, function(err, result) {
if (err) return callback(err);
db.updateOrder(orderId, { paid: true }, function(err) {
callback(err, result);
});
});
});
}
Chuyển thành async/await, thêm proper error handling
với try/catch, và đảm bảo tất cả callers cũng được update.
Dependency Injection
# Prompt
Hiện tại code import trực tiếp dependencies:
const db = require("./database");
const emailService = require("./emailService");
const logger = require("./logger");
function createUser(data) {
const user = db.insert("users", data);
emailService.send(user.email, "Welcome!");
logger.info("User created", user.id);
return user;
}
Refactor sang dependency injection:
1. Function nhận dependencies qua parameters hoặc constructor
2. Tạo composition root nơi wire dependencies
3. Viết unit tests với mock dependencies
4. Existing behavior không đổi
Monitoring refactoring progress
# Prompt cho tracking progress
Tạo refactoring scorecard cho codebase:
Metrics cần track:
1. Files đã migrate: X/45 routes
2. Test coverage: trước vs sau
3. Lines of code: trước vs sau
4. Cyclomatic complexity: trước vs sau
5. Dependencies: circular deps count
6. Build time: trước vs sau
7. API response time: trước vs sau
Tạo script chạy hàng tuần, output ra markdown table.
So sánh với baseline (trước refactoring).
Pitfalls cần tránh khi refactoring
- Refactor quá nhiều cùng lúc: Mỗi PR nên chỉ refactor một concern. Review dễ hơn, rollback dễ hơn, risk thấp hơn.
- Refactor mà không có tests: Viết characterization tests trước. Không có tests nghĩa là không có safety net.
- Thay đổi behavior khi refactoring: Refactoring chỉ thay đổi structure, không thay đổi behavior. Nếu muốn fix bug hoặc thêm feature, làm trong PR riêng.
- Không communicate với team: Refactoring ảnh hưởng đến mọi người. Thông báo trước, coordinate timing, và cập nhật documentation.
- Bỏ qua database migration risks: Schema changes cần planning kỹ nhất. Luôn có rollback plan và test trên copy of production data.
- Big-bang rewrite cho production system: Trừ khi system rất nhỏ hoặc không có users, luôn dùng incremental approach. Lịch sử phát triển phần mềm đầy rẫy big-bang rewrites thất bại.
Claude Code workflow cho refactoring hàng ngày
# CLAUDE.md section cho refactoring workflow
## Refactoring Guidelines
- Trước khi refactor: chạy tests, commit code hiện tại
- Mỗi refactoring step: một commit riêng với message rõ ràng
- Sau mỗi step: chạy tests, verify không break
- Nếu tests fail: revert về commit trước, phân tích lại
- Code review: yêu cầu Claude Code review refactored code
so với original, xác nhận behavior preserved
- Documentation: update comments và docs cho code mới
Tổng kết
Refactoring codebase lớn là marathon, không phải sprint. Claude Code làm mỗi bước nhanh hơn và an toàn hơn, nhưng chiến lược tổng thể vẫn phụ thuộc vào quyết định của bạn: refactor cái gì trước, đến mức nào, và khi nào dừng. Sử dụng Plan Mode để lên chiến lược, file-by-file approach để giảm risk, characterization tests để đảm bảo safety, và incremental deployment để kiểm soát impact. Codebase hoàn hảo không tồn tại, nhưng codebase có thể maintain, test, và mở rộng là mục tiêu hoàn toàn khả thi với Claude Code.
Bai viet co huu ich khong?
Bản quyền thuộc về tác giả. Vui lòng dẫn nguồn khi chia sẻ.







