Archive for the ‘編程技巧’ Category

昨天寫的〈測試驅動開發要徹底重構〉,曾經提到 Steven 說到「如果像閣下文中說:『系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護』,我則認為是在進行 TDD 時候沒有徹底去重構系統。」,同人認為這段話有根本上的邏輯的繆誤,於是回應:

假如上面的話是成立的,那麼代表只要徹底重構系統就不會造成「系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護」嗎?如果是這樣,那 XP 根本就不需要 Refactoring 這個實務來改善程式的結構,因為你都徹底重構程式,程式結構變差的情況是根本不可能發生。

而且依照本人 20 年來軟體開發的經驗,我還不沒有看到有程式一開始就寫得很好,到後來可以不用改變架構而符合新需求的。倒是隨著對問題的更清楚,或是程式需求的變化,讓程式必須重構的現象時常履見不鮮!

所以如果自以為自己博覽群書,很懂得TDD的實務。也不要忘了用心思考,以免自己對TDD的最佳實務的認知,不小心犯了根本上的邏輯繆誤?

結果,後來同人在 Facebook 的 Scrum community in Taiwan 看到 Steven 做了以下的回應:

先說件簡單易明的事情,其實我很不喜歡閣下把 TDD 放到 『精神』 層次,卻忘記了基本步驟。

之前的討論根本連事實層次都被忽略,根本談不上是什麼多少年的經驗或者如何用心思考,連 TDD 的最基本步驟也忘記,TDD 的基本步,不是閣下做多少年工作就可以把人家的定義去改變的,我也不明白為何有 20 年工作經驗就可以把 『Refactoring』 說成 『並不必然是 TDD 的必要的步驟』,這不是邏輯問題,更不是有過什麼開發經驗然後用心思考就可以改變的事情,這是就算對軟體開發的認知不夠閣下那麼 『全面』 的都能看出的謬誤,如果閣下這樣就認為是因為說不過閣下就建議多看書本,本人深表遺憾。

我是來討論問題的,我沒有興趣去傷害閣下感情,好好閱讀書本,只是反映 TDD 三個步驟是什麼根本不存在爭議,更沒有 『好好閱讀什麼書或文章才能跟我討論』 的意思,我還未自大得要別人看過多少書才可以討論問題,亦正如討論問題我也不用跟別人說我有多少年工作經驗一樣,而且本人是衷心認為多讀書是有益的(不管是閣下還是什麼人),多讀書亦不是為上來辯論的,不過閣下如果感到有所不悅的,我就先行道歉,還是希望冷靜一點討論問題。

而簡單的例子也是思辦的過程的一部份,一方面是簡單易明地討論問題,另一方面是如果連簡單的例子也說不通,又怎麼能去談更複雜的問題呢?

上面提到:』那XP根本就不需要 refactoring 這個實務來改善程式的結構,因為你都徹底重構程式,程式結構變差的情況是根本不可能發生。』

不如冷靜一點再讀讀這句子,』XP 不需要 Refactoring 是因為徹式地進行 Refactoring』,我就看不明白這是什麼邏輯,一邊說不需要,另一邊說徹底地進行。

這裡的問題是軟件是會改變的,可能是新增功能,也可能發現有其他問題,每次帶來的變更其實都需要進行重構的。所以說 XP 不需要 Refactoring 也不正確。

世上的確沒有一寫就好的代碼,而且世界是會變的,Refactoring 就是避免以後的更改越來越困難。把 『徹底地進行重構』 理解成 『XP 不需要重構這實踐』 完全沒有邏輯可言。

我也沒有反對不用改變程式架構就能滿足新需求,亦沒有否定 Refactoring 的重要性,只不過我還是建議新的功能以 TDD 方式進行開發,有測試、有代碼、然後進行重構的。

在足夠測試覆蓋下進行重構是可使系統在不斷演進及需求不斷改變之下,使架構或設計仍然處於可以維護的狀態,相反我指出的是,如果程式架構和設計越來越難維護,是重構的力度不足夠。

前面還提到:』但重構的目的為何?就重構的定義在不改變功能的情況下改善程式結構,以增加程式碼的彈性以利未來增加或改變功能。因此如同那第三步所言,為了去掉重覆性而重構。』

如果重構只是為了去掉重覆性,那 TDD 的第三步不如叫 『Remove Duplication』 好了,無可否認代碼重覆是很常見的問題,但把這裡的重構限制成消除代碼其實會局限系統的將來發展,而且到了 TDD 的第三步,系統是應該有足夠的測試去覆蓋系統,重構的力度沒理由只局限於新增功能和現有程式的重覆。

我就相信閣下是 Refactoring 的專家,也應該會知道一些 Refactoring 的模式是完全相反的,例如 『Pull Up Method』 和 『Push Down Method』,更是需要觀察當時的情況來作決定,而沒有一面倒那個才是好的模式,我實在不明白為何要把 TDD 的 Refactoring 局限到只做 『去掉重覆性』。

這是實務上會發生的事情,消除代碼重覆以外的重構還是會發生的,如果只是單單只是 『消除重覆』,這會是另一個我認為進行重構不夠徹底的事情。

上面已經不單單是用心思考,而是由理論到實踐都可以看得到的事情。

跟討論 Refactoring 和 TDD 觀點以外的聲音,就引用 Chet Henderickson 的說法,全部都是我錯好了,現在可以解決問題嗎?

看了 Steven 的回應,同人當下的反應是不想浪費時間與心力與他周旋下去,但後來想到或許是因為 1/9 敏捷開發分享會我要分享實施 XP 的經驗與心得,也許這是一個巧妙的同步事件。我可以趁這個機會,導正對 XP 或是 Agile 的一些錯誤觀念。

例如敏捷開發並不是教條式的照本宣科,開發者要懂得變通最重要的是用心思考,而非把必要的思考都看成精神層面的問題,這並非適用於敏捷開發的心智模式。以下是同人在 Facebook 的 Scrum community in Taiwan 的回應,但一些詞句有略為做過一番修飾,以清楚表達我對測試驅動開發步驟的看法。 Read the rest of this entry »

同人在〈測試驅動開發的精神〉中,提到「Refactoring 並不必然是 TDD 的必要的步驟」的觀點。David Ko 回應他猜測 Steven 是根據 Kent 在《Test-driven Development》一書的前言中提到的說法,來認為測試和 Refactoring 也是TDD很重要的一環。他說:

…here’s what we do: we drive development with automated test, a style of development called Test-Driven Development(TDD). In Test Driven Development:
* Write new code only if an automated test has failed
* Eliminate duplication
……

The two rules imply an oder to the tasks of programming.
1. Red – Write a little test that doesn’t work …
2. Green – Make the test work quickly…
3. Refactor – Eliminate all of the duplication….

Red/green/refactor – the TDD mantra

在這段敘述中,Kent 認為 Red/green/refactor 是 TDD 的三字箴言,是 TDD 這個 practice 重要的工作。

所以 Steven 才會強調在做 TDD 時,』測試是第一步』,』Refactoring 本身是 TDD 三步之中其中必要的一步』

假如正如 David Ko 所說,測試與 Refactoring 是 TDD 很重要的一環,而來強調 TDD 不應該忽略 Refactoring 的工作,這樣的觀點我可以接受,但同人隨後又看到 Steven 的說法,覺得他自以為是的論點讓我很遺憾。

本人從來沒有指出 TDD 是測試 Practice,亦沒有認為測試是 TDD 的目的。

上面的 『功能』 指代碼的內容規格,寫測試時候就是思考新功能的行為,用測試用例的方式去表達出來。就正如:

AssertEquals(3, add(1,2))

那麼簡單,已經是對 add() 作出規範,投入兩個參數,輸出一個數字作結果,並規範了新 fn 的名字是 『add』。寫這句 Assert 時候就應該思考到 add 有兩個參數,投入 1 和 2 就會得 3 這個數字。

重構是 TDD 其中一步,不存在混淆點,再者,沒有測試覆蓋下的根本不能說成重構;另一方面,先寫代碼後補測試是很痛苦的事情。

如果像閣下文中說:「系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護」,我則認為是在進行 TDD 時候沒有徹底去重構系統。

我建議閣下好好閱讀 TDD 的書本,Refactoring 是 TDD 必需的步驟,由 Kent Beck 到 Robert Martin 以至其他不太有名的 TDD 書本作者,都指出 TDD 的第三步是重構,而不是 『Refactoring 並不必然是 TDD 的必要的步驟。』

看到「我建議閣下好好閱讀 TDD 的書本」這種不尊重不同觀點的說法,同人根本就不想浪費時間來跟對方爭論。即使耐著性子仔細看他的論點,像那些「沒有測試覆蓋下的根本不能說成重構;另一方面,先寫代碼後補測試是很痛苦的事情。」、「如果像閣下文中說:『系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護』,我則認為是在進行 TDD 時候沒有徹底去重構系統。」等說法,更讓同人懷疑他對 TDD 的認知,其實是過於偏向理念化而不夠切合實際。

不過,對 David Ko 提出 Kent 認為 Red/green/refactor 是 TDD 的三字箴言的說法,同人倒是覺得有探討的必要。以下分享我在 Facebook 的 Scrum Community in Taiwan 回應 David Ko 的觀點,這些觀點應該可以解釋為什麼測試開發不需要徹底重構 Read the rest of this entry »

在 Facebook 的 Scrum Community in Taiwan 看到 David Ko 提到:

聽到有人說 TDD 是個測試的 practice,跟 RD 不是那麼有關,我想這是誤會了。TDD是一種設計的活動,它並不是單純在做 verification,它是一種 spec 確認的活動。

David Ko 還分享了一篇探討 TDD 的文章供大家參考。這讓同人想到我之前曾經對 InfoQ 的一篇有關軟體開發信仰問題的文章做過的評論

在那篇文章的評論當中,同人認為作者不應該拿 TDD驗收測試相提並論,因為 TDD 並不是測試的 practice,而是設計的活動。如果這兩種實務可以相提並論,似乎就像是爭論有了良好品質的設計就可以省略測試,或是增加測試的效率就可以取代設計的重要性。同人當時在我的評論中提到:

測試的涵蓋面要夠廣,軟體開發所需的開發成本與時間就要增加,所以與其靠檢驗來控制品質,還不如把設計做好。所以,品質是設計出來的,而非檢驗出來的。

不過,雖然品質並不是檢驗出來的,但軟體要具備足以信賴的品質,卻不能省略測試。舉個例子來說,不管開發者如何確保他的開發所產出的品質,驗收測試是絕對不可能省略的,否則客戶是很難信任軟體是合乎他們需求的。良好的軟體設計可以減輕測試的負擔與壓力,但它絕對無法否定軟體測試的價值。

因此,同人認為不該拿 TDD 來與各種形式的測試來做比較,但在 David Ko 提出這個主題後面的討論,我卻看到一個我不太認同的說法。 Read the rest of this entry »

石頭成在〈與 metavige 和 alexchen 對話 Java 語言〉這篇文章中,直言他對 Java 語言泛型的批評:

我不認為 Java 讓我們「慢工出細活」,我覺得它帶來的是「冗餘的複雜性」。就算以 C++ 的觀點來看 Java 程式碼,我仍覺得 Java 程式碼有許多不必要的複雜度。Java 把類別變成施加在程序員身上的束縛,而是不是幫助我們進行抽象化資料處理的工具。

我個人認為,所謂「更強化的靜態型態」是跟 C++ 樣板相比, Java 泛型比起 C++ 樣板是在走回頭路。就我到目前為止的 Java 使用經驗來看,我幾乎以為泛型只是 Java 專門用來重新設計容器類別的特殊語法。在那以外的場所,你大概不會想用泛型來重構你的程式碼。

metavige 就說會想用 Strategy Pattern 來重構我在〈從 C++ Template 到 Java Generic,一步一步來〉舉的例子,而不會用泛型。但是泛型難道不是用來處理這個問題的直覺想法嗎? Java 沒有足夠理由說服我們不要用泛型來做,但是用泛型來做… 呃,似乎更困難。

石頭成用泛型來重構程式碼,是不是處理他的問題之直覺想法?我想石頭成比較偏好動態型態語言的自由,比較不欣賞「更強化的靜態型態」的做法。或許是因為 C++ 用 template 實作不會受到參數型別類型的限制,自然會讓他比較想用泛型來重構程式,以去除資料型態與演算邏輯的相依性。然而,看過石頭成所舉的實例,同人認為用泛型來實作的想法未必是直覺的做法。

表面上看起來好像實作泛型可以讓某一段程式碼重複使用,但 Java 在泛型的限制,也增加他重構程式碼的困難度與複雜度。這麼說來,假如石頭成的想法是正確的,用 Java 的泛型來重構程式碼,只會讓程式員沒事自討苦吃。然而,同人在仔細研究他的程式碼之後,發現可以用更簡潔的方式來使用 Java 的泛型。不過,還是讓我們先來看看石頭成的程式碼: Read the rest of this entry »

4
十月

以字元串列常數當索引鍵值

   Posted by: jim yeh   in 問題解決, 編程技巧, 職場

最近同人在開發資料剖析的程式時,碰到一個很奇怪的現象。我使用 map 容器來存放剖析資料的結果,然而卻發現以原先新增資料所用的相同鍵值,竟然會找不到該筆資料。但我的測試程式顯示容器中所存放的鍵值與資料,其內容是正確的,但為什麼用相同鍵值去找,結果竟然回傳查無資料呢?原來問題就出在以字元串列常數當索引鍵值。 Read the rest of this entry »

程式碼的重覆性使程式不容易維護、以及增加系統出錯的機率,同時使得程式的再用性難以提昇。因此降低程式碼的重覆性對程式碼的品質與彈性有很大的助益。然而,在資料存取與與物件型別的無法分離的情況下,卻會使得開發者難以對治程式碼的重覆性;相同處理資料的邏輯,受限於處理資料的不同型態,使得開發者必須依據資料型態不同,而改寫同一段程式碼,使得程式碼很難共用。

就像一個比較常見的例子是資料永續存取的實作,常會因為存取資料型態的不同,而影響到資料存取的界面,因此對每一個需要持久化的商業領域物件,都必須重覆地實作它們的 CRUD 存取功能。

雖然透過永續層的概念,我們可以將這種重覆性隔離開來,加上有些 ORM 永續框架的協助,開發資料存取物件的負擔並不太大。然而即使如此,我們有沒有可能避免重覆開發資料存取的元件,不用針對每一種物件型別都要寫一支相對的資料存取物件呢?要達到這樣的目的,我們必須分離資料存取的處理程序與物件型別,但要如何才能做到呢? Read the rest of this entry »

在〈開發者的 common sense〉的留言中,同人看到一些網友的批評。我發現這些批評顯示了有些開發者不擅於抽象化思考,而習慣於用經驗法則來取代思考。然而,誠如 Brooks 所言:「軟體的本質是複雜的,而不是偶然發生的」對治複雜度本來就是開發者的天職,而軟體開發的抽象化思考則是其用以統理複雜度的利器。由此看來,那些網友的批評著實令人為他們捏一把冷汗呀。

從路邊的垃圾桶與路人的留言,我們可以發現他們弄錯 common sense 的意思。他們認為 common sense 不能一概而論,因為每個人的 common sense 都不同。但這樣的觀點令人感到疑惑,如果每個人的 common sense 都不一樣,所以無法一概而論,那這種 common sense 還能叫做 common sense 嗎?

到底他們觀點上邏輯的矛盾,問題是出在那裡呢?同人認為問題並不是開發者的 common sense 不存在,而是忽略了開發者應該將經驗化成一般性的通用概念。舉例來說,軟體工程領域本身就是從實務發展出來的理論,其中許多概念就是開發者必須知道的 common sense,對開發者來說是合理的知識,也是他們都知道、無須解釋或加以論證的常識

由此可知,如果開發者缺乏一般性的通用概念,那他碰到問題就很難舉一反三,自然也就難以掌握重點,而只能依據表相來處理問題,往往使得問題變得更複雜。oofunp 的留言就很像欠缺概念思考的開發者常見的反應,一開始以自己熟悉的技術來看問題,最後才發現自己對問題的理解是錯誤的。尤其「以為抽象化是將資料庫定義抽象化」的想法,更是整個弄錯抽象化思考的意義,結果最後他還是誤解了業務規則的意思。

同人前一篇文章所提到的業務規則,並非來自技術領域上萬用的設計,而是對問題領域經過抽象化思考後,所萃取而得到可以解決業務需求的重要概念。顯然 oofunp 的誤解是以技術的角度來看待抽象化思考,才會產生嚴重的觀念混淆。事實上,抽象化思考重視的不是技術實作,而是如何從實際問題當中萃取出重要的抽象概念,以增進我們對問題領域的瞭解,才能採用最適當的技術來開發軟體系統。

此外,過份強調技術經驗而輕忽概念性思維的開發者,很容易表現出自己對問題的盲目。就像 X files 留言的批評一樣,責怪同人沒有交待清楚是 XML 格式的問題,直到別人提出質疑才說明與列出參考文獻,認為同人缺乏部落格文章寫作的 common sense。但我的文章已經很清楚地提到是有關「交易訊息」語法的問題,難道他不瞭解交易訊息是 XML 技術的一般化抽象概念表述嗎?XML 只是實現交易訊息概念的一種技術,用以解決跨系統整合的問題。如果他要看到 XML 字眼才知道是訊息格式的問題,那改天換了另一種交易訊息的實作方式,我想大概他腦筋又要轉不過來了吧。

以上網友的三種批評,表面上看起來好像是不同的問題,但其背後都存在同樣的本質,那就是從交易訊息的問題中可見一斑,他們無法以抽象性思考來看待軟體開發的問題。但為什麼開發者需要抽象化思考呢? Read the rest of this entry »

昨天同人在〈又見少了概括性論點〉提到〈必須面對的真相─五大程式設計迷思〉在文章結構上的問題。其實那篇文章除了結構問題之外,同人也在該篇文章內容中,發現了一些值得探討的問題,因此想在這篇文章發表我的看法。

以該篇文章內容而言,同人認為值得探討的有兩個地方,一個是程式語言一直再改變的迷思、另一個則是作者建議讀者,儘量避免用遞迴的方式來寫作程式。第一個問題只是沒有交待清楚作者想要表達的概念,而第二個問題就是嚴重的偏見了,值得讓人進行思辨以建立正確的觀念。 Read the rest of this entry »

最近同人看到有一則噗浪提到「一個穩定的程式並非必然,而是偶然」在此噗浪的回應中,發送這則噗浪的噗友表達他對程式穩定性的看法。

先問自己一個問題,一個程式有幾個 Bugs?如果是不可數,那~~它的穩定是相對非絕對,所以穩定不是絕對的,也不是必然的。

同人覺得這位噗友的觀點很有趣,於是加入這則噗浪的討論。我問如果最開始的程式都是穩定的,那麼是什麼原因讓後來的程式變得不穩定?對這個問題,一些噗友提出了他們的看法,其中有一位噗友回應「是我的機器弄好的程式,跑到別人機器就不穩定了」,發送此則噗浪的噗友認為還蠻接近實際的情形。

他提到 Bugs 在自己的機器沒產生,在別人那邊可能會產生,問題可能是因為自己的機器有 Bugs,而不是別人的機器,他說假設機器不會出問題是會有副作用的。

那麼為什麼不在應用系統要運作的目標環境下直接開發程式呢?因為,嚴格來說,開發者不可能有完全一致的環境,因此開發者只能假設環境是不會改變。但實際上,在不同的機器上、甚至在相同的機器上,不同的時間可能也會出現難以預料到的變化,結果使得程式變得不穩定。

這位噗友認為,當我們愈信任一個系統,其實可能是一個災難,因此程式的穩定非絕對而是偶然,它是由一連串的偶然所累積而成的。同人發現這個觀點讓人很難反駁,在我過去程式開發的經驗中,並不乏遇到原來運作正常的程式,在不同時空環境出現問題的現象。正如同這位噗友提到的,作業系統與程式語言它們本身也都是程式,很難確保它們不會出問題。

相信很多人都曾遇到過,程式發生失常通常只因為一個令人難以捉摸的小錯誤,因此似乎真的可以說「穩定的程式是偶然的」。不過,如果穩定的程式真的是偶然的,程式的穩定就只能依賴運氣而不是人為努力,事情真的是這樣嗎?其實這位噗友太過強調環境變化的隨機性,卻忽略了適應環境變化,程式開發必然會經歷複雜演化的過程。穩定的程式是演化而來的,雖然演化的過程是偶然、但其最後結果卻是必然。換句話說,穩定的程式是偶然下的必然Read the rest of this entry »

5
十二月

藏拙

   Posted by: jim yeh   in 利害關係人, 學習, 專案團隊, 溝通, 生活感觸, 編程技巧, 衝突

黔之驢》的寓言故事告訴我們藏拙的智慧。如同故事中老虎先前不知驢子的虛有其表,以為牠的龐然大物而對產生敬畏之心。但當黔驢技窮的底細被老虎發現後,可憐的驢子便喪生於虎口之下,這都是因為那頭驢子不懂藏拙的智慧。

從事軟體開發的工作,同人常觀察到一些開發者不懂藏拙的智慧,意欲表現自己很有能力,但卻總是被人看到他們虛有其表的黔驢之技。通常這種人不懂得用虛心受教來充實能力,而只會把問題的責任推到他人身上。

我們當然很希望這樣的人,不要出現在工作經驗當中。但很不幸地,世事總是難以如我們的預期,如果不幸在工作碰到這樣的人,我們應該如何自處呢? Read the rest of this entry »