Sáu tháng trước, tôi tiếp nhận một dự án Kotlin Android không ai đụng đến từ 2021. 50,000 dòng code. Không có test. Deprecated libraries khắp nơi. Một God Activity 3,000 dòng. Và deadline ship feature mới trên đó.

Tôi cũ sẽ mất hàng tuần đọc hiểu codebase trước khi dám đụng vào. Thay vào đó, tôi dùng Claude Code làm co-pilot cho toàn bộ quá trình hiện đại hóa. Đây là cách chính xác — và những sai lầm tôi mắc phải.


Điểm Xuất Phát: Chế Độ Khảo Cổ

Trước khi refactor bất kỳ dòng nào, tôi cần hiểu đang đối mặt với gì. Tôi gọi đây là “archeology mode” — khai quật các tầng lớp codebase để hiểu lịch sử và cấu trúc.

"Tôi vừa tiếp nhận dự án Android legacy. Trước khi thay đổi:
1. Đọc cấu trúc project và cho tôi architecture map tổng quan
2. Xác định file lớn nhất (theo số dòng)
3. Tìm deprecated API đang dùng
4. Kiểm tra Gradle dependencies xem lib nào outdated
5. Map luồng navigation từ Activity chính
Chưa thay đổi gì. Chỉ giúp tôi hiểu địa hình."

Claude Code mất khoảng 2 phút đọc file và cho ra báo cáo toàn diện:

  • Kiến trúc: MVP lỏng lẻo, nhưng Presenter nhúng trong Activity
  • File lớn nhất: MainActivity.kt (3,100 dòng), DataManager.kt (1,800 dòng), NetworkHelper.kt (950 dòng)
  • Deprecated APIs: AsyncTask (47 chỗ), HttpUrlConnection (12 chỗ), Support Library v28
  • Deps cũ: Retrofit 2.5 (hiện tại: 2.9), Kotlin 1.5 (hiện tại: 1.9), không có Coroutines
  • Navigation: Mọi thứ đổ về MainActivity với Fragment transaction thủ công

Một prompt duy nhất cho tôi bản đồ tư duy mà lẽ ra mất 2 ngày đọc code thủ công.


Chiến Lược: Vòng Tròn Đồng Tâm

Bạn không thể refactor mọi thứ cùng lúc. Đó là cách phá hỏng mọi thứ cùng lúc. Tôi dùng chiến lược “vòng tròn đồng tâm” — bắt đầu từ ngoài và đi vào trong.

Vòng 1: Dependencies — Update libraries mà không đổi code Vòng 2: Infrastructure — Thay thế deprecated APIs bằng tương đương hiện đại Vòng 3: Architecture — Tái cấu trúc code thành layers đúng cách Vòng 4: Features — Xây feature mới trên nền tảng đã hiện đại hóa

Mỗi vòng phải ổn định trước khi sang vòng tiếp. Claude Code vô giá ở mỗi giai đoạn.


Vòng 1: Update Dependencies

Điểm khởi đầu an toàn nhất. Update dependencies, sửa lỗi compilation, xác nhận app vẫn chạy.

"Update Gradle dependencies lên phiên bản stable mới nhất.
Với mỗi dependency:
1. Cho tôi thấy version hiện tại vs. version khuyến nghị
2. Ghi chú breaking changes trong changelog
3. Thực hiện thay đổi trong build.gradle.kts
Bắt đầu với Kotlin version, rồi AndroidX, rồi third-party libraries.
Làm từng cái một để phát hiện vấn đề sớm."

Claude Code update từng dependency có phương pháp. Kỹ thuật quan trọng: từng cái một. Khi tôi thử yêu cầu Claude update tất cả cùng lúc (ở project khác), nó tạo ra cơn ác mộng compilation — 15 lỗi chồng chéo và không biết dependency nào gây lỗi nào.

Bài học: Với legacy code, luôn để Claude làm từng bước nhỏ, kiểm chứng được.

Sau khi update Kotlin, chúng tôi gặp vấn đề thật đầu tiên:

"Kotlin update từ 1.5 lên 1.9 break 23 file vì behavior mới mặc định
cho sealed interfaces. Đọc error messages từ ./gradlew build và sửa
từng file một."

Claude Code sửa từng file, chạy build giữa mỗi lần sửa để verify. 23 fixes, zero regressions. Thủ công thì mất cả buổi chiều frustration.


Vòng 2: Thay Thế Deprecated APIs

Task lớn nhất: thay 47 AsyncTask bằng Coroutines. Đây là nơi Claude Code thực sự tỏa sáng — refactoring cơ học, lặp lại, theo pattern rõ ràng.

"Cần thay tất cả AsyncTask bằng Kotlin Coroutines. Pattern:
TRƯỚC:
class FetchDataTask : AsyncTask<Void, Void, Result>() {
override fun doInBackground(): Result = api.fetchData()
override fun onPostExecute(result: Result) { updateUI(result) }
}
SAU:
viewModelScope.launch {
val result = withContext(Dispatchers.IO) { api.fetchData() }
updateUI(result)
}
Bắt đầu với DataManager.kt — nhiều AsyncTask nhất (12).
Thay từng method một. Sau mỗi lần thay, show diff."

Chìa khóa là cho Claude pattern trước/sau rõ ràng và giới hạn từng method một. Khi tôi để nó thay cả 12 lúc trong lần thử trước, nó tạo race condition tinh vi vì hai AsyncTask có dependency relationship không rõ ràng trong code.

Bài học: Legacy code giấu dependencies. Thay thế incrementally và verify mỗi bước.


Vòng 3: Phá Vỡ God Activity

Phần khó nhất. MainActivity.kt 3,100 dòng với 14 responsibilities — authentication, navigation, data loading, notifications, deep links, analytics, và nhiều nữa. Tất cả trong một class.

Tôi KHÔNG yêu cầu Claude refactor tất cả cùng lúc. Thay vào đó:

Bước 1: Xác định responsibilities

"Đọc MainActivity.kt và phân loại mọi public và private method
vào nhóm responsibility. Ví dụ: authentication, navigation,
data loading, UI updates, notifications. Chỉ phân loại, không đổi code."

Claude xác định 14 responsibility riêng biệt với 47 methods.

Bước 2: Extract từng responsibility

"Extract authentication responsibility từ MainActivity vào class
AuthManager mới. Bao gồm methods:
- checkLoginState() (dòng 145)
- handleLogin() (dòng 178)
- handleLogout() (dòng 210)
- refreshToken() (dòng 245)
Tạo AuthManager trong src/auth/AuthManager.kt. Dùng constructor
injection cho dependencies. Update MainActivity delegate sang AuthManager.
Giữ nguyên behavior — đây là refactoring, không phải rewrite."

Mỗi extraction theo cùng pattern:

  1. Xác định methods thuộc một responsibility
  2. Tạo class mới
  3. Di chuyển methods, inject dependencies
  4. Update MainActivity delegate
  5. Verify build pass
  6. Test feature thủ công

Sau 14 extractions qua 3 sessions, MainActivity.kt giảm từ 3,100 dòng xuống 180 dòng — chỉ còn lifecycle management và delegation.


Những Sai Lầm Tôi Mắc Phải

Sai Lầm 1: Yêu Cầu Quá Nhiều Cùng Lúc

Lần đầu thử Vòng 3: “Refactor MainActivity thành kiến trúc MVVM đúng cách.” Claude generate diff khổng lồ thay đổi 2,400 dòng cùng lúc. Trên giấy trông ổn. Nhưng nửa app bị hỏng vì Claude không tính đến implicit state dependencies giữa 14 responsibilities.

Cách sửa: Chia mọi refactoring lớn thành single-responsibility extractions.

Sai Lầm 2: Không Tạo Checkpoints

Trong Vòng 2, tôi làm 15 AsyncTask replacements liên tục trong một session mà không commit. Claude tạo bug tinh vi ở replacement #8 mà tôi không phát hiện cho đến #15. Phải undo hết và làm lại.

Cách sửa: git commit sau mỗi replacement thành công. Làm cho thay đổi của Claude atomic và reversible.

Terminal window
# Workflow thực tế cho mỗi replacement:
git add -A && git commit -m "refactor: replace AsyncTask in FetchDataTask"
# Nếu có gì break:
git revert HEAD

Sai Lầm 3: Tin Claude Nói “An Toàn”

Claude Code đôi khi nói “thay đổi này an toàn và không ảnh hưởng behavior.” Với legacy code, điều này gần như không bao giờ đúng. Code trông unused có thể được gọi qua reflection. Một “dead” code path có thể trigger bởi response cụ thể từ server. Legacy code đầy mìn ẩn.

Cách sửa: Verify mọi thay đổi “an toàn”. Chạy build. Test feature. Đừng tin — verify.


Kết Quả

MetricTrướcSau
MainActivity.kt3,100 dòng180 dòng
AsyncTask usages470
Deprecated APIs593 (giữ có chủ đích)
Build warnings340+12
Class size trung bình450 dòng120 dòng
Thời gian hiểu feature30-60 phút5-10 phút

Toàn bộ hiện đại hóa mất khoảng 3 tuần (part-time). Không có Claude Code, ước tính 8-10 tuần. Chìa khóa không phải Claude nhanh — mà là nó làm được phần cơ học (update dependencies, thay thế theo pattern, extract-and-delegate) với consistency hoàn hảo trong khi tôi tập trung vào quyết định kiến trúc.


Checklist Kỹ Thuật

Nếu bạn đang đối mặt legacy codebase, đây là approach đã hiệu quả:

  1. Khảo cổ trước. Dành một phiên hoàn chỉnh chỉ để hiểu. Chưa đổi gì.
  2. Vòng tròn đồng tâm. Dependencies → Infrastructure → Architecture → Features.
  3. Một responsibility mỗi lần. Không bao giờ refactor hai thứ cùng lúc.
  4. Cho Claude pattern. Show trước/sau cho replacement đầu tiên, rồi để nó lặp lại.
  5. Commit sau mỗi thay đổi. Làm rollback trivial.
  6. Verify mọi claim “an toàn”. Legacy code nói dối. Luôn verify.
  7. Giữ sessions tập trung. Một vòng mỗi session. Bắt đầu mới cho mỗi vòng.

Refactoring legacy code với Claude Code được trình bày chi tiết trong Phase 9: Legacy Code & Brownfield Projects của khóa học Claude Code Mastery. Phases 1-3 miễn phí.