前言:本篇文章是閱讀 Overengineering can kill your product — Mind the Product 後整理的心得再加上自己的經驗所撰寫而成。
當開發軟體的時候你是傾向盡量設計的簡單好懂,或者是考慮未來會發生的各種狀況設計一個周全有彈性的系統呢?
想得太簡單可能會太僵硬、需要經常修改或是複製程式碼,考慮的太多又會讓程式碼變得太複雜而成為另一種難以修改的狀況。只有維持在一個適合該專案的複雜度才能讓開發工作事半功倍,但這件事情其實並不容易做到。
我們今天想討論的是考慮太多的狀況,也就是過度工程 (Over-engineering)。
Over-engineering 是指實作或設計一些東西來解決一個你根本就沒有的問題,用過於複雜的方式來解決問題,但其實有更簡單、顯而易見的方式可以解決同樣的問題,而增加了不必要的複雜度。比如說為一個不需要 plugin 的軟體制定了多層次可擴展的 plugin 架構。
但為什麼這件事情會發生呢?
幾個原因
會發生 Over-engineering 的情況大多是無意的。第一個最主要的原因是開發軟體時為了「以防萬一」加入了未來可能會用到的架構或是程式碼,但這個以防萬一的情況發生的機率卻很低甚至根本不會發生。
第二的原因跟工程師的成長有關。當工程師在學習時會吸收一些新知後,就會在自己認為需要的時候嘗試著使用新學習到的知識,但誰天生就是大師呢?在尚未熟悉的狀況總會拿到槌子就開始到處亂釘釘子,想要到處試試自己新學到的酷東西,並且不知道設計到多複雜是恰到好處的狀況下就出現了 over-engineering。
不過隨著自己被自己寫的複雜程式碼雷到之後,慢慢的會回歸到比較簡單的寫法。
這個過程通常會來來回回的,有時候會後悔自己應該多考慮一些,有時候會覺得自己怎麼會把事情搞得這麼複雜,最後逐漸收斂到一個平衡的狀況。
睿智的軟體開發者或許可以避免這個狀況,但我是中過自己的招。
第三個常見原因是需求不清楚。在這樣的狀況下,開發者會預先作一些假設以及額外的功夫來確保自己不會受到這樣不確定性的影響,這也很有可能會造成 Over-engineering。
最後一個是你可能需要考慮換工作的情況,就是你工作太無聊了。如果你的工作沒有令人興奮的挑戰要處理,此時你可能會透過嘗試新東西來排解無聊而導致無謂的複雜度。說真的這個我也發生過,年輕的時候我曾經簽了一個很長的工作合約,而其實那邊的開發需求不多,所以我確實會把比較新的技術引入到工作中。
造成的影響
當開始實作以後都不會派上用場的功能或架構時,代表花費的時間都沒有實質上的回報,就會開始造成時間的浪費。
等到開發完成後,通常複雜度會同時影響 (a) 修改、新增功能時所需要的心力 (b) 其他同事要入手所需要花費的心力。有完善文件是比較好的情況,當沒有文件時,即使設計得再優良的架構或程式,如果沒有人可以理解與修改,而且這些複雜度又沒有派上用場,原本的問題又更加嚴重。
這樣的複雜度也會跟隨著這個產品的生命週期一直存在,當這些架構多存在一天,每個參與這個專案開發的工程師就會一直受到影響,開發與維護成本上甚至有加乘效果。
上面所造成的影響都會造成要花費更多時間與資金成本在專案上面,如果這是一個新創公司的產品,搞不好因為 Over-engineering 就直接導致把錢燒完關門大吉了。
如果這是個簡單好懂的實作,即使較沒彈性需要經常修改,但是由於容易理解的關係,可以很快的進行修改,整體成本的花費通常都會比較少。
過度工程的例子
我自己曾經在 side project 作過自然語言的 BDD 測試規格,這樣的好處是即使不是工程師也可以透過自然語言(也就是中文)撰寫規格,而這些規格會被自動的套入測試案例當中變成一個「可以執行的規格」。
這聽起來很理想 — 但是如果沒有不懂寫程式的人加入,為什麼不用一般的測試案例,直接讓工程師可以更直觀方便的寫測試呢?
所幸是這是一個實驗專案,是自己對技術的探索,但想像這樣的技術如果在工作上面使用又沒有實際造成正面的影響就不妙了。
另外經常發生的就是在產品還在探索階段就引入 Microservice 或是可以承載百萬人以上的複雜基礎建設等。以上這些技術在適合的時機都是非常好的選擇,但是如果在錯誤的時機使用就會造成不必要的痛苦。
比如說在太早期就架設了 Kubernetes,但是在產品探索的階段時一台 heroku, vercel 或是 EC2 都是更優良的解決方案來探索各種可行性,等到真的需要到使用 Kubernetes 這樣的架構時,那已經是個令人羨慕的擔憂了,代表你的產品很多人使用,需要更有彈性擴充的基礎建設。
如何避免
其實要警覺到過度工程的苗頭並不容易,除了一些很顯而易見的例子外,大多在討論時並不容易察覺這樣的現象。但有些行動對減少 over-engineering 有所幫助。
第一件事情就是開發時要盡可能的了解使用者,並且透過跟他們接觸,了解到他們所遇到的問題。產品開發最重要的目的就是要解決使用者的問題,如果我們花費了時間卻沒有解決任何使用者的問題,很多時候都是白費力氣。
跟使用者有更多的互動可以更感同身受的了解到他們的問題產生共鳴,透過使用者的角度來評估你的工作,而不要單純只用工程的角度來評估。
同時提高透明度也會有幫助。試想這樣的情境:你的工作每天就是從 backlog 裡面挑出一件工作執行,但是你並不理解這個任務的脈絡是什麼,不知道上面為什麼決定要作這個任務。此時在沒有脈絡的狀況下,就只能以工程的角度來看待工作任務。
如果專案執行透明度更好的狀況下,成員在認領工作任務時,可以知道任務目的,實作之後對專案與使用者會帶來什麼改善。同樣都是認領工作但有了背景脈絡後,成員就可以知道實際要解決的問題是什麼,解決問題時除了從工程的角度審視外,也可以從使用者的角度來思考解決方法是否可以解決使用者的問題。
提高透明度的方式有幾個,比如說更好的在 issue tracker 紀錄這些脈絡,或者是營造團隊內每個人都樂意回答問題的氛圍,而不要抱持「你作就對了,問那麼多幹嘛」。
最後成員之間如果能夠更經常交流知識與經驗也可以降低發生的機率,在 Perpetual Protocol 工作時成員們經常採用 Pair Programming 再加上團隊內的知識分享也是個很不錯交流知識的方法。
當知識經常在成員之間交流時,不同的開發經驗會在成員之間流動,如果有人曾經遭受到複雜性的荼毒,也可以更好的分享這些經驗。
但程式愈簡單愈好嗎?
其實不然。over-engineering 最大的問題是製造了「不必要的複雜度」,但有許多專案都有複雜度的必要。所以問題是「不必要」加上「複雜度」。許多產品還是會需要有「必要的複雜度」,就如同前面舉過的 Kubernetes 的例子,當我們的產品已經需要更彈性與擁有豐富工具的基礎建設來擴充時,我們還是會需要更複雜但可以對專案帶來正面影響的工程方式。
Over-engineering 之所以難判斷,在於每個人所認知的未來情境都不同,當我們在討論一件工作所考慮到的未來情境到底是「足夠」或是「不足」並不是那麼容易取得共識。
如果大家對可見的未來的想像類似時,要取得共識是比較容易的。但是有些人特別出類拔萃,他們所擁有的知識所預見的未來可能比我們想像的要更遠。如果從事後諸葛來看幾年前的事情會變得很容易判斷到底是「過度工程」還是「神預測」,但是當下要判斷就不是一件那麼容易的事情。
在這樣難以判斷的時候就只能透過更了解你的使用者、透明的工作環境以及經常性的知識交流降低 over-engineering 的問題。而身為預言家則需要有更好的溝通能力能夠把你所看到的未來順利的傳遞給大家。
結論
就如同我上面說的其中一個原因,工程師在學習知識時,總是會有一段學習的歷程,而這段歷程的實戰經驗卻很難傳授給另外一個人。很多軟體開發的方法論看起來很美好,即使旁人跟你提醒世界總是不完美的時候,當下也不見得能夠接受對方的說法,甚至覺得對方不求上進,不想把事情做好。
唯有自己踩到了自己設下的陷阱,從各種專案的戰場上歷經風霜的歸來之後,才會從自己獲得的寶貴經驗知道實務上要怎麼恰到好處的解任務。
面對 over-engineering 我們不該是往死裡打的抵抗它,而是要了解它存在的緣由,並且多跟你的團隊成員討論溝通,強調產品的價值何在,透過各種方法來減少 over-engineering。即使沒辦法有一個完美的解法,也要控制影響在一個小範圍內,透過實驗適度的讓每個人都獲得一些經驗(不管是美好或是苦澀的)。
然後,繼續往前踏上成為一個更好的工程師的旅途。
參考資料
如同開頭所述,本篇文章是 Overengineering can kill your product — Mind the Product 閱讀後重新整理的心得再加上自己的經驗所撰寫而成。
另外也閱讀了前同事的文章 過度設計 (Over Engineering) | 喲哪桑 Speaking 之專案工作日誌,這篇文章是超過十年前寫的,看來這個問題過了這麼久還是大家關心的事情。