ousterhout-quality-program
Nó làm gì
Sử dụng bất cứ khi nào mã đang được viết hoặc xem xét tạo ra hoặc thay đổi một ranh giới — một module mới, lớp, thành phần, helper, hook, dịch vụ hoặc wrapper; bất kỳ việc trích xuất hoặc tập trung mã dùng chung nào; bất kỳ khoảnh khắc "hãy làm cho cái này có thể tái sử dụng" — và khi xem xét, tái cấu trúc hoặc thiết kế một module một cách rõ ràng. Đánh giá xem một trừu tượng có xứng đáng hay không: độ sâu module, có nên ẩn một quyết định thiết kế hay không, mã trùng lặp có bảo vệ một bất biến dùng chung hay chỉ là tương đồng, giao diện có ổn định hay không. Ngăn ngừa việc áp dụng SOLID/Clean Code một cách máy móc dẫn đến nhiều lớp nông cạn. Cũng định nghĩa bài kiểm tra chi phí đọc (mã dễ đọc và thay đổi cho cả con người và agent) và quy trình tái cấu trúc một codebase hiện có theo tiêu chuẩn này.
Cài đặt sẽ mở mục này trong ứng dụng desktop AgentsRoom của bạn. Nếu ứng dụng chưa được cài, bạn sẽ được đưa tới trang tải về.
SKILL.md
---
name: ousterhout-quality-program
description: Sử dụng bất cứ khi nào mã đang được viết hoặc xem xét tạo ra hoặc thay đổi một ranh giới — một module mới, lớp, thành phần, helper, hook, dịch vụ hoặc wrapper; bất kỳ việc trích xuất hoặc tập trung mã dùng chung nào; bất kỳ khoảnh khắc "hãy làm cho cái này có thể tái sử dụng" — và khi xem xét, tái cấu trúc hoặc thiết kế một module một cách rõ ràng. Đánh giá xem một trừu tượng có xứng đáng hay không: độ sâu module, có nên ẩn một quyết định thiết kế hay không, mã trùng lặp có bảo vệ một bất biến dùng chung hay chỉ là tương đồng, giao diện có ổn định hay không. Ngăn ngừa việc áp dụng SOLID/Clean Code một cách máy móc dẫn đến nhiều lớp nông cạn. Cũng định nghĩa bài kiểm tra chi phí đọc (mã dễ đọc và thay đổi cho cả con người và agent) và quy trình tái cấu trúc một codebase hiện có theo tiêu chuẩn này.
---
# Chương Trình Chất Lượng Ousterhout
## Tổng Quan
Nhiệm vụ của một module là che giấu sự phức tạp phía sau một giao diện nhỏ gọn. Thước đo cốt lõi là **độ sâu**: một module sâu cung cấp một giao diện đơn giản trên một chức năng đáng kể; một module nông có giao diện gần như phức tạp như chính phần triển khai của nó, nên nó không mang lại giá trị gì. Sự phức tạp là điều bạn cảm nhận khi một thay đổi buộc bạn phải hiểu hoặc chạm vào mã mà bạn không ngờ tới — Ousterhout chỉ ra hai nguồn gốc: **phụ thuộc** (bạn không thể thay đổi A mà không thay đổi B) và **sự mơ hồ** (thông tin quan trọng không rõ ràng).
Ousterhout chỉ cho bạn cảm nhận về một module tốt. Nó mạnh nhất khi kết hợp với một vài lăng kính khác giúp bạn biết ranh giới nên đặt ở đâu và cách di chuyển đến đó một cách an toàn. Kỹ năng này là lăng kính kết hợp đó.
## Nơi Các Đánh Giá Thực Sự Sai Lầm
Hai lỗi mà kỹ năng này tồn tại để sửa — được quan sát lặp đi lặp lại trong mã do agent viết — là ở phần **biện pháp khắc phục**, không phải ở phán quyết tách hay không tách:
1. **Sửa nông.** Với sáu lần ép kiểu `as unknown as`, người đánh giá không hỗ trợ sẽ gom chúng lại thành một helper `castRows<T>()` chung — gọn gàng hơn, nhưng sự mơ hồ vẫn còn. Sửa sâu là các mapper kiểu row→domain có kiểm thử được định vị trước (áp dụng Parnas: một lần ép kiểu là dấu hiệu của ranh giới bị thiếu; Beck: chứng minh ánh xạ trước khi di chuyển nó). Làm gọn một mùi hôi không phải là loại bỏ nó.
2. **Tách ra phản xạ.** Với cùng logic cập nhật lặp lại trong ba component anh em, mọi người đánh giá không hỗ trợ đều nói "tách ra helper dùng chung" — phản xạ DRY. Quy tắc của chương trình này, mở rộng từ Metz: chờ đợi bất biến, không phải lần thứ ba giống nhau — tập trung khi mã bảo vệ một quy tắc dùng chung, không phải khi nó chỉ giống nhau.
Khi bạn thấy mình đề xuất một sửa đổi, hãy chạy nó qua cả hai câu hỏi: nó có loại bỏ sự mơ hồ hay chỉ di chuyển nó, và việc tách ra có bảo vệ một bất biến hay chỉ đơn giản là loại bỏ trùng lặp hình thức?
## Cổng Tỷ Lệ
Bỏ qua lăng kính khi một thay đổi không thêm tên xuất khẩu/nhập khẩu mới, không tạo module/lớp/component/helper/hook/service/wrapper mới, và không tập trung gì cả. Đổi tên thuần túy, codemod cơ học, chỉnh sửa cấu hình/dữ liệu, và sửa lỗi một dòng được miễn. Khi nghi ngờ, chỉ chạy hai kiểm tra cốt lõi (độ sâu, bất biến) và dừng lại.
## Quy Tắc Đầy Đủ
Mọi đoạn mã được tạo hoặc đánh giá đều phải qua lăng kính Ousterhout trước khi nhiệm vụ được gọi là hoàn thành — không chỉ các đánh giá thiết kế rõ ràng — ngoại trừ các thay đổi dưới cổng tỷ lệ (không có ranh giới mới, không tập trung: đổi tên, codemod, chỉnh sửa cấu hình). Hai kiểm tra: (1) **Độ sâu** — một giao diện mới phải che giấu nhiều hơn đáng kể so với những gì nó phơi bày; một giao diện phức tạp như những gì nó bao bọc thì không mang lại giá trị gì. (2) **Bất biến** — chỉ tách mã dùng chung khi nó bảo vệ một quy tắc dùng chung, không bao giờ vì ba chỗ giống nhau; và một sửa đổi phải loại bỏ sự mơ hồ, không chỉ di chuyển nó (tập trung sáu lần ép kiểu thành một helper vẫn là sáu lần ép kiểu). Khi một thay đổi tạo ra hoặc định hình lại một ranh giới, trước tiên hãy tìm cách một sản phẩm đã được thiết lập giải quyết một vấn đề có hình dạng và quy mô này và áp dụng quy ước của nó trừ khi có lý do rõ ràng không làm vậy (một mẫu nhớ lại từ đào tạo là một khẳng định, không phải nguồn), sau đó chạy các kiểm tra dưới đây.
## Khi Nào Dùng
- Quyết định xem một lớp/hàm/hook mới có xứng đáng với giao diện của nó hay chỉ là một đường truyền nông.
- Một file vượt ngưỡng kích thước và bạn đang quyết định *cách* tách nó, không chỉ là có nên tách hay không.
- Mã lặp lại khiến bạn muốn tách ra một helper dùng chung.
- Thiết kế hoặc đánh giá một ranh giới quanh một quy tắc nghiệp vụ (kiểm tra phạm vi ủy quyền, quy tắc tiền/tính làm tròn, bảo vệ chuyển trạng thái máy trạng thái, quy tắc lưu giữ dữ liệu).
- Một giao diện sắp có thêm tham số hoặc trường hợp đặc biệt.
- Đưa một codebase hiện có lên tiêu chuẩn này — xem phần "Tái cấu trúc Codebase Hiện Có theo Tiêu Chuẩn Này" bên dưới.
**Không dùng cho:** các chỉnh sửa cơ học tầm thường, hoặc khi một quy ước dự án đã quy định cấu trúc — xem Cổng Tỷ Lệ ở trên. Ưu tiên `karpathy-guidelines` cho kỷ luật thay đổi chính xác và kỹ năng phát triển theo kiểm thử cho mạng lưới an toàn tái cấu trúc, khi có sẵn.
## Các Lăng Kính
Mỗi lăng kính thêm đúng một câu hỏi. Ousterhout là xương sống; các lăng kính khác sửa các điểm mù của nó.
| Kính lúp | Câu hỏi duy nhất nó thêm vào | Khi nào nó ghi đè |
|---|---|---|
| **Ousterhout** — mô-đun sâu | Giao diện này có che giấu nhiều hơn những gì nó phơi bày không? | Xương sống mặc định. |
| **Parnas** — ẩn thông tin | Quyết định thiết kế nào (có khả năng thay đổi) mà mô-đun này che giấu? | *Lý do* một mô-đun nên sâu. Nếu nó không che giấu gì thay đổi, độ sâu chỉ mang tính hình thức. |
| **Brooks** — thiết yếu vs ngẫu nhiên | Điều này có loại bỏ sự phức tạp ngẫu nhiên, hay chỉ di chuyển sự phức tạp thiết yếu của miền? | Loại bỏ các "refactor" chỉ di chuyển sự lộn xộn mà không thu nhỏ nó. |
| **Evans** — Domain-Driven Design | Ranh giới này có được đặt tên bằng ngôn ngữ miền, không phải ngôn ngữ tiện ích chung không? | Đổi tên `utils`/`helpers` — đặt tên ranh giới theo bất biến mà repo này thực sự có. |
| **Fowler** — refactoring / mùi code | Bước nhỏ nhất an toàn để tiến tới thiết kế sâu hơn là gì? | Biến "nên sâu hơn" thành các bước cụ thể sau khi vượt qua các bài test. |
| **Beck** — thiết kế đơn giản, test-first | Tôi đã chứng minh hành vi hiện tại trước khi làm sâu thêm đường nối chưa? | Là phanh ngăn kiến trúc quá sớm. Làm cho nó hoạt động và được test trước, rồi mới làm sâu đường nối đúng. |
| **Hickey** — đơn giản vs dễ dàng | Điều này có trộn lẫn các khái niệm không liên quan, hay thực sự là một khái niệm duy nhất? | Một helper nông thường *dễ* (gần, nhanh), không phải *đơn giản* (ít khái niệm đan xen). Ưu tiên đơn giản. |
| **Metz** — trùng lặp hơn trừu tượng sai | Mã lặp lại này có bảo vệ một bất biến chung, hay chỉ giống nhau (quy tắc của chương trình này, mở rộng Metz)? | Metz: trùng lặp rẻ hơn trừu tượng sai — nhúng lại trừu tượng sai thay vì uốn nó. Chương trình này mở rộng: **không** tập trung chỉ vì nó lặp lại; chỉ tập trung khi nó bảo vệ một bất biến thực sự. Chịu đựng trùng lặp cho đến khi bất biến lộ diện. |
| **Định luật Hyrum** — hành vi quan sát được | Người gọi có dựa vào hành vi vượt quá hợp đồng giao diện này không? | Ủng hộ giao diện nhỏ, ổn định: mọi hành vi quan sát được cuối cùng đều trở thành trọng tải. |
## Công thức kết hợp
Áp dụng theo thứ tự này — các kính lúp sau chỉ quan trọng khi các kính lúp trước đã đạt:
1. **Metz — cổng vào thừa nhận.** Ranh giới/trừu tượng này có xứng đáng tồn tại không? Quy tắc của chương trình này, mở rộng Metz: chỉ trích xuất khi mã bảo vệ một quy tắc chung — ba cái giống nhau không phải là bất biến lộ diện. Nếu không, dừng lại.
2. **Parnas / Ousterhout** — Ẩn quyết định dễ thay đổi (phạm vi ủy quyền, quy tắc làm tròn, bảo vệ chuyển đổi, quy tắc giữ lại) phía sau một mô-đun sâu.
3. **Evans** — Đặt tên mô-đun đó bằng ngôn ngữ miền, không phải `utils`.
4. **Beck / Fowler** — Với mã hiện có, cố định hành vi hiện tại bằng test, rồi refactor theo các bước nhỏ an toàn. Với mã mới tạo, không có hành vi hiện tại để cố định — viết test định nghĩa hành vi mong muốn.
5. **Hickey** — Từ chối các giao diện trộn lẫn các khái niệm không liên quan chỉ vì quy trình làm việc trông giống nhau.
## Mẫu chống cấu trúc
**SOLID / Clean Code cơ học tạo ra các mô-đun nông.** Đọc giáo điều —
mỗi lớp một trách nhiệm, trích xuất mọi hàm, giữ mọi thứ nhỏ —
cho ra một đám lớp có giao diện phức tạp như thân lớp. Khi
một quy tắc nói "chia nhỏ", hãy hỏi quyết định *nào* mà việc chia nhỏ che giấu (Parnas) và
nó có che giấu nhiều hơn những gì nó phơi bày không (Ousterhout). Nếu nó không che giấu gì thay đổi, đừng chia. Cái bảo vệ này quan trọng nhất khi bị áp lực refactor ("dọn dẹp", "file này quá lớn") — trong phân tích bình tĩnh, người đánh giá đã chống lại; giữa lúc refactor, với mệnh lệnh tạo ra thay đổi rõ ràng, là lúc đám file nông được viết ra.
## Sai lầm phổ biến
- **Chia nhỏ chỉ dựa trên kích thước.** Một mô-đun truy vấn 400 dòng che giấu một quyết định nhất quán có thể sâu hơn bốn mô-đun 100 dòng mỗi cái rò rỉ cùng các phép nối.
- **Đặt tên chia nhỏ là `helpers`/`utils`.** Nếu bạn không thể đặt tên bằng ngôn ngữ miền (Evans), ranh giới có thể sai.
- **Trích xuất ngay lần xuất hiện thứ hai.** Quy tắc của chương trình này, mở rộng Metz: chờ bất biến, không phải lần xuất hiện thứ ba.
- **Làm sâu trước khi cố định hành vi.** Beck: không có test chứng minh hành vi hiện tại, refactor "làm sâu" là viết lại.
- **Đếm một pass-through là một mô-đun.** Một wrapper chuyển tiếp đối số không che giấu gì — theo định nghĩa là nông.
- **Nhầm lẫn vần điệu với bất biến.** Bằng chứng tốt nhất của bất biến chung là thay đổi cùng nhau: các bản sao đã được sửa hoặc thay đổi cùng lúc trong lịch sử (cùng một lỗi được sửa ở hai nơi). Các cái giống nhau thay đổi độc lập là vần điệu; để chúng trùng lặp.
- **Dọn mùi thay vì loại bỏ nó.** Tập trung sáu phép ép kiểu thành một helper ép kiểu chung là phiên bản gọn gàng của cùng một sự mơ hồ. Sửa sâu là đặt tên ranh giới mà phép ép kiểu che đậy.
## Chi phí người đọc: bài kiểm tra thứ ba
Độ sâu và bất biến quyết định ranh giới có nên tồn tại không. Chi phí người đọc
quyết định mã xung quanh có dễ thay đổi không. Người đọc tiếp theo, là người hay agent,
trả giá cho từng dòng họ phải tải để thay đổi an toàn.
Agent trả bằng token và điều hướng bằng tìm kiếm văn bản, đọc một phần và
vòng kiểm tra kiểu/test, nên cùng lỗi tốn họ nhiều hơn. Hãy hỏi:
- **Có thể tìm thấy?** Một tên cho mỗi khái niệm, viết giống nhau ở mọi nơi, có thể tìm bằng tìm kiếm văn bản thuần túy. Khuyết điểm: tên được ghép từ các chuỗi, kết nối bằng hiệu ứng phụ của import, chuỗi re-export che giấu định nghĩa, hai tên cho một khái niệm.
- **Người đọc có thể dừng sớm không?** Hợp đồng nằm ở đầu file hoặc trên phần export: những gì nó hứa, những gì nó ẩn, những gì nó không bao giờ làm. Khuyết điểm: hợp đồng chỉ có thể suy ra bằng cách đọc phần thân.
- **Có thể kiểm tra bằng máy không?** Kiểu dữ liệu chính xác vào và ra ở mọi ranh giới, để kiểm tra kiểu thay thế việc đọc các caller. Khuyết điểm: `any`, dictionary trần, cờ boolean mà ý nghĩa nằm trong phần thân.
- **Có thể thấy sự kết nối không?** Những chỗ phải thay đổi cùng nhau được bắt buộc (một kiểu chia sẻ, một bài test, một nguồn duy nhất) hoặc, nếu không, được đánh dấu ở cả hai nơi. Bằng chứng của sự kết nối ẩn là sự thay đổi đồng thời trong lịch sử mà không có gì trong code đề cập.
- **Không có nhiễu?** Không có comment lặp lại code, không có code bị comment, không có nhánh chết, không có comment lịch sử thay đổi, không giữ đường dẫn lỗi thời bên cạnh bản thay thế.
- **Dự đoán được?** Bố cục theo mẫu hiện có của repo; test nằm ở nơi người đọc sẽ tìm và chạy độc lập.
Kích thước file không được đề cập cố ý. Một file rất lớn là lý do để tìm một quyết định ẩn thứ hai, không bao giờ là lý do để cắt: người đọc có thể tìm kiếm và đọc một phạm vi, và việc tách mà không ẩn gì thêm sẽ thêm giao diện mà không giảm tải.
Đối với các dấu hiệu trong code và bản đồ mã repo, sử dụng `context-audit` nếu có: neo `AIDEV-NOTE:` của nó (một sự thật không thể phục hồi cộng với tham chiếu nguồn gốc, tối đa hai dòng, tại chỗ) là quy ước cho sự kết nối không thể bắt buộc.
## Tái cấu trúc Codebase Hiện Có theo Tiêu Chuẩn Này
Việc cải tạo được đánh giá giống như code mới; điểm khác là thứ tự và sự kiềm chế. Phần lớn codebase nên để nguyên.
1. **Điều tra, chỉ đọc.** Liệt kê các ranh giới (module, dịch vụ, helper chia sẻ). Với mỗi bản ghi: quyết định nó ẩn, hoặc "không có"; kích thước giao diện so với phần thân; đối tác thay đổi đồng thời từ lịch sử; khuyết điểm chi phí đọc. Chưa thay đổi gì.
2. **Xếp hạng theo tần suất thay đổi, không phải theo xấu xí.** Ưu tiên là tần suất code thay đổi nhân với chi phí đọc. Code lạnh hoạt động giữ nguyên, dù nông cạn. Độ phức tạp miền thiết yếu giữ nguyên (Brooks).
3. **Gán một biện pháp cho mỗi phát hiện:**
- lớp trung gian hoặc wrapper không ẩn gì: xóa nó, caller dùng trực tiếp cái nó bọc;
- trừu tượng sai lệch bởi cờ và trường hợp đặc biệt: nhúng lại (Metz), rồi tìm invariant thật;
- anh em nông cạn chia sẻ một quyết định: gộp lại sau một giao diện;
- quyết định rò rỉ (caller biết định dạng, quy tắc, schema): kéo xuống module sở hữu;
- tên chung chung (`utils`, `helpers`, `manager`): đổi tên theo quyết định nó ẩn, hoặc hòa tan vào caller;
- ranh giới không kiểu: gán kiểu, thay cast bằng mapper mà nó che đậy;
- kết nối ẩn: bắt buộc hoặc đánh dấu cả hai nơi;
- nhiễu: xóa nó.
Các rhyme thay đổi độc lập không có biện pháp.
4. **Khóa hành vi trước.** Không biện pháp nào bắt đầu cho đến khi test chứng minh hành vi hiện tại của code nó chạm tới (Beck). Refactor giữ nguyên hành vi; thay đổi hành vi là commit riêng.
5. **Chia công việc thành đơn vị một agent có thể hoàn thành một mình.** Một ranh giới mỗi đơn vị. Mỗi đơn vị đặt tên file nó sở hữu, hợp đồng phải giữ, và lệnh chứng minh nó độc lập. Không hai đơn vị đồng thời viết cùng file; file chia sẻ (barrel, registry, bảng route) có chủ sở hữu duy nhất hoặc chờ tích hợp. Thay đổi giao diện mà nhiều đơn vị phụ thuộc được thực hiện trước, như đơn vị riêng.
6. **Đo kết quả.** Chọn một thay đổi đại diện trước khi bắt đầu và đếm file, dòng người đọc phải tải để làm; đếm lại sau. Tên export và tổng dòng nên giảm hoặc giữ nguyên. Refactor thêm giao diện phải có lý do rõ ràng.
7. **Dừng** khi phần còn lại là lạnh, thiết yếu hoặc rhyme.
Các kỹ năng liên quan, nếu có: `repo-review` (kiểu thiết kế) tạo điều tra như artifact chỉ tư vấn; `design-cleanup` chạy vòng fix và quét lại cho phức tạp ngẫu nhiên; `context-audit` thêm neo và bản đồ mã; `ousterhout-build-deep` là checklist thời gian tác giả cho agent làm đơn vị.
## Vị trí của kỹ năng này
Kỹ năng này là lớp đánh giá và phán xét: dùng để quyết định trừu tượng có sâu, đặt tên đúng quyết định, và đáng để tách ra không. `find-shared-code` dùng nó làm bài kiểm tra đầu vào khi quét lịch sử gần đây tìm code đáng chia sẻ. Phụ lục dưới đây trình bày lý luận của từng tác giả.
---
## Phụ Lục: Các Kính Lúp Chi Tiết
Chế độ lỗi mỗi tác giả bắt, và một bước mỗi người cho bạn. Bảng trên là tham khảo nhanh; đây là lý luận phía sau.
### Ousterhout — Module Sâu (xương sống)
*Triết lý Thiết kế Phần mềm.*
- **Độ sâu** = lợi ích (chức năng ẩn) ÷ chi phí (độ phức tạp giao diện). Module sâu ẩn nhiều sau ít. Module nông giao diện gần bằng độ phức tạp phần thân, nên không có lợi.
- **Độ phức tạp** là bất cứ điều gì về hệ thống làm khó hiểu hoặc sửa đổi. Hai nguồn:
- **Phụ thuộc** — không thể thay đổi một phần mà không chạm phần khác.
- **Mờ mịt** — thông tin quan trọng không rõ ràng từ code.
- **Triệu chứng:** khuếch đại thay đổi (một quyết định, nhiều chỉnh sửa), tải nhận thức (bao nhiêu phải giữ trong đầu), không biết không biết (không biết thay đổi ảnh hưởng code nào).
- **Bước chính:** kéo độ phức tạp *xuống dưới* — module hấp thụ trường hợp khó để caller không phải lo. Tham số cấu hình và pass-through đẩy độ phức tạp *lên* caller; đó là nông cạn.
Bắt: giao diện rò rỉ hiện thực; helper không giúp gì.
### Parnas — Ẩn Thông Tin (tại sao độ sâu quan trọng)
*Về Tiêu chí Sử dụng để Phân rã Hệ thống thành Module (1972).*
- Phân rã quanh **các quyết định thiết kế có khả năng thay đổi**, không phải quanh các bước của
một phép tính. Mỗi module ẩn một quyết định như vậy.
- Đây là tổ tiên trực tiếp của module sâu. Một module là sâu *bởi vì* nó
ẩn một quyết định mà nếu không sẽ lan tỏa qua các caller.
Những điểm cần lưu ý: một "module" không ẩn gì thay đổi — độ sâu của nó chỉ mang tính trang trí. Hãy hỏi:
điều gì thay đổi phía sau giao diện này mà caller không bao giờ thấy? Nếu câu trả lời là
"không có gì," thì ranh giới đó chỉ là sự trang trí.
### Brooks — Độ phức tạp thiết yếu và độ phức tạp ngẫu nhiên
*Không có viên đạn bạc.*
- Độ phức tạp **thiết yếu** vốn có trong miền (đánh giá thực sự phức tạp như vậy). Độ phức tạp **ngẫu nhiên** là những gì công cụ và cấu trúc của chúng ta áp đặt.
- Chỉ có độ phức tạp ngẫu nhiên mới có thể loại bỏ được. Một lần refactor "dọn dẹp" bằng cách chuyển độ phức tạp miền thiết yếu từ file này sang file khác thì không làm được gì.
Những điểm cần lưu ý: việc sắp xếp lại được ngụy trang thành đơn giản hóa. Hãy hỏi: tổng độ phức tạp có giảm không,
hay chỉ là nó di chuyển?
### Evans — Domain-Driven Design
*Domain-Driven Design.*
- Ranh giới nên được đặt tên theo **ngôn ngữ phổ biến** của miền, không phải theo các thuật ngữ tiện ích chung chung. Một module gọi là `helpers` thì không đặt tên gì; một module gọi là `AccessScope` hoặc `PricingPolicy` thì đặt tên cho một bất biến.
- Các bounded context giữ cho các bất biến kinh doanh không bị rò rỉ qua các mối nối.
Những điểm cần lưu ý: phân rã đúng nhưng tên không có ý nghĩa. Nếu bạn không thể đặt tên module bằng ngôn ngữ miền, có lẽ bạn đã cắt ranh giới sai chỗ.
### Fowler — Refactoring và Mùi Code
*Refactoring.*
- Cung cấp các bước cụ thể, an toàn, có tên (Extract Function, Move Field, Replace Conditional with Polymorphism) để đi từ thiết kế hiện tại đến thiết kế sâu hơn.
- Mỗi bước đều giữ nguyên hành vi và nhỏ, nên có thể đảo ngược.
Những điểm cần lưu ý: khoảng cách giữa "nên sâu hơn" và biết commit tiếp theo là gì.
Ousterhout đặt mục tiêu; Fowler là con đường.
### Beck — Thiết kế đơn giản, Test-First
*Test-Driven Development; XP.*
- Bốn quy tắc thiết kế đơn giản, theo thứ tự Beck công bố: vượt qua test, không trùng lặp, thể hiện ý định, ít phần tử nhất. Chương trình này theo thứ tự Fowler/Haines sau này — ý định trước trùng lặp — vì nó phục vụ quy tắc bất biến mở rộng Metz của chương trình này (xem Metz, bên dưới):
không hành động trên trùng lặp cho đến khi bạn có thể đặt tên ý định mà nó bảo vệ.
- Test-first là phanh chống kiến trúc quá sớm. Làm cho nó chạy và chứng minh hành vi *trước*, rồi mới làm sâu thêm mối nối mà test hiện bảo vệ.
Những điểm cần lưu ý: kiến trúc được xây dựng trước khi hành vi được xác định. Nếu không có test chứng minh hành vi hiện tại, một refactor "làm sâu" là viết lại chưa được kiểm chứng.
### Hickey — Đơn giản và Dễ dàng
*Simple Made Easy.*
- **Đơn giản** = không đan xen: một khái niệm, không lẫn với các khái niệm khác (khách quan).
- **Dễ dàng** = gần bên, quen thuộc, nhanh để sử dụng (tương đối với bạn).
- Hai điều này độc lập. Một helper nông thường *dễ dàng* — nhanh viết, gần bên — nhưng không *đơn giản* nếu nó đan xen các mối quan tâm không liên quan.
Những điểm cần lưu ý: sự tiện lợi giả dạng thành thiết kế. Ưu tiên các cấu trúc giữ khái niệm không đan xen ngay cả khi một cấu trúc đan xen nhanh hơn để gõ.
### Metz — Ưu tiên trùng lặp hơn trừu tượng sai
*"The Wrong Abstraction" (2016).*
- Trùng lặp rẻ hơn nhiều so với trừu tượng sai. Một trừu tượng được trích xuất quá sớm buộc mọi caller tương lai phải uốn theo các giả định không bao giờ đúng với tất cả họ.
- Khi một trừu tượng sai, cách khắc phục của Metz là nhúng lại nó và để trùng lặp quay trở lại, thay vì uốn nó cho vừa một trường hợp mà nó không được xây dựng cho.
- **Quy tắc của chương trình này, mở rộng Metz: không tập trung hóa chỉ vì code bị lặp lại. Tập trung hóa khi nó bảo vệ một bất biến thực sự, được chia sẻ.** Cho đến khi bất biến đó lộ diện, hãy chịu đựng trùng lặp.
Những điểm cần lưu ý: tập trung hóa quá mức — helper chia sẻ nông mà mọi người giờ phải làm việc quanh nó. Đây là đối trọng với "DRY bằng mọi giá" cơ học.
### Luật Hyrum — Hành vi quan sát được trở thành hợp đồng
*"Với đủ số lượng người dùng, mọi hành vi quan sát được của hệ thống bạn
sẽ được ai đó phụ thuộc."*
- Bất cứ điều gì một giao diện *tình cờ* làm — thứ tự, thời gian, văn bản lỗi — ai đó cuối cùng sẽ dựa vào. Vậy bề mặt bạn phơi bày lớn hơn bề mặt bạn đã tài liệu.
- Điều này ủng hộ sở thích của Ousterhout về **giao diện nhỏ, ổn định**: bạn phơi bày càng ít, càng ít có thể vô tình trở thành điểm chịu tải.
Những điểm cần lưu ý: giao diện rộng sẽ cứng nhắc. Mỗi quan sát thêm trở thành ràng buộc trong tương lai.
### Cách Chúng Kết Hợp Với Nhau
- **Parnas → Ousterhout:** ẩn một quyết định dễ thay đổi → module là sâu.
- **Brooks:** xác nhận độ sâu đã loại bỏ độ phức tạp thay vì chỉ di chuyển nó.
- **Evans:** đặt tên ranh giới bằng ngôn ngữ miền.
- **Beck → Fowler:** xác định hành vi, rồi refactor bằng các bước nhỏ an toàn.
- **Metz:** chống tập trung hóa cho đến khi bất biến là thật.
- **Hickey:** giữ giao diện chỉ một khái niệm.
- **Hyrum:** giữ giao diện nhỏ để nó có thể ổn định.
Nguy hiểm là trộn Ousterhout với cách đọc cơ học SOLID hoặc Clean Code:
điều đó tạo ra nhiều lớp và hàm nhỏ với giao diện nông — hoàn toàn trái ngược với module sâu.
Ousterhout, với Metz làm đối trọng, là liều thuốc giải độc.
Tag
Tìm hiểu thêm
Claude Ads: skill Claude Code kiểm toán tài khoản quảng cáo của bạn
Claude Ads là skill mã nguồn mở cho Claude Code: hơn 250 kiểm tra trên Google, Meta, LinkedIn, TikTok hay Amazon Ads, điểm số trên thang 100 và kế hoạch hành động ưu tiên, trong khoảng mười phút. Cài đặt, lệnh, giới hạn và cách điều phối trong AgentsRoom.
AGENTS.md: một file context cho mọi coding agent (Codex, Antigravity, Claude)
AGENTS.md là file hướng dẫn di động mà các coding agent của bạn đọc trước khi đụng vào code. Nên ghi gì trong đó, nó khác CLAUDE.md ở chỗ nào, và làm sao giữ một context duy nhất xuyên suốt Codex, Antigravity và Claude.
Tải AgentsRoom
Chạy tất cả agent AI của bạn, trên mọi dự án, trong một cửa sổ duy nhất.
Ứng dụng đồng hành: theo dõi agent khi đi đường
Sử dụng Claude, Codex, Antigravity CLI hoặc nhà cung cấp AI khác.
Gửi lỗi và yêu cầu thẳng vào backlog công khai của bạn.