Giới thiệu sách: A Philosophy of Software Design

Chào mọi người,

Hôm nay mình muốn giới thiệu đến anh em một cuốn sách cực kỳ giá trị cho bất kỳ ai làm trong lĩnh vực phát triển phần mềm: “A Philosophy of Software Design” của John Ousterhout và Bryan O’Sullivan.

Cuốn sách này không đi sâu vào các pattern hay kỹ thuật cụ thể, mà tập trung vào những nguyên lý cốt lõi và triết lý đằng sau việc thiết kế một hệ thống phần mềm tốt . Từ việc quản lý độ phức tạp, thiết kế module, đến việc viết code dễ đọc và bảo trì, “A Philosophy of Software Design” mang đến những góc nhìn sâu sắc và thực tế, giúp chúng ta suy nghĩ thấu đáo hơn về công việc hàng ngày của mình.

Nếu bạn đang tìm kiếm một nguồn tài liệu giúp nâng cao tư duy thiết kế, xây dựng những ứng dụng bền vững và dễ hiểu, thì đây chắc chắn là một cuốn sách không thể bỏ qua.

P/S: Mình từng áp dụng “Clean Code” và thấy nhiều điểm hay. Tuy nhiên, dần dà mình nhận ra một số khía cạnh mà “Clean Code” chưa hẳn đã bao quát hết. Đến khi đọc “A Philosophy of Software Design”, mình thấy quyển sách này giải thích những điểm đó rất tốt và phù hợp với cách mình tư duy hơn. Mình cảm thấy những nguyên lý trong “Philosophy of Software Design” thiết thực và đáng để mình áp dụng hơn trong công việc.

3 Likes

I haven’t read the book, but your enthusiasm has piqued my interest. So I searched the Web for all the comments and information about Prof. John Ousterhout’s book. Here are some of them. Read them and form your own opinion:

My opinion is that software design is a dynamic process that is determined by the nature of the problem the software is intended to solve.

As the goal grows over time, so does the software. It’s a dynamic process. If the new goal deviates from the original to some extent, complexity also increases. Because changes in the future are unknown at the time of design, it’s nearly impossible to create a generic, architectural software design. Therefore, reducing complexity is difficult. As commentator Tiger Abrodi wrote, “If you write code that seems simple to you, but others consider it complex, then it is complex.”

one of the goals of software design is to reduce the number of dependencies and to make the dependencies that remain as simple and obvious as possible.

This contradicts most IDE frameworks. I’ve seen a lot of Github code and noticed that some approaches were packaged with numerous small modules or interfaces. I doubt they’re all easy to read if you constantly have to jump back and forth between code to find the independences and their function. In other words, it makes the software more obscure and more complex…

Deep and shallow modules. The best modules are deep: they allow a lot of functionality to be accessed through a simple interface. A shallow module is one with a relatively complex interface, but not much functionality: it doesn’t hide much complexity.

2 Likes

Gần đây có 1 cậu sinh viên hỏi em về Clean code. Em có skim lại 1 phần nội dung, và sau 1 số làm việc thì em thấy clean code không quá phù hợp với cá nhân.

Btw cuốn này nghe tên có vẻ hấp dẫn, cám ơn a Đạt giới thiệu.

Nói chung em thấy giờ đọc các sách này và xem xét, triết lý nào phù hợp với cá nhân thì áp dụng, ngoài ra xem xét lập luận để suy nghĩ thêm. Có 1 điều rất đơn giản là không nhất thiết (và thường là không nên) cố gắng áp dụng toàn bộ các nguyên tắc (của bất kỳ sách nào), cơ mà vài năm gần đây em mới nhận ra.

Anh Đạt có thể nói về vài nguyên tắc/nguyên lý/triết lý mà anh thấy hợp lý và hay áp dụng không anh?

Theo anh thì cách viết kiểu idiomatic của Kotlin có gọi là Shallow Module không?
Và nó vi phạm “Design for Readability, Not Writability” không ?

Quan điểm của em là viết kiểu if…else nhìn phát hiểu luôn, dev ngôn ngữ khác nhìn vào vẫn hiểu, còn xài idiomatic đôi khi phải đọc docs rồi viết code chạy ví dụ để hiểu rồi mới dám xài (khi mới switch sang Kotlin)

Ví dụ:

1. coerceAtMost

// Idiomatic
val clamped = brightness.coerceAtMost(100)

// Tường minh
val clamped = if (brightness > 100) 100 else brightness

2. coerceAtLeast

// Idiomatic
val minThreads = requested.coerceAtLeast(1)

// Tường minh
val minThreads = if (requested < 1) 1 else requested

3. coerceIn

// Idiomatic
val percent = raw.coerceIn(0, 100)

// Tường minh
val percent = when {
    raw < 0   -> 0
    raw > 100 -> 100
    else      -> raw
}

4. takeIf

// Idiomatic: trả về filePath nếu tồn tại, ngược lại null
val cfgPath = File("/etc/app.cfg").takeIf { it.exists() }

// Tường minh
val cfgPath = if (File("/etc/app.cfg").exists()) File("/etc/app.cfg") else null

5. takeUnless

// Idiomatic: chỉ lưu log nếu message KHÔNG rỗng
message.takeUnless { it.isBlank() }?.let(::println)

// Tường minh
val finalMsg = if (!message.isBlank()) message else null
if (finalMsg != null) println(finalMsg)

6. require

// Idiomatic: validate input cho hàm công khai
fun createUser(name: String) {
    require(name.length >= 3) { "Name too short" }
    /* … */
}

// Tường minh
fun createUser(name: String) {
    if (name.length < 3) {
        throw IllegalArgumentException("Name too short")
    }
    /* … */
}

7. check

// Idiomatic: bảo đảm trạng thái nội bộ
fun withdraw(amount: Int) {
    check(balance >= amount) { "Insufficient funds" }
    balance -= amount
}

// Tường minh
fun withdraw(amount: Int) {
    if (balance < amount) {
        throw IllegalStateException("Insufficient funds")
    }
    balance -= amount
}

@907pqv
I’ve been waiting for the founder’s response to your question, but it seems he’s too busy and expects some DNH member to give you an answer. So I’ll do that. To your question.

Idiomatics / shallow module

Let’s first try to understand the two terms idiomatic and shadow module. The former is an attempt to name something linguistically natural for humans. The latter is a small, independent unit or file containing standardized functions/parts that can be used to build a more complex structure, such as an app (e.g., with the java.io.InputStream module) or a car (e.g., with the Gearbox unit). A shadow module is nothing more than a “flat” module that unnecessarily contains too few functions or parts to be economical or meaningful.

Then we’ll talk about the relationship between compiler techniques and idiomatics. Compiler techniques begin with microcoding, assembly language (2GL), and 3GL like C/PASCAL, and end with OOPL like C#, JAVA, or PYTHON. In compiler techniques, problems must be solved step by step. This means developing an algorithm that leads to the desired solution, for example, an algorithm for a query with three steps:

  1. if condition
  2. OK: …
  3. FAILED: …

Both depend on the problem and the algorithm. In low-level environments with memory constraints, microcoding or assembly language is the best choice. From a high-level perspective, however, this type of programming is cumbersome and unreadable, but fast and efficient. Assembly language is therefore the first attempt to make programming languages ​​more idiomatic.

At higher levels, where the problems are still manageable and resources are relatively abundant, 3GL is more suitable. For example, for human-readable trigonometric functions, logarithms, etc. Instead of programming in assembly language, 3GL uses a compiler to translate human-readable functions like cos() and ln() into assembly code. It’s the second step toward idiomatic language, so to speak.

The democratization of computers in 1990 required rapid software development, and 3GL became less suitable as problems became increasingly complex and had to be treated as entire objects. OOPL, like C++/JAVA/C#, is the next step toward idiomatics. Objects represent the problem as a whole with multiple facets that can then be isolated and solved. A next step toward idiomatics with object and function/method names in a human-readable language. With idiomatic objects, code becomes smaller and more readable in natural human language. Software development times are reduced, but throughput (performance) also decreases (I remember JAVA 1.0 being so slow that you could enjoy a cup of coffee before a small loop finished). Today, however, throughput is significantly better. All this is thanks to rapid memory development (in 1990, the average PC had 1 MB of RAM). With more RAM in GB, OOPL becomes popular and is therefore a new approach to software development with idiomatics that are easier and more meaningful for human to read.

Kotlin is to OOPL what 4GL is to 3GL (SQL, for example, is a 4GL). It represents the latest step toward more compact and idiomatic programming algorithms. Therefore, your Kotlin examples can be considered functions rather than “flat modules,” such as the trigonometric functions of the java.lang.Math module.

In general, the more idiomatic a programming language is, the slower its throughput (or performance). An example of an equal comparison algorithm (here: x86 assembly)

  Microcoding  2GL (Assembly)  3GL (C)        OOPL(C++/JAVA)  Kotlin                                4GL (NATURAL)
  00111000     CMP             if condition   if condition    similar OOPL                          IF condition
  1011xxxx     Jxx label_1     {              {               or: idiomatics as expression.           THEN ...
  ...          ...               ...            ...               example x = if (a > b) a else b     ELSE ...
  1110xxxx     JMP label_2     }              }               or: idiomatics as function            ENDIF
               label_1:        else           else                example: coerseXXXX or takeIf
                               {              {
  ...          ...               ...            ...
               label_2:        }              }
  ...          ...             ...            ...

Note: More about 4GL NATURAL click HERE
As you can see, readability improves from 2GL to 3GL/OOPL to Kotlin/4GL. The more natural the names, the better the readability and the more idiomatics there are. The reason for the low throughput, by the way, is that an idiomatics acts like a wrapper around a section of code, as you can see in the table above. In a narrow sense, idiomatics like coerseXXX or takeIf can be considered “shadow modules.”

The idiomatization process is transmorgrifying from natural human words to human thinking, as OOL objects are still too limited in human thinking. In OOL, a car or a tree is an object with specific descriptions as functions. However, human thinking does not view a car or a tree as an object, but rather as a generic subject that encompasses more than just physical and logical functions, but also rational and emotional aspects. And that is the new era of AI Large Lnaguage Models (LLMs).

AI_Models.
We’re currently experiencing the most visible transmorgrification in ChatGPT where problems are solved and represented in the most natural way for human mind, something unknown in any programming language. You can even ask ChatGPT for a piece of executable code.

Design for Readability, Not Writability

Idiomatics is a maximal approximation of natural human language. In the veritable sense: Readability = comprehensibility. And what is writability? It is the state or condition of being writable. Therefore, in the context of “designing for readability, not writability,” I cannot say anything about “writability.”

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?