The Clean Coder

Bob 大叔的書之一《The Clean Coder》,算是《Clean Code》的續集吧。

《Clean Code》講怎麼寫 code,《The Clean Coder》講怎麼當 clean coder。記錄些心有戚戚焉的片段跟感想。

測試

設計「易於測試的程式碼」(p.48)

我想設計良好的程式應該也會好測試?從設計還是從測試出發,最後目標是一樣的,偏好哪種我覺得沒什麼差。

到目前為止,我大多從設計出發。先想概略設計、class 的責任、class 之間的關係等等,接著實作會邊寫功能邊寫測試,但不是 TDD。通常是實作一小塊、寫那一小塊的測試,而不是先寫測試才實作。即使從設計出發還是需要測試的,因為可以確保程式是對的,之後也不怕修改。

沒有實際用 TDD 寫 code,只在 dojo 小小玩過,所以說不上從測試出發是不是會導向好設計,能想像的只有因為要測試所以功能切分上應該不會太糟。

這一策略有時也叫無情重構(merciless refactoring),我稱它叫作「童子軍規則」:對每個模組,每次 check in 程式碼,就要讓它比上次 checked out 時更為簡潔。(p.49)

幾年前曾經聽一位大師說過,不過很多時候不要弄得更亂已經謝天謝地了,偶爾才順手修一點。

如果你有一套覆蓋了所有程式碼的自動化測試,如果那套測試可以隨時快速執行,那麼你根本不會害怕修改程式碼。(p.50)

看到混亂的函式時,你的第一反應是:「真是一團糟,這個函式需要整理。」你的第二反應是:「我不會去碰它!」為什麼?因為你知道,如果去動它,就要冒著破壞它的風險;而如果你破壞了它,那麼它就纏上你了。

但是如果你能確信自己的整理工作不會破壞任何東西,那又會是怎樣的一個情況呢?如果你擁有我剛才提到的那種把握,會怎樣呢?如果你只需按下一個按鈕,然後在 90 秒內便可以確信你的修改沒有破壞任何東西,只是讓程式碼變得更好了,那麼又會是怎樣的一種情況呢?

這就是 TDD 最強大之處。擁有一套值得信賴的測試,便可完全打消對修改程式碼的全部恐懼。當看見糟糕的程式碼時,就可以放手整理。程式碼會變得具有可塑性,你可以安全地將之雕琢成簡單而滿意的結構。(p.113)

呵呵呵……真的。看到一團亂 code 覺得該整理又立刻覺得不想碰它。改了就得保證它還是正常的,再小的變動都無法確保不會弄壞些什麼。因為就是會自認不會有問題的修改卻在意想不到的地方壞掉(通常 code 愈沒有分清楚權責愈容易發生這種事)。沒測試可能沒發現改壞,沒有完整的測試可能沒發現壞在其他地方。在測試得花費時間的情況下,我通常不會碰路過看到的亂 code,不然就是賭人品簡單測過就「相信它是好的」。完整的自動化測試是個很好的保護措施,如果能確保東西不會改壞,就能安心修整雜亂的 code。

現實總是不美好,在沒有自動化測試的情況下,我的變通方式是「在加新功能或修改功能時順便整理」。如果可以也會一起加入 unit test。因為會改 code(雖然以一些設計原則來說,加新功能所需修改的 code 應該不多甚至沒有),測試時也會確認舊有功能是否正常,既然本來就要測,就能順便整理啦。

GUI 的測試

編寫 GUI 的驗收測試時,必須使用 GUI 背後相對穩定的抽象元素。

測試系統功能時,應當呼叫真實的 API,而不是 GUI。

要把 GUI 和業務邏輯分開。

透過 GUI 來進行測試非常容易出問題,除非你要測試的僅僅只有 GUI。因為 GUI 很容易變化,所以針對 GUI 的測試很不穩定。

最好把 GUI 和業務規則「去耦合」,在測試 GUI 本身時,用 Stubs(一種測試替身)替代業務規則。(p.139)

像 submit form 用 post data 去測,測試 business logic 要用 API 測而不要用 GUI 操作測。js object 中功能及邏輯部份跟 UI element 分得愈開愈好,這樣才能測功能跟邏輯而不用管變動性很大的 UI element。只有在要測試的東西就是「GUI 本身」才用 GUI 測試。

TDD 三大法則

  1. 在撰寫一個單元測試(測試失敗的單元測試)前,不可撰寫任何產品程式。
  2. 只撰寫剛好無法通過的單元測試,不能編譯也算無法通過。
  3. 只撰寫剛好能通過當前測試失敗的產品程式。

看這本書前就聽過某大師講過,還是記錄一下免得難找 XD

測試金字塔

  • 人工探索式測試:約 5%
  • 系統測試:約 10%
  • 整合測試:約佔 20%
  • 元件測試:50% 覆蓋
  • 單元測試:近乎 100% 覆蓋

需求測不準定理

有一種現象叫作「觀察者效應」或稱為「不確定原則」。每次你向業務方展示一項功能,他們就獲得了比之前更多的資訊,這些新資訊反過頭來又會影響他們對於整個系統的看法。(p.127)

偷拿物理來用!XD

做業務的人和寫程式的人都容易陷入一個陷阱,即「過早進行精細化」。

一看到需求已經被滿足,關於到底要什麼,他們就會冒出更好的想法──通常那不會是他們當時看到的樣子。(p.127)

這也是 RD 會覺得很幹的地方。

RD 的心聲:「怎麼拼命滿足了需求,demo 的時候又說要這個要那個?跟之前說的又不一樣,到底是怎樣?」

雖然我能稍微理解對其他人來說,會以現在看到的東西為基礎再更進一步知道更確切的需求,但我自己在 demo 聽到要這個又要那個,有時候難免升起一把火,尤其是中間要是遇到很麻煩的問題好不容易解掉了之類的。想想好像是修養不夠…

如果體認到「需求一定會一直變」,能做點因應是比較好的,例如用些設計原則跟方式,降低需求變動時的修改難度。

開發人員也會掉進精確化的陷阱。他們知道必須評估整個系統,而且通常認為需要精確評估。但是,事實並非如此。

首先,即便擁有全面準確的資訊,評估也通常存在著巨大的變數。其次,因為「不確定原則」的存在,所以不可能透過反覆推敲而實現早期的精確性。需求是一定會變化的,所以追求那種精確性是白作工的。

專業開發人員知道,預估可以並且必須基於「那不怎麼精確的需求」,這些預估只是預估而已。為了強調這點,專業開發人員通常會在預估中使用誤差棒,這樣業務方就能理解不確定性。(p.127)

我曾經希望完整知道整個系統的細節後才評估,可是沒多久就發現辦不太到,有太多因素導致系統某些部份是不那麼確定的,例如 third party 複雜的使用方式、商業流程尚未確定、domain knowledge 不足、系統的隱藏問題等等。以至於無論如何避免,還是會在做下去後發現這裡缺了那裡漏了,或者後來才知道這個 domain 有些觀念是一開始不曉得的。

既然無法一開始完整知道所有細節,只好換個方法。首先,設計時在一個程度上保持軟體的彈性,但不過度使用複雜的 pattern 增加彈性,只要降低 component 間的耦合度就能讓往後的修改不那麼難。其次,雖然系統中有不確定性很高的地方,卻也有「現在就能確定」的部份,所以我會將不確定的部份劃分在系統的某一塊並且對它有個基本結構的想法,先實作現在確定的部份。隨著時間推進,原本不太確定的部份可能變得確定,例如幾天後拿到 API 文件、確定了商業流程等等,到這時再做細部設計跟實作。

避免過早精細化的辦法是盡可能地「推遲」精細化。(p.127)

我算是在針對系統不確定性高的部份推遲「精細化」,延遲細部確認各種作法跟設計。

三元分析法

可以用來預估時程。

  • O:樂觀預估(Optimistic Estimate)
    • 非常樂觀的數字。如果一切異常順利,可以在這個時間內完成。
    • 發生機率應小於 1%
  • N:常規預估(Nominal Estimate)
    • 機率最大的數字。
  • P:悲觀預估(Pessimistic Estimate)
    • 非常悲觀的數字。考慮各種意外狀況的預估。
    • 發生機率應小於 1%

任務的期望完成時間:

u = (O + 4N + P) / 6

任務完成時間的機率分佈標準差,用來衡量不確定性:

a = (P - O) / 6

讓預估時程是個機率分佈,而非一個聽起來確定(實際上根本是瞎猜)的值。

溝通

通常,各方握手言歡,以為其他人都明白自己的意思。雙方以為取得了共識,然後帶著截然不同的想法離開,這種事屢見不鮮。(p.140)

這超級無敵容易發生。

人類語言十分不精確,一個字詞對不同人可能有不同意義或解讀。有精確意義的科學或工程術語還好,但大多數討論需求是不會用這種術語的(會逼死客人或業務),而需求描述卻是軟體開發非常需要清楚確認的部份。如果不確認過某些字詞的精確意義,或者不在過程中不斷的確認彼此的意思,非常容易出現大家都說 A 可是我的是 A、你的是 a、他的是 A’,等到東西做下去才發現怎麼通通不一樣,然後又要改,RD 哭哭

Bob 大叔解決溝通問題的方式是使用驗收測試,但我還沒有很了解為什麼驗收測試可以解決溝通問題,是因為驗收測試足夠精確並且是由業務端認可過的?

「凡是不能在 5 分鐘內解決的爭論,都不能靠辯論解決。」這類爭論之所以要花這麼多時間,是因為各方都拿不出「足夠有力的證據」來支持己見。所以這類爭論依據的不是「事實」,而是「信念」。

在沒有資料的情況下,如果觀點無法在短時間(5 ~ 30 分鐘)內達成一致,就永遠無法達成一致。唯一的解決方法是「去取得資料,讓資料來說話」。

該怎麼得到解決問題所需要的資料呢?有時候可以做一些實驗,也可以做些模擬或是建立模型。但是有些時候,最好的辦法是拋硬幣來決定到底該如何選擇。

如果爭論必須解決,就應當要求爭論各方在 5 分鐘內向大家表明問題,然後大家投票。(p.151)

愈缺乏客觀知識、資料或數據的討論,愈容易陷入主觀之爭。而陷入主觀之爭,往往誰也無法說服誰,容易變成長時間的辯論,到最後只是看誰肯先讓步。

專注力

專業開發人員會學習安排時間,妥善使用自己的專注力點數。我們選擇專注力點數充裕的時候進行程式設計,在專注力點數匱乏時做其他事情。(p.152)

一旦專注力點數耗盡,你就無法控制專注力。你仍然可以寫程式,但是多半寫出來的程式需要在第二天重寫,或者在幾週或幾個月之後備受這段程式碼的煎熬。所以,更好的辦法還是花 30 ~ 60 分鐘來換換腦子。(p.153)

想睡覺的時候不要寫 code 不要寫 code 不要寫 code。

這念書的時候就有所感了,因為我基本上無法熬夜,愈晚腦子愈頓。想睡覺寫出來的東西往往不知道在幹嘛,與其寫出一堆不知道在幹嘛、事後還要重改的東西,不如去休息。這才是效率啊。

壓力

觀察自己在危機時刻中的反應,就可以瞭解自己的信念。如果在危機中依然遵循著你在常規狀況下遵循的紀律,就說明你確實相信那些紀律。反過來說,如果在危機中改變了行為,就說明你並不真的相信常規行為中的紀律。

選擇那些「你在危機時刻依然會遵循的紀律原則」,並且在所有工作中都遵守這些紀律。遵守這些紀律原則是避免陷入危機的最好途徑。(p.172)

當事情十分困難時,要堅信你的紀律原則。之所以你會將之奉為紀律,是因為他們可以指引你度過高壓時期。(p.173)

我想到飛行訓練或者任何有風險運動的練習。這些活動遇到危機或危險,通常不會希望自己整個亂掉、胡亂跑亂衝,能依靠的應該是平常練習中的訓練、紀律以及習慣。同理,軟體開發遇到危機應該更要依靠平常的習慣跟紀律,而非打亂原本的工作方式。

協作

深刻理解業務目標。
你需要理解「手上正在撰寫的程式碼,其業務價值是什麼」(p.177)

有時候知道業務價值會比較有動力。

有時候知道業務價值才能判斷 solution 對不對,尤其是討論對象一開始就是 RD 的時候。以目前的經驗來說,RD 似乎很容易直接進入 solution 的細部討論,萬一一開始理解錯業務價值或目標或者一開始 solution 是錯的,後續 RD 間無論如何討論細部都無法達到業務重點。

不正常的團隊最糟糕的症狀是,每位程式設計師在自己的程式碼週邊築起一道高牆,拒絕讓其他程式設計師接觸到這些程式碼。

將程式碼所有權的各種隔斷全部打破、由整個團隊共同擁有全部程式碼的做法,相較於上述方式要好得多。我讚同這種做法:團隊中每位成員都能簽出任和模組的程式碼,做出任何他們認為合適的修改。我期望擁有程式碼的是整個團隊,而非個人。(p.180)

儘管每位團隊成員都有自己的位置,但在緊要關頭時,每位團隊成員也要能夠接替其他人的位置。(p.181)

嘛,我同意原作者容易修改自己寫的程式,改起來可能比較快,但這是我唯一想得到這種「護住地盤」行為可能的好處了。所有人都能改所有程式碼,好處就像 Bob 大叔說的其他人可以看到盲點、提供不同意見、每個人都可以接替其他人的位置等等。

我好像除非完全是自己的 project,不然不怎麼在意別人看或改我寫的 code(只要別把結構愈改愈爛就好)。而且我也不怎麼喜歡讓自己成為「唯一能修改或看得懂某一塊程式的人」,因為……這樣放假還可能被找很煩,呃,咳咳,我只是覺得工作的時候就專心工作、放假的時候就好好放假。另外,東西其他人能接手代表我可以再去做其他東西。