訪談 Kent Beck

與 JUnit 之父及 TDD 創造者討論編程與測試以及他如何看測試的演進

Du Spirit
Java Magazine 翻譯系列
17 min readJul 14, 2020

--

Translated from “Interview with Kent Beck”, Andrew Binstock, Java Magazine November/December 2016, page 36. Copyright Oracle Corporation.

Binstock: 我知道您現在在 Facebook 工作,您在那裡的主要工作是什麼?
Beck: 我主要著重在工程教育上,我正式的職稱是技術教練,這意味著我大多數時間是與工程師結對編程 (pair programming) 與對談。

Binstock: 他們是特定有經驗的工程師或剛進入這領域的工程師?
Beck: 所有類型都有。我發現如果我指導特定層級的一群工程師,我會聚焦在他們遇到的瓶頸模式,坦白說,我有點厭倦講相同的故事與解決相同的議題,所以,我為這些議題編寫課程,我們有一個非常好的組織透過課程加速工程師的學習,我們有給大學畢業生的課程,也有課程給正要轉型成技術領導的人,以及課程給從外部招募進來的技術領導人,因為 Facebook 的自有文化,所以如果您用過去習慣的,以命令與服從的方式引導,在這裡是行不通的。

Binstock: 當您在像 Facebook 這樣的地方工作,您可能比大多數工程師能看到不同層級的規模,所以會有什麼不同嗎?如果我問您如何審閱需在規模上多加考慮的程式,您會有什麼不同的說法?
Beck: 這是一個好問題,因為它很難總結,所以我可以給您一些實際的例子。日誌 (logging) 很重要,在某些情況下,效能 (performance) 也很重要,一點點效能的退化可能會讓整個網站掛掉,由於我們試著以有效率地方式使用資本、CPU、頻寬以及任何資源營運,所以有時後餘量非常小,因此,對某些團隊來說,效能大多是可犧牲的,而且只有小幅的增加,我認為那是不太尋常的。日誌完全是為了在某些可怕的事發生後能夠除錯,以傳統的極限編程風格 (extreme programming style) 來說,您不會需要它 (YAGNI),因此不會寫它,當然,當您需要它時,自然就會寫,即便事後不再需要,您仍舊會需要它。

Binstock: 我了解。
Beck: 您需要一個能在服務死後驗屍的選項,這選項是這麼有價值,我過去從沒注意到過。

Binstock: 您們如何將程式簽入到主線?我能想像到有大量的測試在簽入前會套用到那些程式,測試的規模比一般的商業網站要大,真是如此嗎?
Beck: 這不完全是真的,有一個我從經濟學教授學來的原則叫可逆性,假設您有一個複雜的系統對刺激的反應無法預測,Henry Ford 建造那些前所未有的大工廠,複雜的系統,一點點小的改變可能會有巨大的影響,所以他對這的應對是減少工廠可能的狀態,例如讓所有的車都是黑色的,然而不是所有的車都是黑色,因為 Henry Ford 很吝嗇於控制,所以噴漆站很簡單要嘛噴漆要嘛不噴。

這讓整個事情容易管理。但是,Facebook 不能減少 Facebook 的狀態,我們希望能持續增加更多的狀態,這是我們如何連結這個世界,因此,與其減少狀態的數量,我們讓決定是可逆的,在 Henry Ford 的工廠,當您切割某一片鋼材,您無法反悔,當然,我們在 Facebook 也是這麼做,如果您讓一個決定是可逆的,你不需要像您剛說地如此嚴格去測試它,你只需要注意何時去復原或關閉它如果它發生問題。

Binstock: 這是相當有趣的替代方案。
Beck: 不過,也有許多反例。例如,處理金錢的程式就特別嚴格地處理,Linux kernel 也是特別嚴格地處理,因為它會被部署在數十萬的機器上,但改變網站,您有相當多種不同的方式取得回饋,您可以用手動測試取得回饋,您可以在內部使用它來取得回饋,您可以釋出到小比例的伺服器上然後觀察數據,如果有些事開始變混亂,您只需關掉它。

Binstock: 所以不可逆的決定會相當嚴格地執行且極度的測試,而其他的會比較簡單因為可逆性。
Beck: 是的。

JUnit 的開發

Binstock: 我們來談一下 JUnit 的起源,您製作的相當多影片將它記錄下來,所以不只再走過一遍,讓我問一些問題,這些工作當初您與 Erich Gamma 是如何分工的?
Beck: 我們結對編程所有事。

Binstock: 所以您們倆全程參與整個專案?
Beck: 除非坐在一起,不然我們不碰觸程式,這持續好幾年。

Binstock: 那時候您們是用 TDD 的方式開發?
Beck: 是的,很嚴格,我們不曾在沒有一個壞掉的測試案例的情況下加新需求。

Binstock: 很好,所以你們如何在 JUnit 可以執行測試前做測試?
Beck: 自食其力,它初期看起來很醜,您可能需要從命令列開始,然後很快地,您得到足夠的功能讓您很方便地執行測試,然後,有時候您弄壞東西但卻得到誤診的結果,於是您會發現所有的測試都通過,但因為剛剛做的修改,我們沒有執行任何測試,於是您又必須回到一開始。人們應該試試這個練習,這是一個非常有用的練習,自食其力開發一個測試框架測試自己。

Binstock: 您在這之前所擁有的只有 SUnit (JUnit 的前身,Beck 為 Smalltalk 寫的),它好像沒有在寫 JUnit 時提供不只是慨念層級的幫助。
Beck: 不,我們完全從頭開始。

Binstock: 您在 JUnit 5 的角色是什麼?就我所知,您似乎沒有明顯地參與在這次的釋出中。
Beck: 是的,我想幾乎任何事都沒參與是最貼切的說法,事實上,我認為,在某一點上,他們討論如何在一個釋出中做出兩種不同的改變,而我說,讓它們變成兩個不同的版本,所以就一個家長的建議,就是這樣了。

測試優先

Binstock: 您仍然按照嚴謹測試優先的原則工作嗎?
Beck: 不,有時候,是。

Binstock: 是的,談一下您演進到那樣的想法,當我看您的書 Extreme Programming Explained 時,似乎沒有太多餘地,您已經改變想法了嗎?
Beck: 當然,當時我不知道有一個變數存在,它在抉擇自動化測試何時有價值時很重要,就是程式碼的半衰期,如果您在探索模式,您只是想知道一個程式可能怎麼運作,而且大多數的實驗都會失敗或是在幾小時或幾天內被刪除,那 TDD 的好處就不會生效,它還會讓實驗慢下來,增加從『我懷疑』到『我知道』之間的延遲,您會希望這延遲時間越短越好,如果測試能幫助您縮短致時間,那很好,但通常它們讓延遲時間更長,如果延遲時間很重要而且程式碼的半衰期很短,那您不該寫測試。

Binstock: 確實,探索時,如果出錯,我可能會返回,然後寫些測試讓程式朝我預期的方式進行。
Beck: 我學習到很多種回饋的形式,測試只是其中一種回饋的形式,它們確實有很多好處,但根據您所在的處境,它可能會是非常大的開銷,於是您需要抉擇,這是否是折衷提示的情境中的一個方向或其他?人們希望有一個準則,一個絕對的準則,但就我擔心的,那只是草率的思考。

Binstock: 是的,我想超過二十年程式經驗的好處是對一個整體的規則存在巨大的不信任,這規則堅定地適用。
Beck: 是的,這唯一的規則是思考,IBM 在這做得很好。

Binstock: 我回想您曾提過類似的事,例如 getter 及 setters,真的不用先替它們寫測試。
Beck: 我認為那比我說的要特殊一點,這始終是我的策略,沒有任何東西是必要測試的,很多人寫了大量的程式但沒有任何測試,然而他們賺了很多錢,也為整個世界提供服務,所以,很明顯地,沒有任何東西是必要測試的。有許多種形式的回饋,對於測試,抉擇的一個因素是:什麼可能造成錯誤?所以如果您有一個 getter,它只是一個 getter,它不會改變,如果您可能搞砸它,那我們需要不同的對話,測試不會修好這問題。

Binstock: 當初您歸納出 TDD 的規則時,其中一個基石是每次迭代,應該要在功能上有最小可能的增量,這觀點從哪來?最小功能性的增量重要性為何?
Beck: 如果您有一個巨大的,漂亮的意大利蒜味鹹臘腸,然後您想知道需要花多少時間可以吃完它,一個有效的策略是切一小塊來吃,然後做點算數,所以 1 mm 需要 10 秒,然後 300 mm 將需要我花 3,000 秒,可能更多也可能更少,可能有正向的回饋循環,也可能是負向的,或其他事情導致時間改變,但最起碼,您有已經有一些經驗。

就我的觀點,一個熟練的程式工程師與大師的分別是大師絕對不會試著一次吃完整個臘腸,首先,他們總是能完成大的事情,他們會將它切片,這是第一個技巧,知道哪裡您可以切割。

第二技巧是能以有創意的順序完成那些小的切片,因為您可能認為你必須從左到右,但是您並不需要。有些人可能說:『嗯,您必須先寫輸入的程式碼,才能寫輸出的程式碼』,我則是說:『我不認同,我可以建立在記憶體中的一個資料結構,然後根據那寫輸出的程式碼』,所以我可以先寫輸入再寫輸出,或是先寫輸出再寫輸入。

如果我有 n 個片段,我有 n 種因式排列組合,這其中有些沒有任何意義,但大多數有,所以一個大師級程式工程師善用這兩個技巧,將片段切得更薄,考慮更多種排列組合作為實作的順序,這兩個技巧沒有任何形式的極限,您總是可以切割更薄的片段,您可以總是思考實作事情的順序以滿足某不同的目的。

如果您告訴我在禮拜五有個 demo,相同的專案,我實作事情的順序會跟如果您告訴我在禮拜五跑一個負載測試,我會將它以不同的方式切割,我也會以完全不同的順序實作,根據我下一個目標是什麼。

Binstock: 若其他因素無法建議更小的切割,最小可能增量可以是個概括的準則去切割?
Beck: 我不相信有最小的切片,我相信總是有辦法將切片變得更小,就像我以為我已經切得夠小了,我總是能找到某個地方或某種方式將它切成一半的大小,於是我會敲我自己:『為什麼我之前沒想到呢?這不是一個測試案例,這是三個測試案例』,然後進行得更順暢。

Binstock: 好吧,如果謹守單一責任原則 (single responsibility principle),在我來看,您可以相當地動態,有非常非常小的運算,然後將上千個 BB 彈大小的函式組合起來,然後說明如何將它們放在一起。
Beck: 如果您要開啟 40 或 50 個類別,那會很糟糕,我會說在某些點上,那違反內聚力,這不是對的方式。

Binstock: 我想我們的方向是一致的,那就是切割會有個極限,更小的切片不會增加價值,反而會侵蝕其他好處。
Beck: 就這樣,今天我會睡一覺然後明天早上繼續,『為什麼我沒想到那個?如果我橫向切割而不是上下,它明顯會做得更好』,類似這樣,在週五的下午走出門外然後回家,對我來說,是一個觸發想法的方法。

開發流程

Binstock: 幾年前,我聽說您建議開發者,開發時應該準備一張紙跟一支筆在旁邊,以記錄開發過程中所做的決定。您暗示我們會被我們所做的紀錄的數量給嚇到,而那真的發生在我身上,當我看越多所收集的項目列表,我越是了解到當中斷發生,要重建中斷前的整個世界,需要仰賴能記住眾多的微小決策,間斷越長,越難以將那些微小決策找回。我有點好奇,您記錄微決策的想法是否有某種方式的演進,讓那些列表更有用,而不只是一個開發意識的練習?
Beck: 沒有,完全沒有,有一件事,我寫下這些決策,是因為我有記憶上的問題,我不太能在腦中記住複雜的程式,甚至是小程式,我可以做好一個結對編程的夥伴,是因為我可以依賴夥伴的記憶,坐下來然後試著寫複雜的大型程式已經不是我可以做的事了。

然而,我依然能寫程式,在 UNIX 命令列上,因為我能看到整個全貌,只要是一行長度的程式,我能夠建立它,像是一次一個命令,我能完成編程的任務,但它不是可以維護的程式,它們都是一行的程式,我做很多的資料探勘,所以如果您說,建立一個可以做 X 的服務,那不是不同年齡、以不同的方式或不同的速度等等的問題,那已經不是我可以獨立完成的事,那真的是讓人沮喪的地獄。

Binstock: 當我在想您所說的,我想起我自己在記錄微決策所做的努力,我有一點賞識新的事物像是 Knuth’s Literate Programming,實際上,您可以在註解中捕捉您正在做什麼,您嚐試做什麼,以及您所做的決策。我在聽過您討論這特別的紀律後,我真的用那種方式工作好一陣子,某種程度上,它真的有幫助,另一方面,它建立許多雜亂的東西,最終我還是要回去移除那些註解,所以我提出來的唯一原因是想知道您是否有更好的方式。
Beck: 我所知的 literate programs 是它不好維護,因為註解、圖解與程式之間的耦合太強,過去我不曾這麼說,但它就是如此,如果我對程式做了點修改,我不只要改程式和測試,還要改某四段描述或那兩個圖解,然後我需要重新收集資料並再次將它畫出來,因此它不能很有效率地維護。如果您有非常穩定的程式,然後您想解釋它,然而它不會有用,而我說的依然是有意義的。[譯註:這一段有兩個代名詞,that 與 it,讓我很猶豫是否翻譯對了?]

Binstock: 我和 Ward Cunningham 有過對談,他提到多年前與您結對編程時,他常常對於您們怎麼達成決策感到驚訝,以及透過問:『什麼事是我們可以做的,能最容易完成特定目標』的方式讓工具演進,如果您總是用可能最簡單的事情去做,在某個時間點,您不需要回去重構程式,讓您能對程式感到自豪,而不是一堆對問題的暫時解,您如何平衡這兩件事?
Beck: 當然,我不是被付錢來自豪的,像是 JUnit,要嘛不寫,要嘛我們寫讓我們自豪的程式碼,我們可以做這樣的抉擇,是因為我們沒有期限也沒有付錢的客戶。

但是經常,即使我不對程式感到自豪,但我的雇主對成果很滿意,是的,他們稱之為能用,然後我能因此得到報酬,所以有其他理由需要去清理那些程式碼。

答案是肯定的,因為您不知道本質,所以您做出局部最佳化的決策,然後您了解本質,學習,接著您會了解到設計應該變成這樣、那樣,然後這樣,於是,您必須做出決定,在何時、用什麼方式或要不要改進您的程式碼,有時候您會做,有時候不會。

但是我不知道替代方案是什麼?人們會說:『如何,我們來進行重構吧?』好啊,當然,所以有替代方案嗎?[譯註:不做重構的替代方案]

我記得我曾在丹麥辦過一個研討會,在一整天激昂的演講中討論迭代的好處,那天快結尾時,一整天坐在前排的一個傢伙,一直看著我,表情越來越不安,他在結束前終於舉起手問:『一次把它做好不是更容易嗎?』我想擁抱他,我說:『以我所擁有的同理心,是的,它可能更容易,我沒有其他說法了』

Binstock: 有趣的問題!
Beck: 我曾經在飛機上坐在 Niklaus Wirth [譯註:1984 獲頒圖靈獎,其著作 Algorithms + Data Structures = Programs 廣為人知] 隔壁,我告訴空服員,我說我們是同事,他會願意我移過去,所以我像是個跟蹤者,我是他的紛絲,我不介意 [譯註:被當跟蹤狂?],如果您有機會坐在 Niklaus Wirth 旁邊,您該坐過去。於是我們開始聊天,我跟他聊到 TDD 與漸進式設計,而他的回覆是:『我認為那非常地好,如果您不知道如何設計軟體』

Binstock: 那聽請來很像是 Wirth 會說的話。
Beck: 您只能說:『好吧!是的,我不知道如何設計,恭喜,您知道,但我不知道,所以我能怎麼做?我不能假裝我是您』

今日的測試

Binstock: 讓我聊聊微服務吧!它對我來說,對微服務以測試優先的方式開發會變得相當複雜,某些微服務,為了能運作,需要一大包其他的服務存在,您也是如此認為嗎?
Beck: 這其實跟在一個大的類別與好多個小類別之間做抉擇是一樣的。

Binstock: 沒錯,除了,我猜,您需要用超多的假物件去建立一個系統去測試待測的服務。
Beck: 我不這麼認為,如果它是指令式的風格,您確實需要大量的假物件,在函數式風格中,外部關聯是由呼叫的串連 (call chain) 來集中提供,於是我不認為那是必要,我認為您可以從單元測試得到很高的測試涵蓋率。

Binstock: 現今,UI 與過去相比是多麼重要,在過去與現在,您是如何對 UI 做單元測試?您有用過像 FitNesse 或其它框架,或您只是用眼睛檢查測試的結果?
Beck: 我沒有滿意的答案,讓我這樣說,我試過許多東西,我建立過整合的測試框架,我使用過別人的工具,我試過不同的方式去收集 UI 外觀,以可以測試的方式,但沒有一樣可行。

Binstock: 如今,您仍然處在相同的情境,是嗎?
Beck: 是的,我沒看到什麼從根本上改變,所有都跟誤判 (false positives/negatives) 有關,測試告訴您所有的東西都正常以及所有的東西都壞掉的比例是如何?這會對測試的信任造成傷害,測試框架有多常告訴您有東西壞了,但其實所有的事都是正常的?很常,一個像素非常細微的改變,然後測試全壞了,然後您必須一個一個走遍測試,『喔,不,這是好的』,到您拆掉那些測試之前,您不會太常去做那件事。

Binstock: 代價是損失時間與失去信心。
Beck: 是的。

開發環境

Binstock: 現今您喜好的開發環境是什麼?不論是在家或在工作
Beck: 任何像是開發的事情,我都在 UNIX 的命令列或 Excel 中完成。

Binstock: 在 Excel 中?
Beck: 是的,因為我可以看到所有的東西。

Binstock: 怎麼說?
Beck: 像是轉換,我在 UNIX 命令列中做資料轉換,像是數字轉數字,然後我用 Excel 將它們畫成圖片。

Binstock: 當您在進行非資料探勘相關的編程時,如您之前提及的,您仍然使用 Smalltalk 做探索之類的工作。
Beck: 是的,對我來說,Smalltalk 的好處是我記住 API 足夠長的時間,我仍能掌握所有的細節。

Binstock: 您一貫使用多螢幕嗎?
Beck: 是的,越多像素越好,Terry Pratchett 有個很好的說法,他說『人們問我為什麼有六台螢幕接到我的 Mac,然後我告訴他們,因為我無法連接八台』,Oculus 或其他虛擬實境的技術開始要吹起漣漪,但沒人知道是哪種形式。

Binstock: 我們勢必將經過好幾次的迭代,在虛擬實境實際找到協助編程的角色之前。
Beck: 是的,我深深相信可以擺脫文本式的原始碼,然後能直接操作抽象語法樹,我曾和我朋友 Thiago Hirai 一起做一個稱作 Prune 的程式編輯器的實驗,它看起來就像是一個文字編輯器,顯示一個文字編輯器該有的內容,但是您只能對抽象語法樹進行操作,而它真的比較有效率,更不容易出錯,它不需要太多認知上的努力,讓我深信那是未來的浪潮,我不知道它是否能在 5 年內或 25 年內成真,但我們將很快在未來某時間點能操作語法樹。[譯註:想法真的很特別,我則是無法想像該怎麼操作抽象語法樹]

Binstock: 是的,所有的事情都在改變與往前進,但我們真的沒有從用墨水和紙張寫程式的程度邁進多少。
Beck: 不,我們仍在打洞卡上寫程式,它只是在某一層上再畫上一層,但它還是相同的東西。

Binstock: 程式員活動的地方和初始相比沒有進步太多,除了有不錯的 IDE 和其他這類的東西,實際的行為仍是相同的,最後一件事,我知道您是一位音樂家,您在寫程式時聽音樂嗎?
Beck: 當然。

Binstock: 什麼樣的音樂,您會喜歡在寫程式的時候聽?
Beck: 我用它來調節我的能量水平,所以,如果有點激動,我會聽一些舒緩的音樂,我的 go-to 音樂是 Thomas Tallis 的 The Lamentations of Jeremiah,是一個非常低調的聲樂四重奏中世紀音樂,如果我有點低迷,我需要把自己帶起來,於是我聽 go-go 音樂 [譯註:我對音樂不熟,不確定 go-go 是一個名詞還是指精神充沛的],那是原產於華盛頓特區的一種鄉土爵士樂。

Binstock: 好吧,我從沒聽過。
Beck: 那是專屬我的音樂。

Binstock: 太棒了,謝謝您!

譯者的告白
這一篇翻得很辛苦,沒想到像是閒話家常的對談,比技術文章還更難翻譯,這應該也是因為生活詞彙實在太少了,所以一些看起來很短的句子,完全不知道該怎麼翻譯的很生活化,更不用說對話中的代名詞,要想半天了。

這是 2016 年底的 JUnit 5 專刊的其中一篇,沒想到這篇文章翻譯到現已經過這麼久了。

--

--