hoangquochung1110 / public-notes

0 stars 0 forks source link

A philosophy of software design #8

Open hoangquochung1110 opened 3 months ago

hoangquochung1110 commented 3 months ago

Classification

hoangquochung1110 commented 3 months ago

Khả năng nhận biết độ phức tạp là một kĩ năng then chốt. Nhằm có quyết định đúng đắn giữa các lựa chọn thiết kế

Các biểu hiện của complexity

  1. Change amplification: Một thay đổi đơn giản đòi hỏi phải sửa đổi code ở nhiều nơi khác nhau.
  2. Cognitive load (tải nhận thức): mức độ kiến thức mà dev cần biết để thực hiện 1 task. Cognitive load cao tức 1 người cần dành tg hơn để học các thông tin bắt buộc (requisite) -> rủi ro bug cao hơn vì họ có thể đã bỏ lỡ thông tin quan trọng nào đó
  3. Unknown unknowns: tức có những thứ chúng ta cần phải biết nhưng ko có cách nào để tìm ra nó, thậm chí nhận thức đc sự tồn tại của nó.
hoangquochung1110 commented 3 months ago

Các tác nhân gây nên complexity

Ở high level, có 2 tác nhân chính:

Dependencies

Obscurity

Nếu chúng ta tìm ra các kĩ thuật thuyết kế mà giảm thiểu dependencies và obscurity thì có thể tạo ra hệ thống đơn giản hơn

hoangquochung1110 commented 3 months ago

Hai cách tiếp cận chính để giảm complexity

  1. Làm code đơn giản (simple) và rành mạch (obvious) hơn
  2. Modular design

Modules cần phải sâu (Modular design)

Module ở đây tức bất kì đơn vị code nào mà có interface và implementation. Nếu interface ở 1 module đơn giản hơn implementation của nó, càng có nhiều cơ hội để module này đc sửa đổi mà ko ảnh hưởng đến module khác

Information Hiding (and Leakage)

hoangquochung1110 commented 3 months ago

Mindset tiếp cận

Thay đổi mindset từ tactical programming sang strategic programming vì working code thôi thì chưa đủ.

Tactical programming:

Strategic programming: yêu cầu tư duy đầu tư, tức dành nhiều thời gian cải thiện thiết kế hệ thống thay vì tìm đường đi ngắn nhất để hoàn thành 1 tác vụ.

Có 2 hình thức đầu tư chính: đầu tư chủ động (proactive) và đầu tư phản ứng (reactive). Đầu tư chủ động: dành thời gian để thử nhiều các lựa chọn thiết kế khác nhau trước khi chọn 1 Đầu tư phản ứng: khi có bug trong hệ thống, thay vì vá hay thậm chí làm ngơ thì dành thêm thời gian để sửa lỗi 1 cách triệt để

hoangquochung1110 commented 3 months ago

Modular Design

Ở thiết kế module, hệ thống phần mềm đc phân tách thành nhiều module mà một cách tương đối chúng độc lập với nhau. Tuy nhiên ở đk thực tế điều này ko xảy ra, các module thường phải gọi các method của nhau để hoàn thành 1 tác vụ, vì vậy dependencies giữa chúng là ko thể tránh khỏi.

💼 Quản lí, giảm thiểu dependencies giữa các module trong hệ thống: Dependencies giữa các module có thể hiểu: khi 1 module sửa đổi, các module khác có thể cần phải sửa đổi theo. Ví dụ, module A thay đổi 1 argument bắt buộc ở 1 method của nó, tất cả các module khác mà gọi method này buộc phải thay đổi theo để đáp ứng đúng signature của method.

💼 Xây dựng module có chiều sâu (Deep module): Bằng cách tách biệt interface (của module) khỏi implementation (của nó), chúng ta có thể che giấu đi complexity khỏi phần còn lại của hệ thống.

Ví dụ: cho 1 chiếc máy giặt có các thành phần cấu tạo chính như sau: lồng giặt, motor máy giặt, van cấp nước, dây curoa. Giả sử máy giặt cung cấp 2 chế độ giặt chính cho users (Interface): giặt quần áo và giặt chăn màn. Về bản chất bên trong, máy giặt sẽ cài đặt cái thông số kĩ thuật khác nhau, tương ứng từng chế độ. Ví dụ chế độ quần áo, van cấp nước đổ vào 50L ở nhiệt độ 40 độ C, motor tạo công suất 100W/h trong khi chế độ chăn màn, nó sẽ cung cấp 100L nước tại 100 độ C và yêu cầu motor tạo ra công suất 200W/h. Dĩ nhiên users ko cần hiểu biết sâu về vận hành, việc của họ đơn giản chỉ vặn núm xoay chọn chế độ giặt.

Các nguyên lí thiết kế cơ bản

Module có chiều sâu

Module có thể chia thành 2 thành phần: Interface và Implementation Module có chiều sâu tức: Cung cấp interface đơn giản cho users trong khi hỗ trợ rất nhiều tính năng đằng sau

❌ Các lời khuyên thường đc truyền bá như: class nên ngắn gọn (small), method nên dài dưới N dòng. Thoạt đầu các module này 1 cách đơn lẻ trông đơn giản -> giảm complexity. Tuy nhiên, bản thân chúng ko mang lại nhiều tính năng lớn cho hệ thống -> Cần nhiều chúng, mỗi cái lại có interface khác nhau, khi cộng dồn/tích luỹ chúng lại làm tăng complexity về mặt tổng thể toàn hệ thống. Ngoài ra, việc các module ngắn gọn gọi nhau để hoàn thành 1 tác vụ dẫn đến phong cách lập trình dài dòng (do boilerplate code cần có cho mỗi module)

Information Hiding (and Leakage)

Là 1 trong các kĩ thuật quan trọng để đạt đc module có chiều sâu Các thông tin cần che giấu bao gồm:

Information hiding giảm complexity theo 2 cách:

  1. Đơn giản hoá interface của module, interface là 1 hình thức trừu tượng hoá các tính năng của module -> giảm cognitive load yêu cầu của dev người mà dùng module. Ví dụ
  2. Giúp việc mở rộng hệ thống dễ dàng. Nếu 1 phần thông tin đc che giấu, tức ko có dependencies giữa thông tin đó và các module liên quan. Điều này giúp bất kì sự thay đổi nào liên quan đến thông tin này sẽ chỉ ảnh hưởng chính module của nó mà thôi.

    Interface đơn giản quan trọng hơn so với implementation đơn giản

Dev: ❌ Để users đối diện với sự phức tạp, ví dụ: khi bạn ko rõ về một điều kiện xảy ra, bạn quăng exception và để caller xử lí nó. Hay khi bạn ko chắc về 1 policy đc vận hành như thế nào thì bạn định nghĩa một parameter để control nó và đùn đẩy trách nhiệm tìm ra giá trị phù hợp cho người dùng -> khuếch đại complexity, thay vì một mình bạn (module developer) đối phó vơí nó thì rất nhiều người (users) phải xử lí nó ✅ Dev cần "đấu tranh" (strive/fight) để làm cho việc sử dụng module của người dùng càng đơn giản càng tốt

hoangquochung1110 commented 3 months ago

Nhận biết và phòng tránh các redflag khi thiết kế hệ thống

Các red flags

  1. Shallow module: interface của 1 module ko đơn giản hơn nhiều so với implementation của nó
  2. Information leakage: 1 quyết định thiết kế đc phản ánh ở nhiều modules
  3. Temporal decomposition: Cấu trúc của module/system đc thiết kế dựa trên thứ tự các tác vụ nên đc hoàn thành, thay vì dựa vào Data Hiding
  4. Overexposure: 1 API buộc callers học các tính năng ít dùng để vận hành/sử dụng các tính năng phổ biến
  5. Repetition: Các đoạn code non-trivial (có ý nghĩa, quan trọng) bị lặp đi lăp lại
  6. Special-General Mixture: special-purpose code trộn lẫn với general purpose code
  7. Conjoined Method
  8. Comment Repeats Code
  9. Vague Name
  10. Hard to Pick Name
  11. Hard to Describe
  12. Nonobvious Code: hành vi và ý nghĩa của một đoạn code ko thể đc hiểu 1 cách dễ dàng