看板 Soft_Job 關於我們 聯絡資訊
單元測試有時候反而破壞程式碼的易讀性和維護性 因為要做到單元測試,就得斷開所有的相依性 而對抗相依性,作法就是引入 DI。但是 DI 就會增加代碼閱讀和維護的複雜度。 舉例來說,如果代碼內有時間上的相依性,例如用了 DateTime 物件取得現在時間做某些 判斷,原本可以很簡單的寫出易於閱讀和維護的邏輯: If (DateTime.Now > 12:00:00) then return “PM” else return “AM”; 為了讓單元測試可以控制驗證條件,只能 Interface IDateProvider { virtual GetNow(); } Class DateMock : IDateProvider { GetNow() { return 13:00 } IDateProvider dateProvider = container.Build(....); If (dateProvider.GetNow > 12:00:00) then return “PM” else return “AM”; 然後再搞個 config 想辦法讓程式吃到你寫的 DateMock 類別.... 上面是sudo code 就不用討論語法細節 這就是單元測試的代價!程式真的會比較容易閱讀嗎? 單元測試要花在切除相依性的條件花費的成本時間遠高於撰寫production code 而且你的 production code 能不能賺錢還不知道咧? 到底需不需要單元測試和 clean code ? 先搞清楚寫的用途和目的,你寫的東西有沒有 真正的商業價值再說吧。 不要把 clean code 和 TDD 無限上綱了 工程師最容易自嗨就是這樣,還會自以爲「這是專業」? 乞丐的乞討專業比我們強也不會產生「價值」。 ※ 引述《banqhsia (BEN)》之銘言: : ※ 引述《peanut97 (丁守中)》之銘言: : : 大家中秋節快樂,快收心了。 : : 想問一個假設性問題,大家在工作上,如果有一份專案的 code 是某位前人一手寫的 : : 後來新人加入,變成前人帶新人,此時繼續維護那份code。 : : 但再過一陣子,前人離職了,唯一的創始者走了。 : : 新人把舊 code 重構,或是砍掉重鍊的機率高嗎? : 先跟主管、老闆提,確認有人支持你,不然你會被當成怪物 : 「為什麼要改?」 : 「系統好好的幹麻改?」 : 「改了有好處嗎?」 : 「會花多久時間?」 : 「時間剩不多,要動不動隨便你,不要影響到時程」 : : 我的想像是,如果一份code是出自於1個人之手 : : 我的想像是,如果一份code是出自於1個人之手 : : 那麼code就是他的世界觀、他的切入點 : : 那麼code就是他的世界觀、他的切入點 : : 後面的人看著他的世界觀,有時候不一定能全部接受 : : 而有人的地方就有政治 : : 當他還在的時候,當然就不會亂動。 : : 而當他走了的時候,後面的人,一看不爽,就可能改寫成自己看得爽的、 : : 好改的code。 : : 如果是一個團隊,那當然要好好討論為什麼要改 : : 哪些因素造成現在不好的情況,以及主管同不同意改等等的。 : : 只是我很好奇,1,2人的專案,改的機率高嗎? : : 是不是,code只能是「現在還存在公司的人」能控制的才行。 : 我們公司的經驗,以前因為很多原因 (十幾年前的 code) : 導致系統沒有測試、沒有嚴謹的 coding style、方法註解也很少 : copy paste 是基本,沒有遵照 SOLID : 錯誤不是丟 null 就是 false (欸! 我的 Exception 呢? : 每個 team 成員想怎麼寫就怎麼寫,不管後續的漣漪 : 反正東西交出來就好 多棒 : 然後中間當然成員就是來來去去的 : 我看這之中大概也是 有人進來 -> 看 code -> what the fuck? -> 離職 : 大概就是這種循環 : 因為自己痛過,知道 clean code、設計模式 的重要性 : 進公司沒多久就跟主管說這 code 不能搞,一定要重構 : 每個工程師一定看不爽前人的 code XD : 但是不是說說而已,總是要提出改善的方法 : 理所當然地,你前面那些問題我都被問過 : 幸好我的主管與老闆也是支持我這件事情 : 但是我們討論的結果,現在的 code 也沒辦法全部翻掉,怎麼辦 : 在那個時候剛好要把原本的系統生出一套 API : 原本的系統是 server-side template render,模板與資料、樣式高耦合 : 這個沒辦法改成 API : 我們的作法是,把原本 DAO 抽出來,放進框架裡當成 library : 然後加一層 Adaptor 讓新系統相容舊模組 : 舊的 DAO 邏輯怎樣就不去動他,要改一律在 Adapter 裡面改 : (就算 method rename 也是) : 在這個新系統,以 SOLID 與設計模式為基楚 : controller 與邏輯之間,包裝成 service 呼叫 (一律把該寫的東西放在該去的地方) : service 只准有抽象的敘述,實作的部分寫成介面去依賴,由子類別注入 service : 使用 DI Container 自動注入依賴的類別,不准直接 new (方便替換測試) : 提交功能分支以前,要一併提交單元測試與 functional test,否則不准進 develop : 這些都是規定好寫在專案文件裡面的 (以前沒有的文件,現在開始留下記錄) : 接著就是重頭戲的部分 --- 跟人有關的 : coding style 怎麼統一? 就算統一了還是會有漏網之魚 : 不小心看到 violation 還是會白眼翻到尾椎 : 所以這邊必須用自動化工具檢查,綁在 CI 流程裡 : 這樣已經可以省掉很多人工的部分 : 接著定期 code review,挑出變數不懂、「比較不易修改」的類別… 等等 : 這樣至少做到了幾件事情 : 1. 以前沒有的,現在開始留下記錄 : 2. 把 legacy code 加上 test : 3. 把系統改成 API server 從視圖抽離後,再動前端 (refactor 分階段) : 4. 統一 coding style : 5. 符合 SOLID、易於修改的架構 (為了以後著想) : 當然前面幾篇貼文,有些前輩也有提到 : 「有人的地方就有政治」 : 這句話應該劃 _底線_ 三顆星 ★★★ : 這之中太多阻礙了,電腦不會背叛你,人會 XDD : 做了這麼多品質保證措施,一定會遇到成員反對 : 「一個變數而已,幹麻花這麼久時間討論?」 : 「我覺得 code review 花太多時間」 : 「我覺得一個東西,幹麻拆這麼多類別,放在一個不就好?」 : 深入探討提出這些問題的同事心態,不外乎幾個原因 : 1. 影響 issue solving 時間 --> 覺得產出變慢 : 2. 覺得寫單元測試很麻煩 --> 覺得程式會動就好 : 3. 覺得一個類別不用提煉為抽象+實作 --> 現在又沒這麼多需求 幹麻拆? : 但他們忘記事實是,程式本來就是會修修改改的,而且也是會給別人改的 : 這讓我想到一句話 : 「 : 據統計,你現在挖的坑,有80%會自己來擦屁股 : -- 不知道是誰 」 : 即便跟他們說品質是什麼,clean code 的重要性,他們還是覺得『不需要』 : 所以大多數的問題,都是「人」造成的 : stop writing legacy code,從你我開始 : 但如果你身邊沒人支持,更進一步的說: 沒有得到老闆/主管支持 : 這公司也差不多 GG (因為不在意品質) : 如果你是 team 的其中一個人,試著先從統一 coding style 開始 : 我在 Modern Web 2018 聽到有一場 keynote 是 Terry Yin 大神的「遺留代碼經濟學 : 裡面有提到,其實也不用這麼討厭 legacy code : 因為有那些東西,我們工程師才得以被養活。 : 如果你發現有 code 裡面處處是坑,怎麼辦? 那就別再挖坑了啊!! : 我們現在做的,只是把以前沒做的都補回來而已 : 但是,不要再欠下更多的技術債 : 債總有一天還是要還的,而且還會生利息 : 最後我要說一句 : 很多前輩們會建議,要重構前先寫測試 : 但是 : 一個萬能類別/萬能方法/高耦合,要寫單元測試,發現整個系統都要寫 : 有些 hard code 的 class 還不見得可以 mock : 「人」的問題 : 無解。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 220.135.20.48 ※ 文章網址: https://www.ptt.cc/bbs/Soft_Job/M.1537962940.A.A36.html
Killercat: 這個很正常 要先界定黑箱範圍 這算FAQ了... 09/26 20:10
Killercat: 黑箱一開始一定非常大 唯一的要求就是testable output 09/26 20:11
Killercat: 慢慢穩定以後才會慢慢的在把黑箱切小 09/26 20:11
Killercat: 另外其實你舉的例子怪怪的 這input output都是testable 09/26 20:12
Killercat: Mock也沒那麼困難 我不知道為什麼要搞那麼複雜... 09/26 20:13
accessdenied: 原本Input不在自己可控的範圍內,早上和晚上測到的o 09/26 20:15
accessdenied: utput不同就不能說是testable, 因為單元測試應該要 09/26 20:15
accessdenied: 能隨時跑、隨時測、都一致。 09/26 20:15
Killercat: er... Mock DataTime.now()很困難嗎 o_oa? 09/26 20:16
Killercat: 我知道你想表達的點 但是GMock(C++) Mokito(Java)做到 09/26 20:17
Killercat: 這件事情都不困難啊... ? 09/26 20:17
landlord: 我不用 interface 也可以模擬 DateTime 耶。 09/26 20:17
accessdenied: Anyway, 只是個例子,不用糾結在DateTime 的mock困 09/26 20:18
accessdenied: 不困難,實務上絕對會遇到為了Mock而大費周章的案 09/26 20:18
accessdenied: 例 09/26 20:18
landlord: 你講的是學習單元測試必定會碰到的障礙,這肯定沒錯 09/26 20:18
landlord: 但碰到障礙是解決障礙,還是退回原路,就是選擇了 09/26 20:19
Killercat: 有是有啦,不過真的不常見,底層才比較會碰到 09/26 20:19
senjor: 有,Database的Accessor要MOCK的確蠻麻煩的 09/26 20:19
landlord: 任何東西無限上綱跟把它當萬靈丹,都是有問題的 09/26 20:20
senjor: 但是回頭來說,程式也是人寫的,既然都知道有特例,幹嘛不 09/26 20:20
landlord: 但怎麼拿捏尺度,怎麼往更深入和實務學習,就是專業差異 09/26 20:20
Killercat: 我是覺得 碰到mock困難 該做的應該是提出案例討論 而非 09/26 20:20
senjor: 針對特例做閃躲就好,何必因為特例否決全部 XDD 09/26 20:20
Killercat: 全盤否定掉TDD / UT的concept 09/26 20:20
landlord: 為了單元測試而多墊很多不必要的中間層,那是菜鳥錯誤 09/26 20:21
Killercat: 應該說 除非語言很奇耙 不然其實現有套件來講 應該很多 09/26 20:21
Killercat: 都不需要你墊這些中間層只為了能測... 09/26 20:22
Killercat: 或者中間夾了層打不爛的巨大黑箱 不然其實特例討論即可 09/26 20:22
landlord: 大部分測試案例難加,難維護,維護成本高,都是設計太爛 09/26 20:23
landlord: 而要解決的問題是「設計太爛」,而不是「不寫測試」 09/26 20:24
accessdenied: 我沒有要全盤否定喔。但對堅持一定要的人,我覺得是 09/26 20:24
accessdenied: 可以多想想的 09/26 20:24
Killercat: 我倒覺得思考能不能做到的可能性很重要 不過的確,總會 09/26 20:26
Killercat: 碰到一些窒礙難行的時候,我是覺得就討論看看 09/26 20:26
Killercat: 說真的以我的經驗 這種case不是很多 09/26 20:27
x000032001: 不知道貴公司一天下git blame的次數有沒有上千次 09/26 20:29
sharku: Mock技術決定一切 09/26 20:30
landlord: 如果模擬now寫成那樣子,我贊成你,我也希望他不要寫。 09/26 20:32
landlord: 只是在製造債,把簡單代碼搞得更複雜而已。 09/26 20:32
Killercat: 文雅點 我們都叫他annotate而不是blame XD 09/26 20:34
accessdenied: 喔!91大週六有課程喔?工程師該去的都去喔,不要再 09/26 20:49
accessdenied: 寫拿著那噁心的DI code了 09/26 20:49
Killercat: DI都是有原因有目的才DI 不是DI噁心 是亂DI才噁心... 09/26 20:50
accessdenied: 也是啦,刀子殺人不是刀子的錯,是用刀的人 09/26 20:52
KanzakiHAria: 大師先推再說 09/26 22:15
viper9709: 原來是這樣 09/27 00:45
superpai: 你不會寫 pure function嗎? 09/27 09:08
Ghamu: 好棒 09/28 00:34