最近又有一個同事和我討論TDD并質(zhì)疑TDD是否真的有用霍狰,已經(jīng)記不清是第幾次類似的討論了,前段時間王垠的一篇關(guān)于 AI 的文章中也順帶黑了一把 scrum 和 tdd腿短,作為一個堅持實踐 tdd 的tdd粉,我覺得應(yīng)該深入討論下這個問題钝诚,有時候聽一聽不同的意見敲长,反而能讓我們看的更加清楚辑鲤。
那么盔腔,為什么這么多人不喜歡 TDD 呢弛随?
TDD 和設(shè)計
有一個普遍存在的誤解,認(rèn)為采用 TDD 能夠幫助我們做出更好的設(shè)計宁赤,而當(dāng)抱著這個期待去 TDD 的時候舀透,卻發(fā)現(xiàn)得到的結(jié)果也只是 just so so,瞬間粉轉(zhuǎn)黑??
我自己在最初嘗試 TDD 的時候也一直存在這種錯覺惑芭,畢竟在 TDD 的過程中的確可以得到更加面向接口的設(shè)計继找,通過小步迭代進(jìn)行快速重構(gòu)遂跟,最終得出的代碼相較于之前還是有明顯提升的。觀念的轉(zhuǎn)變來源于兩次Coding Dojo,第一次是TDD的Coding Dojo幻锁,通過使用TDD我自認(rèn)為得到了一個很解耦的設(shè)計凯亮,而同一個題目在第二次關(guān)于軟件設(shè)計的Coding Dojo中卻得到了完全不一樣的結(jié)果,采用抽象原子加組合的設(shè)計模式越败,代碼更加簡潔触幼,可擴(kuò)展性也更高硼瓣。
這件事讓我對 TDD 產(chǎn)生了懷疑究飞,差一點也要粉轉(zhuǎn)黑,不過堂鲤,在進(jìn)行一番思索之后我才發(fā)現(xiàn)我在糾結(jié)的根本是一個偽命題亿傅。
TDD 通常不提倡前期把詳細(xì)的設(shè)計全部做好,而是鼓勵在重構(gòu)過程中進(jìn)行涌現(xiàn)式設(shè)計瘟栖,根據(jù)需要一步一步剝離或者調(diào)整接口葵擎,根據(jù)TDD的3步軍規(guī),你新增的所有代碼應(yīng)該是剛好能保證前一個測試用例通過半哟,事實上酬滤,如果我們嚴(yán)格按照3步軍規(guī)去做,很快就會發(fā)現(xiàn)一個問題:因為每次增加的代碼不多寓涨,代碼設(shè)計只能一點一點調(diào)整盯串,在某一次的調(diào)整過程中如果代碼設(shè)計進(jìn)入一個岔道,往后可能就只能在這個岔道上一路向北戒良,不能回頭了体捏!看起來是因為遵守了3步軍規(guī),導(dǎo)致驅(qū)動出一個 just so so 的設(shè)計糯崎。
實則不然几缭。
雖然大多數(shù)時候我們要遵守3步軍規(guī),但是當(dāng)我們意識到有更好的設(shè)計涌現(xiàn)出來的時候沃呢,要果斷的拋棄現(xiàn)有的用例和代碼年栓,基于新的設(shè)計重新進(jìn)行 TDD!
而至于我們能否意識到更好的設(shè)計薄霜,則和我們在設(shè)計方面的能力有關(guān)韵洋,和TDD無關(guān)!
所以 TDD 只是驅(qū)動開發(fā)(Develop)黄锤,而不是驅(qū)動設(shè)計(Design)搪缨。
TDD 和單元測試的成本
另一個一直糾結(jié)不休的爭論是關(guān)于成本,如果一個功能只需要200行代碼就能搞定鸵熟,我為什么要多寫200行的測試呢副编?看起來工作量翻了一倍,而且一旦后面設(shè)計有調(diào)整流强,這200行的測試用例立馬就打了水漂痹届!
這個問題很難解釋呻待,特別是對因為存在類似疑問而從來不曾真正動手去寫單元測試的人,不管什么樣的解釋都看起來很蒼白队腐。在嘗試解釋了 N 多遍并失敗之后蚕捉,這里我再嘗試把這些失敗的解釋總結(jié)一下。
開發(fā)效率不僅僅體現(xiàn)在代碼編寫上
內(nèi)部質(zhì)量不受控的代碼柴淘,在調(diào)試和后期維護(hù)中會耗費(fèi)成倍的成本迫淹,一個簡單的 for 循環(huán)越界,或者 == 寫成 = 的手誤为严,在寫代碼時只是那么一兩秒的腦回路短路敛熬,但在未來可能會花上幾天甚至幾周的時間定位,更不用說復(fù)盤第股,分析報告和毫無意義的改進(jìn)舉措了应民!
單元測試能有效減少代碼中的低級錯誤,而TDD能在編碼環(huán)節(jié)就讓你更清楚的理解需求邊界和測試場景夕吻,不至于到調(diào)試階段才發(fā)現(xiàn)某些場景在編碼時遺漏诲锹。
即使是僅針對代碼編寫效率,TDD也有助于提升
TDD有助于我們進(jìn)行出面向接口編程涉馅,合理的接口劃分能將全局復(fù)雜的業(yè)務(wù)邏輯分解到各個獨(dú)立的接口實現(xiàn)中归园,從而降低局部復(fù)雜度;另外控漠,TDD讓編碼過程更專注蔓倍,更容易進(jìn)入到“心流”的狀態(tài)挫鸽,從而提升程序員的編碼效率播演。
測試的重構(gòu)是很自然
至于測試的重構(gòu),不妨和 FT 類比一下休建,如果用戶需求發(fā)生了變化碉渡,測試場景需要調(diào)整聚谁,F(xiàn)T也要進(jìn)行重新設(shè)計,UT 也是一樣滞诺,當(dāng)待測試的對象變更了(類重新設(shè)計)形导,對應(yīng)的測試代碼肯定也要刪除重寫。實際情況中习霹,除非是基礎(chǔ)數(shù)據(jù)類型發(fā)生重構(gòu)會觸發(fā)大面積的測試用例重構(gòu)外朵耕,大多數(shù)情況下代碼之間依賴的都是接口,相對而言接口是比較穩(wěn)定的淋叶,如果僅僅是接口實現(xiàn)的變更阎曹,往往只需要重構(gòu)對應(yīng)該接口實現(xiàn)的測試用例即可,對依賴該接口的類沒有波及。
這個問題是最難解釋的一個处嫌,只能說如果我們連這種成本都不愿意付出栅贴,那我們還是不要做軟件了。
TDD 和大神
我想Linus在寫Linux內(nèi)核的時候熏迹,應(yīng)該不是采用 TDD 的方式開發(fā)的檐薯,但是 Linux 絕對是非常偉大的產(chǎn)品,有很多偉大的設(shè)計注暗,我們也看到坛缕,實際上很多大神級別的人物,并不采用 TDD 的開發(fā)方式友存,包括前面提到王垠的文章祷膳,這段時間在看《Unix編程藝術(shù)》甚至對于 C++/OO 都并不抱太多好感陶衅。
所以我在想屡立,為什么呢?
后來想到一個可能的結(jié)論——對于這些大神級別的人物來說搀军,因為他們對于軟件開發(fā)本質(zhì)的理解已經(jīng)達(dá)到相當(dāng)?shù)母叨扰蚶琓DD這種工具或者方法對于他們來說,都太過于重量罩句,或者說束手束腳焚刺。
這樣一想就又釋然了,誰叫咱還沒到那高度呢门烂,還是老老實實寫 TDD 吧……