是什麼阻礙了代碼的重用?問題是否應該只解決一次即可?
剛學編程的時候有種想法,認為難題應該只解決一次。
但漸漸接觸多了前端開發,經常要重複編寫代碼,特別是生成頁面時。
是什麼原因導致了代碼無法重用呢?
主要的問題在於,如果一個問題你沒有去在不同的環境下解決很多遍,你很難分得清楚,哪個部分是通用的,哪個部分是跟你當前的環境緊密相關的。如果你什麼都不懂,你做出來的代碼當然也沒辦法很好的被重用。所以問題不可能只被解決一次,我們追求的只能是每一次解決的時候花的代價要更少,長遠來講趨向於0。
因為復用並非無代價,而且代價往往還很高。
從工程上說,任何特性都不是無代價的。復用提供了解決一類問題的靈活性,而靈活性作為一種功能,同樣有代價——正如過多地使用虛函數有性能損失,而過多地使用 interface 則一定程度上降低代碼可讀性。如果構建靈活性的基石皆有代價,那麼我們不可能期望靈活性可以免費獲得。而所有的問題解決之道本質上都一樣:我們需要權衡每一個選擇的好處和壞處,做出對我們現在的項目最有利的方案。舉例來說:我們不會在項目最緊張的時候討論把業務邏輯抽取出來做一個通用的框架,原因很簡單:時間不夠用。我們也不會在討論怎麼設計通用框架的時候過多地討論我們具體項目的邏輯,因為追求通用性的設計目的導致我們不可能完全為某一個具體的業務優化。
所以從這個意義上來說,阻礙代碼重用的最大原因,事實上來自項目自身:復用代碼在絕大多數情況下,都不是一個項目的最終目的。對任何項目來說,唯一絕對存在的目的,是在指定的時間內完成客戶給出的需求。當短期內完成功能的需求和復用發生衝突時,理智的項目管理者都不會選擇將注意力放在復用上。當然,熱衷於復用的程序員必然會以長遠的好處為理由為復用辯護;但正如前面的規則指出的,這依然是一個工程上的選擇問題,因而仍然需要折衷。在遇到問題時,總是先倒向某一個結果再試圖解釋,這不是折衷,而是預設立場,這恰恰是工程的大忌。
至於第二個問題,回答是:是的,對任何問題只解決一次是理想狀態,但重複解決三到五次問題並非十惡不赦。客戶關心的是我們能不能解決他們的問題,而不是能不能對任何問題都只解決一次。——不要把自己的需求誤以為是用戶的需求,這仍然是一個工程問題。可復用的東西(小到函數,大到框架),一定是從諸多應用場景中抽取出來的,換句話說,一定要先有場景,在場景達到一定數量一定複雜度之後才能抽象出來可復用的部分,也就是常說的重構
一開始就追求復用性沒什麼意義,浪費時間不說,更可能假象的場景根本就不存在,或者實際情況超過想像
一些老手們經驗足夠豐富,之前遇到過的問題越多,在開始設計的時候就能兼顧到更多的復用場景
做開發,成長的幾個階段必不可少
1. 不做設計(新手階段,能實現就好)
2. 過度設計(了解的東西多了,總想追求完美)
3. 簡化設計(認知逐漸深入,學會取捨)
4. 最優設計(熟練掌握,知道概念適配場景)
其中23兩個狀態可能會往複多次,最終達到找到一個平衡的位置
所以我覺得題主的狀態正是逐漸進入過度設計的狀態,想追求絕對的復用,沒什麼不好,有想法就去實踐,讓結果來驗證你的觀念,很多東西的度不是別人能教會的,是必須要親自體驗才能了解的和掌握的重用的代碼,底層的好寫,上層的難寫。
底層代碼抽象的是機器,服務的是程序員。程序員就那麼點追求,讀寫數據,操作數據,要快,要穩,要容易用,滿足就行了。
上層代碼抽象的是需求,服務的是用戶。用戶的需求各不相同,但每個人都覺得自己的需求特有道理,特正義。你說這個我們做不到,因為用了XX lib,立馬被噴。
那麼多前端庫前仆後繼,折戟沉沙,卻又層出不窮,無非是因為某些需求滿足不了。需求總在變,庫也跟著花樣翻新,但總是差了一兩步。說到底,是什麼阻止了重用?
是人心吶,是變幻莫測得人心。
人心是一個 moving target,以固定的 pattern 來揣摩人心,結果就是一段想要解決十個需求,卻一個需求都解決不好的代碼。
所以,底層庫可以自頂向下的設計;而面向客戶的代碼,還要以解決當前問題為先,等到類似的問題多了,再考慮重用。不要剛遇到一個問題,就想寫個通用的解法,這樣的往往悲劇。
長恨人心不如水,等閑平地起波瀾。
是因為懶惰。
同樣的問題應該只解決三次。第一次,你怎麼知道這段代碼能夠重用呢?第二次,也許只是巧合?第三次,看來這確實是個重複出現的問題。然後你就該重構以重用代碼了。
當然還可能是因為偏執。重構容易出一些很明顯的問題,特別是當你使用的語言類型系統不夠強大而你又沒有足夠的測試的時候。這種時候,維護者通常的反應是:重構代碼首先放測試版,出了小問題就修正,只要不是很難處理的大問題一切好說。但是遇到偏執的維護者嘛,就只好放棄了: 放棄 you-get,轉投 youtube-dl。
過早優化是萬惡之源。
編寫代碼時,對「可復用」和「擴展性」的考慮,都應限定在一個很小的範圍內。否則的話,在當前版本多消耗的時間要遠遠大於未來複用代碼時省下的時間。
從「編程藝術」的角度來講,這種解決方法一點都不漂亮。每個人初學開發時都會有這種想法:要完美、要考慮周全、為了不存在的需求而編寫一堆「預備代碼」。但是,在經歷過幾個項目後總結一下,就會發現這種事是多麼浪費時間和精力,當初預想的需求90%沒有出現,出現的那部分也和設想大相徑庭了。自己提的問題, 算是想了很久了, 有了新的想法.
首先來看一個函數定義的簡單的加法, 能重用嗎? 基本可以. 儘管類型不夠嚴謹.(defn inc" [x] (+ x 1))
再看一個函數定義的 DOM 操作, 這個顯然很難重用.
(defn write-x [x]
(let [target (js/document.querySelector "#app")]
(set! target.innerText x)))
區別在哪裡? 首先這個函數依賴參數里沒有定義的內容, 比如 `js/document` 和相關的 API. 其實, 這段代碼還有相當多隱含的依賴, 比如說 DOM API, 比如說 DOM 結構和屬性.
也就是說儘管我們以為我們解決問題的代碼可以被複用, 實際上每一個依賴的細節都需要確認, 才能保證所謂的復用是真實而且直接的, 所有的依賴都需要恰好滿足. 而那些隱含的依賴往往不被人注意, 現實生活當中的問題往往比編程當中問題隱含更多的依賴.
那麼怎樣才能處理依賴最終進行復用? 首先, 必須承認很多情況下依賴難以滿足, 只有極少數可以被複用. 其次, 數學當中問題可以只解決一次, 原因是數學足夠抽象, 抽象到永遠現實當中根本沒有一個 1, 也根本沒有一個圓, 更沒有球體, 但是數學創造了這樣抽象的概念, 而抽象的概念是可以被複用的.問題的複雜度,無論是細節複雜度還是動態複雜度,大致都有一個迅速膨脹的過程
很多時候開發處在曲線的上升部分,以為理解了全部複雜度,嘗試建構重用,對於大一點的問題,這段上升曲線可以持續很長時間,哪怕一個普通Web項目的某個模塊,因為業務不斷演變,到最終穩定往往需要幾年時間,更大的項目,持續幾十年來積累認知,充分理解問題本身也是很正常的問題。
很多時候開發處在曲線的上升部分,以為理解了全部複雜度,嘗試建構重用,對於大一點的問題,這段上升曲線可以持續很長時間,哪怕一個普通Web項目的某個模塊,因為業務不斷演變,到最終穩定往往需要幾年時間,更大的項目,持續幾十年來積累認知,充分理解問題本身也是很正常的問題。
我認為大多數工程的方案最終都是可以重用的,只不過需要時間,科學和科技大致都是這麼一個過程。
你要問前端,我就說是因為前端工資可以很低。一個問題請廉價勞動力重複解決 3 次以上的成本,可能還是低於請架構師做抽象。這是中國人力資源市場的問題,在前端尤其明顯,那就是低端勞動力過剩,高端人才缺乏。
- 編寫代碼時,如果考慮今後的重用,工作量一般是只為當前情況考慮的三倍。(參見《人月神話》)
- 「考慮今後的重用」只能靠猜,一般都是錯的。
- 讀代碼比寫代碼難,所以那些「只為當前情況考慮的」代碼很少在問題稍稍發生變化的時候被改寫成更通用的代碼。
補充一下,原來的第三條寫的太簡短。第三條雖然再說一個困難的現狀,但是它也同時說明這是正確的重用方式。就是每次寫代碼的時候,只為當前情況考慮。準確的說是只為迄今為止遇到過的情況考慮。這樣一份代碼會經歷從專用到通用的過程。但是這個過程也是非常困難的。
同意@馮東的觀點,代碼只有在需要被重用的時候才被改成可重用的。如果有這個心,編寫代碼的時候也不必直接寫成可重用的,寫成可以方便改成可重用的就足夠了。
是你沒找到合適的重用的方法,像ReactJS裡面很多前端組件應該就是可以重用的。你自己都搞不明白要怎麼重用自己的代碼,你指望上帝替你解決了嗎……
因為,沒有兩個問題是相同的問題。
那些看起來相同的問題,因為其不同場合,不同上下文,導致他們仍然不是相同的問題。
另外軟體要解決的常常不是靜態的問題而是動態的,也就是問題隨著時間不斷變化。所以當初看起來跟你相同的問題隨著時間的推移也會變成不同的問題。
所以,先別問軟體為什麼不能復用,因為不能復用是正常態,我們要問的是為什麼軟體可以復用。軟體之所以可以復用只是因為一開始就寫出了可以復用的代碼,換句話說一開始就花費了幾倍的精力來寫最初的軟體,提前把將來的工作做了。所以將來才能復用。
不過現在通常認為提前做還沒有確定需求的將來才用得著的工作並不划算。所以軟體自然就不可復用了。其實沒那麼多廢話,唯一原則是:
實現類似的功能的代碼只應該在系統裡面出現一次。只說說自己的體驗。
三年前我意識到自己的的主要工作方向將向科學計算傾斜,工具傾向於python,然後開始搭建自己的工具模塊。從最初的數學工具,坐標系轉換,電磁場模擬,光的大氣傳播,大氣湍流模擬,到現在加了天文學工具,地理坐標工具,軌道計算,數據圖生成等等。許多計算量大的部分還用c寫了庫用來加速,都有配套文檔。可以說囊括了工作中需要用到的大部分功能。自己寫,自己維護,自己用。
然後今年三月,我決定停止維護。無它,這一堆模塊已經越來越難維護了。
這裡面有自寫的c的庫和python的模塊,也用到了諸如fftw、blas、opencv、scipy、numpy、matplotlib開源模塊,加上許多後來寫的模塊又會用到之前寫的模塊,依賴關係越來越複雜,發現bug要定位錯誤都非常麻煩,何況偶爾還要對現有模塊進行擴展,時間間隔長,不同時間對整體結構的考量都有不同,加在哪兒都成了問題。經常會出現原本在較上層的某個函數其實應該放到更低層的位置,改動的話卻發現這個函數在更多其它模塊里調用了。寫完還得完善文檔…更有時候同功能的函數明明存在,時間長了自己都不記得了,又往其它模塊里寫了一次…
況且python本身的版本更迭還好,2.x變化還不算太大,可各個三方模塊的更改則不是那麼好統一的。加上像scipy之類的玩意,新加入的功能很可能可以代替某個自寫模塊,是否使用都需要一再斟酌。
維護這個東西的精力已經超過了用它們帶來的方便。
本來像這樣的庫,應該在一定的時候進行一次重構。可惜現在工作了實在拿不出那麼多時間來整理了。
總而言之,代碼都是有壽命的。時間一長,掏出來再用的或許比重寫更加麻煩。重用本身就是神話。
一般來說阻礙是經驗。
靜態頁面是不是沒有像JSP那樣的include的方法,把重複使用的代碼包含進去,又或者像PHP也有自己引入重複代碼的方法,又或者是tiles。反正感覺html的頁面特別麻煩。雖然複製粘貼很快。但是修改起來卻是很大的工作量。個人不建議使用框架iframe,其中的缺點可以問度娘。。。前端表示壓力大大的。。。
奮鬥在第一線的程序員,我很認真的在重用和只解決一次的海浪裡面衝浪。
首先我特別欣賞 @蘇莉安 說的 「過早優化是萬惡之源。」不過貌似這句話是很多經典的書裡面說的,或者類似,因為我在很多地方看到。
但是這個回答值解釋了重用不能過早,所以並不是真正的答案。
首先回答是什麼阻礙了代碼的重用。這個要從多個維度進行解釋。
我們討論的範圍:
從技術和項目的分來來看:
技術:
技術這個問題上,我覺得也分為個體和群體。個體,也分為個體短期和個體長期。
群體:
群體想說的,主要是我們為了什麼編程。
在有一個問題裡面我在回答(鬱悶沒有了),不過還沒有完全寫好,被朋友邀請過來,所以我就先來回答這個,然後繼續。
如果編程真的是為了自己技術的提升的話。那麼重用是必須走的路。何為重用,就不斷的提煉自己的代碼,讓程序代碼的結構成為一種藝術。
我真的不知道怎麼來說讓代碼結構成為一種藝術,我也不希望抄很多大師的語錄,也不想用很多概念。
我這裡就說我覺得你在追求藝術的路上跑偏的情況:
編程語言會的多,所以我在提升技術。
api我了解的多,所以我能實現的多,所以我在提升技術。
我對需求了解的比其他人快。(這一點一定質疑特別多,我非常認同對於信息轉換的能力是程序員的戰鬥力,但是我一直不認為是技術的提升。所以戰鬥力提升不代表技術提升。)
我總結了多少種情況,有筆記,可以很快搞定常見的某某某問題。
我做過很多東西,可以直接把這個,那個處理一下,然後就變成這個。
所以,一顆不追求藝術的心讓我們拒絕了重用。
個體:
個體短期:
我第一次知道編程時html,這是當時我師傅說的世界上最簡單的編程語言。那時候我甚至覺得css都好麻煩啊。布局只會用table,而且還美其名曰 div 布局是不精確的。那時候真不知道什麼是重用。
後來我會寫asp了,我發現這世界太nb了,居然可以幾行代碼,寫出那麼多的東西。而且可以變成互動式的網站放到互聯網上,讓別人使用。那時候,每次能多會一種sql語句,能多寫一個功能,都會使我興奮不已。所以那時候,重用對我來說,還很遠。
又後來,我會用了dotnet,這個語言真好,居然有一種東西叫類。然後這個項目放,那個項目放。這個時候我覺得,我很強悍了,因為我比我同齡人都強。我不僅會asp,還會dotnet哦。所以這個時候多一門技能是我炫耀的資本,重用,我根本就沒在乎和理會過。
後來我創業了,我遇到了好多人,技術上,重要的有兩個,這裡我就不提他們的名字了,但是我一直記在心裡,謝謝你們,是你們讓我能夠更快的成長,能讓我了解藝術,並且開始走在藝術的路上。
說了這麼多,已經歷經很多年,我為什麼要放到個人短期裡面呢,因為我每一段就可以視為一個短期。
其實並沒有什麼阻礙了我們去重用代碼,只是我們還不知道重用。
還有就是我們很多時候是走在重用的路上,只是我們走的比較慢。
個體長期:
程序員很多時候並不是從小就喜歡,就立志要做這個,而且就算後來做了程序員,也是因為時髦,被某個電影的nb片段蒙蔽,還有家裡對於未來就業的壓力判斷。
那麼就有很多人其實並不喜歡編程,也許很多藝術家、醫學家、物理學家被淪為了一個程序員。對不起,你們都是散落在程序員中的天使。
更多的,就算是從小就立志要學習計算機編程,也未必就真的適合做程序員,但是我們不得不做,也許是從小的那種心結,也許是沒有其他的選擇,也許是我們自己覺得自己適合。
所以這註定了我們達到不了藝術的高度。所以我們可能很長時間,鬱郁不得志,或者就一直渾渾噩噩。重用對我們來說,沒有那個必要,其實更多的是沒有那個能力。
所以不是我們拒絕了重用,只是重用的台階太高,請允許我在台階下乘涼。
項目:這裡面來說,過早優化是萬惡之源來解釋最好不過了。在90%以上的程序員的工作中,遇到的項目,最大的也沒有多大。能遇到12306,那是佛,能遇到百度、人人那是菩薩,接下來還有羅漢、尊者等等。
其實我們什麼都不是,我們就是人,六道輪迴中的人。
在中國大部分的項目,從時間角度來看,基本上是沒有重用這部分時間的,就連測試的時間也給的很少。更加嚴重的是,項目經理對於項目的預期,完全基於自己的考慮,所以基本上誤差能在30%就已經很好了,50%合格,100%以上的都是非常普遍的,而這30%基本上已經是項目其餘的所有時間了。
所以永遠趕時間的項目摧毀了在項目階段重用。
其實不僅僅在於永遠趕時間。中國人特別喜歡一個外來的詞,叫迭代。說真的,我也很喜歡迭代,但是我想問一下你們知道技術上的迭代是什麼樣子的嗎?
在項目裡面,大家的迭代,不管概念是怎麼樣子的。實際上都是,一版一版的修改,每次製作一部分功能。到執行的層面上就更加可怕,產品經理隨意更改產品結構,UI,UE。這導致了前期越早進入重用的設計,就越陷入萬劫不復的地步。
所以中國式的迭代打碎了程序員的重用夢。
本來想說很多,分割線想了想也算了,就說一點吧。
要避免重用至上論。
項目就是項目,不是所有的地方都重用了有怎麼樣呢?改造成本很高?
如果你設計的結構自覺地重用很強,但是突然發現需求本質性改變,怎麼辦呢?
這裡的利弊衡量,不在於是不是重用,在於項目的目的是什麼。你我都不能衡量,或者說能衡量的人也未必真的能做出來對的決策。
在我只會asp的時候,我就覺得web編程需要mvc,後來在2008年,微軟出來了mvc前端框架,我特別高興,但是他不是全部。
後來我知道了mvvm,這簡直讓我在接下來的半年多的時間裡面保持持續每天只睡三個小時的亢奮狀態。但是他也不是全部。
而mvvm從代碼角度來講,可以只有幾行,但是這確實重用的極致(也許未必這麼高,但是在我心裡他就是)
但是我要說,mvc 就是重用, mvvm 就是重用, 重用就是藝術,不僅僅是你寫出來的代碼,更重要的是一種思想。一種藝術的思想。
再有就是問題是否應該只解決一次即可?
如果這個問題就只發生一次,那麼你也只能解決一次。
如果這個問題偶爾還會發生,就算你重用了,也許有一天你也忘記了。
如果這個問題經常發生,我這次解決了,下次再copy過去唄,無可厚非。
所以是否解決一次即可,我覺得當然可以。
但是如果你在追求藝術,那麼你應該會去解決,可預見性非唯一性的問題。
如果只有你懂,那不叫藝術,可以說那是某種財富。但是如果你覺得那是藝術,我只能說,傻逼 你好:)
ps:這裡的藝術是當前有價值的藝術。
這是一個偽問題:你必須把問題解決了你才真正知道問題的內容是什麼。(解決問題後才能定義問題)
所以你必須重複寫代碼之後,才知道這個代碼應該被寫成可重用的(這絕對是多數情況)。
推薦閱讀: