Category Archives: Programming

程式設計不像建築工程,而是園藝維護

Update: Real Software Engineering by Glenn Vanderburg 這場演講也值得一看

前一陣子念「笑談軟體工程:敏捷開發法的逆襲」這本書,其中有幾篇文章的說法和比喻常被引用,所以我按圖索驥,也把原文章拿出來念一念,包括:

1. What Is Software Design? by Jack W. Reeves

「原始碼就是設計」「 Source code is the design」,避免花費太多時間在分析設計上,原始碼本身就是是軟體設計的產生,而 compile, link 等才是軟體的生產活動。因此,寫程式是「設計」的活動,而不是「生產」的活動,因此傳統設計文件的重要性就大大降低了。

程式設計不是 “building software”,而是 “designing software”。

2. Is Design Dead? by Martin Fowler

軟體系統的設計是演進來的,不能一步到位,而是要藉由憑繁與使用者互動得到的回饋來修改系統設計。

3. Programming is Gardening, not Engineering

與其把程式設計比喻成蓋房子,實際上更像是園藝。

4. Orthogonality and the DRY Principle

所有程式設計活動其實都是維護,因為絕大部分的時間都在改code,寫一點改一點。即使是新專案,也很快需要回頭作修改。

後兩篇是 A Conversation with Andy Hunt and Dave Thomas 訪談。Andy Hunt 和 Dave Thomas 是 The Pragmatic Programmer 這本經典的作者,他們後來共同創辦了 Pragmatic Bookshelf 這家出版社。其他五篇訪談也值得一看,都是關於軟體開發的真知灼見:

Sublime Text 資源整理

自從 Textmate 2 官方開發陣亡之後,就改用 Sublime Text 2 來寫 code 了。整理一些找到的好資料跟 Plugins:

Plugins

Syntax 相關

Settings

編輯 Preferences > Settings > User 加上:


{
  # 把 tab 換兩個空白字元
  "translate_tabs_to_spaces": true,
  "tab_size": 2,
  # 高亮所在行
  "highlight_line": true,
  # 存檔時移除多餘的空白
  "trim_trailing_white_space_on_save": true
}

還有推薦什麼必裝的 Plugins 嗎?

Defensive Programming 防禦性程式設計

TL;DR 避免 Defensive Programming,愛用 Fail Fast 策略。

什麼是防禦性程式設計,在 Erlang’s Programming Rules and Conventions: Do not program “defensively” 裡這樣說明:

防禦性程式發生在程式設計師不相信系統的輸入(input)資料時。一般來說我們不需要測試輸入的資料來確保功能正常。系統中大部分程式碼應該可以假設輸入的資料是正確的,只有一小部分的程式需要真的檢查資料,這通常發生在資料第一次輸入的時候。只要資料在輸入進系統時被檢查過,那麼就應該可以假設它是正確的。

也就是說,當你不相信輸入時,你就得去檢查它。有經驗的程式設計師應該都非常熟悉這種作法,他的缺點在於這常是多餘的資源浪費,不但浪費效能,也增加程式碼維護的成本。好比說一個方法已經檢查過參數了,該方法又繼續把參數傳遞給別的方法時,可能又要再檢查一次。如果所有方法都要針對參數重複做嚴格的檢查,豈不是非常囉嗦。所以 Erlang’s Programming Rules 建議你在系統裡不要檢查,crash 與否的責任在於呼叫方,在於資料一開始輸入進系統的時候,而不是這個方法。

但是,總是有需要檢查的時候,特別是資料第一次輸入進系統的邊界情況,以應用程式來說,就是指使用者的輸入; 以 Library 來說,就是指 Public API。這時候有兩種策略來對付不乖的參數: 1. 修正或略過資料的錯誤(defensive, compensate) 2.丟出錯誤(offensive, fail fast)

讓我們來看看例子吧。以 Check Null 型的問題來說,有三種寫法:

# defensive programming
# 如果參數不符合條件,就略過或修正它
def resize_image(image)
    if image && image.is_a?(File) # 檢查 image 不是 nil,而且是 File
        # execute code
    else
        # ingore, nothing happened.
    end
end

# non-defensive programming
# 不檢查,錯了就讓他 crash 吧
def resize_image(image)
    # execute code
end

# offensive programming (fail fast)
# 檢查,有錯就馬上失敗讓你知道
def resize_image(image)     
  raise "The image is invalid" unless ( image && image.is_a?(File) )                
  # execute code        
end

Daniel Roop 在 Why Defensive Programming is Rubbish 認為 defensive 策略是一種 Hide the Problem Programming,我覺得講的很好。為什麼呼叫方會傳不對的值進來呢? 這是應該要去 trace 的問題,而不是把問題隱藏起來。長久以往下來,整個系統的不確定性就會越來越多,也就越來越難維護。

比較建議的作法是,如果是系統內可以相信呼叫方,那就不要檢查了。如果是邊界情況需要檢查,就做 offensive programming,也就是 “Fail Fast”,把條件限制當做一種契約,如果呼叫方(caller)不照規矩來,整個方法就會 fail 掉,這樣的好處是可以提早發現問題,並且在問題發生的第一時間就可以修好,讓整個系統朝向可以互相信任的方向成長。

再舉一個更惡名昭彰的 defensive programming 的例子:

# defensive programming
def run
    # execute code      
    rescue  # 救回所有例外
end

此例中,無論發生什麼錯誤,這個方法都會回傳 nil,非常安全不會 crash。但是即使 execute code 的地方打錯字也不會怎樣,徹底把問題隱藏起來了。

可是….

可是我們設計API的時候,特別是像 Ruby 這種動態語言,常常允許參數很有彈性耶? 例如

 # 參數 i 除了可以是數字之外,也可以是字串,例如 "123"
def process_integer(i)
    var = i.to_i # 轉數字
    # process var
end

# 參數 s 除了可以是字串之外,也可以是 Symbol 或數字等
def process_string(s)
    var = s.to_s # 轉字串
    # process var
end

# 參數 a 除了可以是陣列之外,也可以只傳一個元素或nil
def process_array(a)
    arr = Array(a) # 讓 arr 一定是陣列
    arr.each do |x|
        # process x
    end
end

好吧,這種算是”有意 (intentional)”的 API 設計,我們擴大了參數的彈性,而且呼叫方(caller)明確知道可以這樣呼叫(最好搭配API文件)。在 Confident Ruby 這本書裡,提到了很多 Ruby 適用的技巧。

Contract by Design

另一種解決檢查參數前置性條件的根本方法,叫做”契約式設計 Contract by Design”,也就是在語言層面去宣告前置條件。不過,大部分的程式語言都沒有內建,Ruby 是有一些第三方套件(利用Ruby的meta-programming特性來做),不過用的人就不多了。

結論

  1. 在系統邊界內,盡量相信輸入,不做檢查
  2. 如果需要檢查,採用有錯誤就中斷掉的方式(Fail-fast),避免 defensive programming 把問題藏起來

後話

另一個會讓整個系統充滿防禦性風格的情況,可能發生在團隊感情不好的環境。為什麼呢? 因為溝通不良,加上沒有適當的規範,所以寫code的時候只好處處防著別人,為了怕 crash 要麻煩別人(caller),只好額外費工加一大堆多餘的條件檢查和處理,把問題都埋起來好讓方法不會crash…

參考資料

註: 有些人認為 “fail fast” 也算是 defensive programming 的一招。我這裡採用對比的 offensive programming 說法。

Kent Beck 的四個簡單程式設計原則

什麼是好設計,打從開始 coding 以來就是一個不斷追求的大哉問,從
SOLID 看到 Design Patterns,每一個原則看起來好有道理,每一個設計模式都在等著你的套用。

但是最近幾年的工作讓我有不一樣的心得,而不再費心追求「完美」的設計。同一個問題,每個人都會想出不一樣的解法,都說自己的解比較好。於是這些設計原則,常就淪為爭論的工具,當我覺得你的解法做太多事情時,搬出YAGNI; 當我覺得你的解法太複雜時,搬出KISS; 當覺得一個類別太肥大的時候,就搬出 SRP,反正都可以找到支持的論點就對了。很多時候其實不管哪一種方案,都是可以接受的可行方案,於是後來我就不想去爭個你死我活、斤斤計較當下的細節設計了。好設計應該是演進(grow)出來的,我首先在乎的反而是有沒有留下可用的測試和清楚好讀的程式,這樣才可以留下(後人)改進的機會。

回到正題,Kent Beck 在 eXtreme Programming 中,對於什麼是 Simple Design,留下了四個 Xp Simplicity Rules,我個人蠻喜歡的,因為很簡單又基本:

  1. Pass All Tests 通過全部測試:有測試才能夠讓軟體不斷的演進(grow)下去,不然你不是被 regression bugs 淹死,就是只能不計成本把code砍掉重練。如何寫好測試也是個大哉問,xUnit Test Patterns: Refactoring Test Code一書是其中的bible,嫌厚的話,可以先看看Refactoring Test Code這一篇paper,都是在講重構你的測試程式。當然,事情總是過猶不及,老闆付錢給你是為了得到 code,不是 tests。測試只是幫助我們寫好code的工具,追求100%完整的好測試並不是目的。

  2. Reveals Intent (Self-Documenting Code) 程式能夠表達出意圖:其中最重要而且基本的,就是要有好的程式命名,無論是類別、方法、變數等等。專講 Readable 的書有一本可以推薦 The Art of Readable Code。我這裡還推薦一個我最喜歡的技巧 Composed Method (搞笑談軟工也有一篇介紹),這招也是 Kent Beck 大師提出的,將細節的實作抽取成一個小方法,讓整個方法裡的操作都是類似的粒度,小小一招,大有作用。

  3. No Duplication (DRY) 不重複:”Every piece of knowledge must have a single, unambiguous, authoritative representation within a system” 完整定義請複誦三次。有時候重複並不是這麼明顯,例如作用重複的程式,但命名不同,或是命名重複,但程式作用不一樣。這裡推薦 David Chelimsky 的影片 Maintaining Balance While Reducing Duplication (slides)和 Maintaining Balance while Reducing Duplication: Part II,可以增進對 DRY 的理解,DRY 不只是在講不要重複程式碼而已。

  4. Has no superfluous parts (Minimizes the number of classes and methods) 不多餘:這一條也就是 YAGNI,不要實作多餘的功能。我個人不怎麼喜愛這個原則。不是不贊同,不需要的功能花時間去寫當然是浪費大家時間,而是這個原則太容易例外了。有時候會寫下多餘的程式來增加功能或擴充性,那是因為你擁有的經驗、直覺跟當時的情境告訴你應該這麼做,這可以在不久的將來就派上用場,過於強調 YAGNI 反而一板一眼教條主義了。光單看”減少類別跟方法的數量”跟 SRP 和 Composed Method 就有所衝突了,後兩者都會增加類別和方法的數量。

重要性 1 > 2 > 3 > 4,也有版本講 1 > 3 > 2 > 4。

參考資料

喜愛的程式設計名言

每次看技術書籍的時候,特別喜歡翻過一遍作者放在章節開頭的名言佳句(如果有的話),通常都蠻有意思的。所以在自己的裡也想這樣做,這是目前收集的名言佳句。不過要做到每則名言都剛好呼應該章內容真是困難啊。

  1. Life’s too short to build something nobody wants – Ash Maurya, Running Lean 作者
  2. Give someone a program, you frustrate them for a day; teach them how to program, you frustrate them for a lifetime. – David Leinweber
  3. There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies. — C.A.R. Hoare
  4. Actually, I’m trying to make Ruby natural, not simple. Ruby is simple in appearance, but is very complex inside, just like our human body. – Matz, Ruby 發明人
  5. Much of the essence of building a program is in fact the debugging of the specification. – Fred Brooks, The Mythical Man-Month 作者
  6. The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. – Tom Cargill
  7. Complication is what happens when you try to solve a problem you don’t understand – Andy Boothe
  8. Weeks of programming can save you hours of planning. – Unknown
  9. Controlling complexity is the essence of computer programming. — Brian Kernighan
  10. All problems in computer science can be solved by another level of indirection(abstraction) – David Wheeler
    …except for the problem of too many layers of indirection. – Kevlin Henney’s corollary
  11. Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. — Rick Cook
  12. Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. — Brian W. Kernighan
  13. I’m not a great programmer; I’m just a good programmer with great habits. ― Kent Beck
  14. Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris. – Larry Wall, Perl 程式語言發明人
  15. Any fool can write code that a computer can understand. Good programmers write code that humans can understand. – Martin Fowler
  16. Measuring programming progress by lines of code is like measuring aircraft building progress by weight. – Bill Gates
  17. It’s not a bug – it’s an undocumented feature. – Unknown
  18. The most depressing thing about life as a programmer, I think, is if you’re faced with a chunk of code that either someone else wrote or, worse still, you wrote yourself but you no longer dare to modify. That’s depressing. – Peyton Jones
  19. It works on my machine! – 數以萬計的程式設計師
  20. Talk is cheap. Show me the code. – Linus Torvalds
  21. I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages — Alan Kay, Smalltalk 發明人
  22. When you choose a language, you’re choosing more than a set of technical trade-offs—you’re choosing a community. -Joshua Bloch
  23. Quality, Speed or Cheap. Pick two. – Unknown
  24. Developer testing isn’t primarily about verifying code. It’s about making great code. If you can’t test something, it might be your testing skills failing you but it’s probably your code code’s design. Testable code is almost always better code. – Chad Fowler
  25. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. – Donald Knuth
  26. No code is faster than no code. – Merb core tenet
  27. If you’re the smartest person in the room, you’re in the wrong room. – Unknown
  28. How long would it take your organization to deploy a change (to production) that involves just one single line of code? Do you do this on a repeatable, reliable basis? – Mary Poppendieck
  29. Nine people can’t make a baby in a month.  — Fred Brooks, The Mythical Man-Month作者
  30. Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer. – Steve McConnell, Code Complete 作者
  31. A person does not really understand something until after teaching it to a computer. – Donald Knuth

有特別喜歡哪句嗎? 或是你自己有喜愛的名言,歡迎告訴我。

物件導向程式的九個體操練習

最近在翻 The ThoughtWorks Anthology(知名軟體顧問公司 Thoughtworks 出的文集),裡面有篇 Object Calisthenics 蠻有意思的。

好的物件導向設計很難,我們都很同意何謂好的設計原則:高內聚力(cohesion)、低耦合(loose coupling)、不重複程式(Don’t Repeat Yourselp)、封裝(encapsulation)、可測試性、易閱讀性等等,但是實際寫的時候卻不容易化身為一行行的程式碼。這篇作者列了九條規則,並建議你練習寫個千行程式嚴格遵守看看,用以改善你的OO實作能力。

初次看到這九條時覺得有點誇張,但其實濃縮了不少OO想法在裡面,如果有閱讀過重構或物件導向設計原則等概念,應該能夠聯想到很多東西,挺有趣的。

1. 每個函式裡面只能有一層縮排,如果需要多一層,請多寫一個 method 去呼叫。

這個規則其實就是要求嚴格遵守 Compose Method:將邏輯操作轉換為細目等級相同的步驟,避免過深的邏輯而無法迅速了解,相信大家應該都有看(寫)過M型程式吧 :p

2. 不要使用到 else 這個關鍵字。

避免寫出複雜的 nested conditional 程式。不論是”重構“或是”重構-向範式前進“這兩本書,都有很多篇幅花在討論如何簡化條件邏輯,作法包括

a. 重構一書提到的 Replace Nested Conditional with Guard Clauses 方式,直接使用 return 返回,不要再 else 了。

b. 請愛用 Ternary Operator:也就是 boolean-expression ? expr1 : expr2。很多簡單的 if else 都可以用 Ternary Operator 簡化到一行一目了然。舉個 Ruby code 例子:


if ( is_something )
"foo"
else
"bar"
end

如果改成三重操作子就俐落多了:

( is_something )? "foo" : "bar"

另外初心者也常寫出根本不需要 if else 的情況:

def is_foobar
if ( a > 0 )
return true
else
return false
end
end

其實只需要這樣就可以了:

def is_foobar
( a > 0 )
end

c. 第三招要先念點書,請善用物件導向的多型(polymorphism)能力,請參考設計模式的 Strategy pattern 或重構的 Replace Conditional with Polymorphism

Continue reading 物件導向程式的九個體操練習