a-philosophy-of-software-design

bởi Rob ZappChưa có lượt cài đặtChưa có lượt thíchCập nhật 8 tháng 10, 2026Danh mục: Kỹ thuật

Nó làm gì

Sử dụng khi viết, thay đổi hoặc xem xét mã mỗi khi thay đổi thêm một tên được xuất hoặc có thể nhập, tạo một module, lớp, thành phần, helper, hook, dịch vụ hoặc wrapper, tập trung mã lặp lại, hoặc thay đổi API. Các quy tắc của Ousterhout (module sâu, ẩn thông tin, kéo phức tạp xuống) cùng với bài kiểm tra bất biến để chia sẻ mã, bài kiểm tra chi phí đọc, và một ghi chú thiết kế bắt buộc ở cuối.

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: a-philosophy-of-software-design
description: Sử dụng khi viết, thay đổi hoặc xem xét mã mỗi khi thay đổi thêm một tên được xuất hoặc có thể nhập, tạo một module, lớp, thành phần, helper, hook, dịch vụ hoặc wrapper, tập trung mã lặp lại, hoặc thay đổi API. Các quy tắc của Ousterhout (module sâu, ẩn thông tin, kéo phức tạp xuống) cùng với bài kiểm tra bất biến để chia sẻ mã, bài kiểm tra chi phí đọc, và một ghi chú thiết kế bắt buộc ở cuối.
---

# A Philosophy of Software Design (John Ousterhout)

## Khi nào sử dụng kỹ năng này

Sử dụng kỹ năng này khi bạn thiết kế, viết, thay đổi hoặc xem xét mã. Nó áp dụng cho thiết kế module, thay đổi API, phân rã, tái cấu trúc, tên gọi, chú thích, kiểm thử và công việc về hiệu năng. Cũng sử dụng khi một thay đổi cảm thấy khó xử, hoặc khi một thay đổi lan rộng qua nhiều tệp.

## Thiên kiến cần sửa

Mã hoạt động không đồng nghĩa với mã đơn giản. Những mảnh nhỏ, mẫu quen thuộc, cờ hiệu, lớp bao bọc và tài liệu bổ sung có thể làm thiết kế phức tạp hơn. Chúng làm vậy khi thêm vào những gì người đọc phải biết, hoặc khi rò rỉ kiến thức sang các module khác.

## Quy tắc quyết định

- Đo lường một thiết kế bằng mức độ nó giảm độ phức tạp. Ưu tiên thiết kế làm giảm tải cho người đọc. Độ phức tạp có bốn dấu hiệu. Một thay đổi cần chỉnh sửa ở nhiều nơi. Các phụ thuộc bị ẩn. Các bước phải xảy ra theo thứ tự cố định. Người đọc phải giữ nhiều sự thật trong đầu.
- Xem thiết kế như công việc liên tục. Một bản vá đầu tiên hoạt động chưa phải là hoàn chỉnh nếu nó làm các thay đổi sau này khó hơn. Đối với quyết định về giao diện, phân tách module hoặc trừu tượng hóa, so sánh hai hoặc nhiều thiết kế khả thi.
- Ưu tiên module sâu. Một module sâu có giao diện nhỏ và ẩn một lượng lớn độ phức tạp. Loại bỏ các dịch vụ chuyển tiếp, lớp bao thư viện mỏng và các module trợ giúp nhỏ. Loại bỏ bất kỳ trích xuất nào thêm tên nhưng không giảm tải cho người đọc.
- Thiết kế giao diện xoay quanh những gì người gọi phải biết, không phải cách triển khai hoạt động. Tránh các chuỗi thiết lập dễ vỡ, cờ chế độ, núm cấu hình và các đối số thể hiện lựa chọn nội bộ.
- Ẩn các quyết định có thể thay đổi. Ví dụ là biểu diễn nội bộ, hình dạng lưu trữ, giao thức, định dạng tệp và mẹo hiệu năng. Quản lý, chuẩn hóa và các trường hợp biên là ví dụ khác. Giữ mỗi cái bên trong module sở hữu kiến thức đó.
- Kéo độ phức tạp xuống module sở hữu chi tiết. Chấp nhận một triển khai phức tạp hơn khi nó mang lại hợp đồng đơn giản hơn cho người gọi và loại bỏ công việc lặp lại ở mỗi điểm gọi.
- Làm cho module tổng quát ở mức độ phù hợp. Không thiết kế module chỉ cho một người gọi. Không thêm trừu tượng mơ hồ cho nhu cầu tương lai. Giữ các trường hợp biên hiếm ra khỏi đường chính, và đặt hành vi đặc biệt vào nơi riêng.
- Kết hợp hoặc tách module dựa trên tổng độ phức tạp. Không kết hợp hoặc tách dựa trên kích thước, thứ tự chạy mã, thói quen hoặc vẻ ngoài. Giữ trạng thái, hành vi, quy tắc và quyết định liên quan cùng nhau. Chỉ tách khi ranh giới mới sâu hơn và người đọc có thể hiểu mỗi bên riêng.
- Giảm số ngoại lệ. Nếu có thể, thay đổi giao diện hoặc quy tắc để trạng thái không hợp lệ không xảy ra. Không bắt mỗi người gọi phải lặp lại cùng mã phòng thủ.
- Dùng chú thích để giảm độ phức tạp. Ghi lại hợp đồng giao diện, quy tắc phải giữ đúng, quyết định thiết kế ẩn và lý do của chúng. Cũng ghi lại các sự thật khó mà người gọi không cần biết. Không lặp lại mã trong chú thích. Không dùng chú thích để che giấu tên xấu, phân tách xấu hoặc luồng điều khiển gây nhầm lẫn.
- Xem tên gọi, tính nhất quán và rõ ràng như thông tin thiết kế. Tên gọi cho người đọc biết trừu tượng, không phải cơ chế. Các thao tác liên quan dùng cùng quy ước. Mã làm người đọc ngạc nhiên làm tăng độ phức tạp, dù nó ngắn.
- Viết kiểm thử dựa trên hợp đồng công khai và API ổn định. Kiểm thử độ phức tạp ẩn và các trường hợp đặc biệt qua các hợp đồng đó. Không để sự dễ dàng của kiểm thử ép buộc giao diện nông hoặc rò rỉ.
- Thêm thay đổi hiệu năng, mẫu, mô hình hoặc framework chỉ vì một trong hai lý do. Nó giảm độ phức tạp trong codebase này, hoặc có bằng chứng cho thấy đánh đổi là cần thiết. Ẩn mỗi tối ưu hóa sau giao diện ổn định.

## Tín hiệu và phản ứng tương ứng

- Một tính năng khó xử, hoặc một thay đổi lan rộng qua nhiều tệp, hoặc người xem xét phải tìm phụ thuộc ẩn. Phản ứng: tìm kiếm việc ẩn thông tin thiếu và module nông. Cũng tìm các bước theo thứ tự cố định, và độ phức tạp mà người gọi phải mang.
- Bạn thêm module, lớp, dịch vụ, trợ giúp, lớp bao hoặc mặt tiền. Hoặc bạn thêm mẫu, tùy chọn, callback hoặc đối số. Phản ứng: chứng minh nó ẩn nhiều độ phức tạp hơn số nó thêm.
- Bạn thay đổi API. Phản ứng: kiểm tra những gì người gọi bình thường phải biết. Người gọi không cần thứ tự gọi, biểu diễn hay lưu trữ. Người gọi không cần vận chuyển, bộ nhớ đệm, giao thức hay định dạng tệp. Người gọi không cần luồng công việc nội bộ hay nhiều bước thiết lập.
- Bạn thêm trường hợp đặc biệt, cờ, đường ngoại lệ, điều kiện hoặc container mà người gọi có thể thấy. Phản ứng: trước tiên hỏi module sở hữu có thể làm gì thay thế. Nó có thể loại bỏ trạng thái không hợp lệ, cô lập hành vi bất thường hoặc cung cấp thao tác mạnh hơn.
- Bạn tách mã, trích xuất hàm hoặc thêm biến. Phản ứng: kiểm tra ranh giới hoặc tên mới có ý nghĩa. Nó không chỉ thêm bước nhảy, trạng thái truyền qua hoặc bước trung gian mà người gọi có thể thấy.
- Mã có các pha như `prepare`, `process` và `finalize`, hoặc người gọi phải xây dựng đối tượng theo giai đoạn. Phản ứng: kiểm tra thứ tự thời gian có phải khái niệm thực sự không. Nếu không, tổ chức mã quanh trách nhiệm ổn định.
- Tên gọi mơ hồ, tên cơ chế, không nhất quán hoặc làm người đọc ngạc nhiên. Phản ứng: suy nghĩ lại về ranh giới trừu tượng. Không chấp nhận tên gần đúng.
- Chú thích dài, lặp lại mã, giải thích giao diện gây nhầm lẫn hoặc hiển thị nội bộ để giải thích cách dùng. Phản ứng: thay đổi trừu tượng, hoặc chuyển hợp đồng thiếu vào giao diện.
- Bạn tối ưu hiệu năng. Phản ứng: đo lường trước, rồi ẩn tối ưu. Không từ bỏ độ sâu module hoặc ẩn thông tin nếu không có bằng chứng đánh đổi cần thiết.
- Bạn kiểm thử hoặc xem xét. Phản ứng: xem hành vi công khai và hợp đồng giao diện. Cũng xem độ phức tạp ẩn sau API ổn định, và các trường hợp đặc biệt giữ sau trừu tượng.

## Danh sách kiểm tra cuối cùng

- Thay đổi có làm giảm nỗ lực để hiểu, thay đổi, xác minh và mở rộng hệ thống không?
- Mỗi phần tử giao diện, wrapper, lớp, helper, tùy chọn và tên có che giấu đủ độ phức tạp để biện minh cho nó không?
- Các quyết định quan trọng có tập trung ở một nơi không? Các phụ thuộc có hiển thị không? Các ràng buộc mà người gọi cần có được ghi lại không? Các phần bên trong có thể thay đổi có được bảo vệ không?
- Các trường hợp phổ biến có hoạt động mà không cần bước bổ sung không? Các điều khiển hiếm, trường hợp đặc biệt, mẹo hiệu suất và chi tiết ngoại lệ có tránh khỏi đường đi chung không?
- Tên có chính xác và nhất quán không? Bình luận có cập nhật, không lặp lại mã không? Mã có tuân theo các quy ước hiện có, trừ khi có thông tin mới cho lý do thay đổi không?

## Cổng kiểm soát

Sử dụng danh sách kiểm tra đầy đủ khi thay đổi thêm một tên mà mã khác có thể xuất hoặc nhập. Cũng sử dụng khi thay đổi tạo một module, lớp, thành phần, helper, hook, dịch vụ hoặc wrapper, hoặc đặt mã lặp lại vào một nơi. Đổi tên, codemod, thay đổi cấu hình, thay đổi dữ liệu và sửa lỗi một dòng không cần dùng.

## Kiểm tra bất biến: chỉ chia sẻ mã thay đổi cùng nhau

- Chỉ trích xuất mã dùng chung khi nó bảo vệ một quy tắc mà bạn có thể đặt tên. Bằng chứng là sự thay đổi đồng thời: lịch sử cho thấy các bản sao được sửa hoặc thay đổi cùng nhau. Mã chỉ trông giống nhau và thay đổi độc lập là vần điệu. Để vần điệu như bản sao. Ba khối tương tự không chứng minh một quy tắc.
- Một sửa lỗi phải loại bỏ vấn đề, không di chuyển nó. Sáu phép ép kiểu chuyển vào một helper ép kiểu tổng quát vẫn là sáu phép ép kiểu. Viết bộ ánh xạ kiểu mà các phép ép kiểu đang che giấu.
- Khi một trừu tượng sai, đặt mã trở lại inline và để sự trùng lặp quay lại. Đừng uốn cong trừu tượng với các cờ.
- Đừng tách mã chỉ vì kích thước. Một module 400 dòng che giấu một quyết định tốt hơn bốn module 100 dòng rò rỉ cùng các liên kết.
- Đọc máy móc Clean Code hoặc SOLID (hàm rất nhỏ, một lớp cho mỗi trách nhiệm) tạo ra các module nông. Kỹ năng này ưu tiên hơn áp lực đó.

## Chi phí người đọc: kiểm tra thứ ba

Kiểm tra độ sâu và kiểm tra bất biến quyết định liệu một ranh giới có phải tồn tại. Kiểm tra chi phí người đọc quyết định liệu mã quanh ranh giới có dễ thay đổi không. Người đọc tiếp theo, là người hoặc agent, trả giá cho mỗi dòng họ phải đọc. Agent trả bằng token. Agent tìm mã bằng tìm kiếm văn bản, đọc một phần, kiểm tra kiểu và kiểm thử.

- **Dễ tìm.** Dùng một tên cho mỗi khái niệm. Viết chính xác như nhau ở mọi nơi, để tìm kiếm văn bản thuần tìm thấy. Lỗi: tên xây dựng từ chuỗi, nối dây qua hiệu ứng phụ import, hai tên cho một khái niệm. Chuỗi tái xuất ẩn định nghĩa cũng là lỗi.
- **Dừng sớm.** Đặt hợp đồng ở đầu file hoặc trên phần xuất. Nói nó hứa gì, che giấu gì và không bao giờ làm gì. Rồi người đọc có thể dừng sớm.
- **Kiểm tra máy.** Dùng kiểu chính xác vào và ra mỗi ranh giới, để kiểm tra kiểu thay thế việc đọc người gọi. Lỗi: `any`, dictionary thuần, cờ boolean chỉ có ý nghĩa trong thân hàm.
- **Phụ thuộc hiển thị.** Hai nơi phải thay đổi cùng nhau. Thực thi bằng kiểu chia sẻ, kiểm thử hoặc nguồn duy nhất. Nếu không được, đánh dấu ở cả hai nơi.
- **Không gây nhiễu.** Loại bỏ bình luận lặp lại mã, và mã bị chú thích. Loại bỏ nhánh chết và bình luận ghi lại lịch sử thay đổi. Loại bỏ đường đi cũ nằm cạnh thay thế.
- **Dự đoán được.** Theo bố cục hiện có của kho mã. Đặt kiểm thử nơi người đọc tìm, và làm cho nó chạy riêng.

Kích thước file không nằm trong danh sách này có chủ ý. File rất lớn là lý do để tìm quyết định ẩn thứ hai. Không bao giờ là lý do để cắt file.

## An toàn

Với mã hiện có, trước tiên viết kiểm thử giữ hành vi hiện tại. Rồi làm module sâu hơn. Với mã mới, viết kiểm thử định nghĩa hành vi dự kiến.

## Ghi chú thiết kế (bắt buộc khi cổng kiểm soát áp dụng)

Khi cổng kiểm soát áp dụng, đặt phần với tiêu đề `## Design note` trong mô tả pull request. Viết hai đến bốn dòng:

- Mỗi ranh giới bạn thêm, và quyết định nó che giấu.
- Mỗi sự trùng lặp bạn giữ lại có chủ đích, và lý do.
- Mỗi phần nông bạn chấp nhận, và lý do.

Nếu cổng không áp dụng, viết `## Design note` theo sau là `Gate not applicable: <lý do>`. Cũng đặt ghi chú thiết kế trong tóm tắt bước cuối cùng của bạn.

## Chế độ xem xét

Dùng phần này khi bạn xem xét hoặc kiểm thử mã do agent hoặc người khác viết.

1. Kiểm tra ghi chú thiết kế. Khi cổng áp dụng và pull request không có phần `## Design note`, báo cáo lỗi chặn. Khi ghi chú không khớp với diff, báo cáo lỗi chặn.
2. Một phát hiện thiết kế chỉ chặn khi thỏa cả hai điều kiện:
   - Nó đặt tên một quy tắc của kỹ năng này. Quy tắc là quy tắc quyết định, cổng, kiểm tra bất biến hoặc một mục chi phí người đọc.
   - Nó nêu chi phí cụ thể cho người đọc hoặc thay đổi tiếp theo. Ví dụ: "Người gọi phải biết hình dạng lưu trữ." "Một khái niệm có hai tên." "Một thay đổi giới hạn cần sửa ba file."
3. Đánh dấu mọi quan sát thiết kế khác là không chặn. Đặt nó trong danh sách riêng với tiêu đề "Ghi chú thiết kế không chặn". Ghi chú không chặn không gửi lại công việc cho người xây dựng.
4. Đừng báo cáo sở thích như một phát hiện. Tên khác, bố cục file hoặc phong cách là sở thích. Nó chỉ thành phát hiện khi vi phạm quy tắc đã đặt tên và có chi phí cụ thể.
5. Khi cùng một phát hiện thiết kế quay lại trong chu kỳ xem xét thứ hai, nâng cấp nó. Đừng yêu cầu thay đổi giống nhau lần thứ ba.

## Kỹ năng liên quan (khi đã cài)

- `find-shared-code`: tìm kiếm chỉ báo cáo lịch sử gần đây cho mã đáng chia sẻ. Nó dùng kiểm tra bất biến và kiểm tra độ sâu của kỹ năng này.
- `refactoring` và `working-effectively-with-legacy-code`: các bước an toàn hướng tới thiết kế sâu hơn. Kỹ năng này quyết định liệu ranh giới mới có giữ lại.

## Nguồn và giấy phép

Kỹ năng này xây dựng dựa trên các quy tắc "mini" cho A Philosophy of Software Design trong kho lưu trữ ciembor/agent-rules-books trên GitHub (giấy phép MIT, commit 893a88a). Cổng, bài kiểm tra bất biến, bài kiểm tra chi phí đọc, ghi chú thiết kế và chế độ xem lại là những bổ sung cho các quy tắc đó. Kho lưu trữ cũng chứa đầy đủ các quy tắc của cuốn sách.

Tag

designarchitectureousterhoutreview