看過一些關于代碼封裝的規范的書,不乏一些國外大牛的書,我印象比較深的比如:一個函式的代碼不應該超過20行。
我是從來沒有遵循這個規則,也不太能理解為什么要這樣做的原因。我自己始終覺得“業務完整性”的表述更重要。比如一個“業務”實作的代碼,如果要通過多個函式,甚至于多個逐層呼叫的函式來完成,這種業務代碼在我來看,是很難閱讀的(需要不斷的進入/跳出函式,這很打斷思路),所以我自己的習慣始終是,一個“業務”的代碼,順序讀下來,代碼間分段明確,注釋足夠,閱讀效果是最好的,幾百行代碼讀起來完全不費勁。
當然,我以前不太寫c代碼,我寫的是c#這種代碼,使用的是VS這種高級IDE,是不是這種高級語言和高級IDE下,代碼規范如我這樣寫更好?我也只是猜測。
最近在看一個歷史專案的c代碼,把我這個疑問又勾起來了,我把它作為案例,拿出來問問大家。
它是這樣的,在一個實作中,封裝了七八層函式,這些函式都只被呼叫過一次,也就是函式封裝起來只是被一個地方呼叫。那我就奇怪了,這樣的話封裝起來干嘛?封裝起來不應該是為了“復用”嗎?
這些函式,依次被呼叫,且僅呼叫一次:
parseVarBind()
parseSequence()
parseSequenceOf()
parseRequest()
parseCommunity()
parseVersion()
parseSNMPMessage()
SnmpXDaemon()
代碼類似:
SnmpXDaemon()
{
//一些代碼
parseSNMPMessage()
{
//一些代碼
parseVersion()
{
//一些代碼
parseCommunity()
{
//一些代碼
parseRequest()
{
//一些代碼
parseSequenceOf()
{
//一些代碼
parseSequence()
{
//一些代碼
parseVarBind()
{
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
請問:
這種封裝方式,每個函式的代碼量是不多,每個函式不超過50行,但
1、這種封裝方式合理嗎?
2、如果合理,為什么合理?
uj5u.com熱心網友回復:
請牢記:源代碼本身的書寫是否結構化或面向物件或符合設計模式或敏捷…并不重要,重要的是你是否使用結構化或面向物件或符合設計模式或敏捷…的方法命名識別符號、閱讀、修改、檢查、測驗源代碼。意思是你程式結構看上去再合理,再簡潔,也不一定比看上去一團亂麻的程式結構在運行或修改時更不易出錯,更方便修改,出錯了更容易找到哪里出錯和具體出錯的原因,更容易改正錯誤。
試對比
圖書館(對圖書的分類夠結構化了吧)
和
搜索引擎(可看作是扁平化任何結構資料,僅支持全文檢索)
哪個處理資訊更方便、更高效。
所以
與其費勁去重構代碼讓其看上去更簡潔、更合理
不如費勁學習grep、sed、awk、……這類全文搜索和批處理編輯的工具。
結構越復雜,越難修改,越難除錯。
有時(甚至大多數時候),看上去越合理、越簡潔的代碼,運行起來性能越差,出錯時查找原因越難,找到出錯原因后改正越費勁。
程式員要做的不是盡力避免錯誤,而是聚焦在快速發現并改正錯誤。真正以快速方式輕易解決錯誤,“快速的失敗”遠勝過“預防錯誤”。Fred George
前微軟C#編輯器的開發主管Jay Bazuzi列出的一些有助于找到正確方向的問題;他覺得前同事們應該用這些問題來問自己;實際上不管在哪里作業的開發者們都應該經常問問自己這些問題:
◆“要保證這個問題不會再出現,我該怎么做?”
◆“要想少出些Bug,我該怎么做?”
◆“要保證Bug容易被修復,我該怎么做?”
◆“要保持對變化的快速回應,我該怎么做?”
◆“要保證我的軟體的運行速度,我該怎么做?”
如果大多數團隊都能不時問一下自己,必定會從中得益,因為這些都是真正強而有力的問題。
uj5u.com熱心網友回復:
復用只是封裝的作用之一還有其他作用 比如擴展 比如修改
比如說 業務變更 之前只需要考慮 <label 開頭的標簽
現在需要多處理一個 <edit 開頭的標簽
你沒做封裝 那么你可能需要在一個幾百行的函式內部逐行確認才能找到什么地方添加這個功能
你做了封裝 定位到對應函式 可能就十幾行代碼的事情
雖然這個函式你只呼叫1次 但是你修改起來定位代碼非常方便
或者說這樣
parseVarBind()
parseSequence()
parseSequenceOf()
parseRequest()
parseCommunity()
parseVersion()
parseSNMPMessage()
SnmpXDaemon()
假設這里面parseVersion()是暫時不需要 我想屏蔽
不管是在這里直接注釋函式呼叫
還是到parseVersion()函式里面直接在第一行寫return
都能非常簡單的完成
如果你的代碼不是這個結構
而是一個幾百行的代碼
你需要從里面找出幾十行代碼 然后再做注釋
多注釋了一行 或者少注釋一行 就可能導致bug
等等等等
uj5u.com熱心網友回復:
我個人代碼一個函式差不多50行左右,最多不會超過80行,超過的話就會拆。我很擁護這個規則的,原因有兩個方面:1、可讀性
首先把功能單一的代碼拆成小函式有利于代碼閱讀,代碼寫出來是給人讀的嘛
SnmpXDaemon()
{
// 幾百行代碼
}
看起來肯定沒有
SnmpXDaemon()
{
parseVarBind()
parseSequence()
parseSequenceOf()
parseRequest()
parseCommunity()
parseVersion()
parseSNMPMessage();
}
這種邏輯清晰,易讀性強。對于代碼的第一個作者而言,一個函式幾百行代碼沒什么問題,但是對于后續維護人員來講,很痛苦,后來維護的人基本不會閱讀所有的代碼,都是看主干,看思路,看自己關心的那一段,所以函式、變數名字一定要取好,對于上面那個SnmpXDaemon()函式來說,如果采用第一種寫法,我不得不把幾百行代碼都看完,但是對于第二種,我可能就看那么幾行,因為一看到parseVarBind()這種我就知道這個函式在做什么,大體也知道它怎么實作的,如果我對這個函式很關心或者不知道它的實作,那我可能就會去看parseVarBind()它的實作,但是不會去看parseVersion()的實作,當然最重要的是邏輯思路,如果一個函式幾百行,如果你不把注釋寫好,我去理解你的思路是很花時間的,因為大家的思維方式可能不一樣,比如我這種吃香蕉從香蕉屁股那端剝的人在看到同事吃香蕉從香蕉蒂那端剝的時候我就震驚了,就算你代碼注釋非常好,但是你也很難保證后續的維護者跟你一樣把代碼注釋寫好,而且人家改動代碼的時候還要去維護你的注釋,但是第二種寫法思路就很清晰,都不用寫注釋,一看就知道先做什么再做什么最后做什么(前提是函式、變數名一定要取好!)。
再說樓主貼的那段代碼,如下:
SnmpXDaemon()
{
//一些代碼
parseSNMPMessage()
{
//一些代碼
parseVersion()
{
//一些代碼
parseCommunity()
{
//一些代碼
parseRequest()
{
//一些代碼
parseSequenceOf()
{
//一些代碼
parseSequence()
{
//一些代碼
parseVarBind()
{
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
//一些代碼
}
這個代碼就很難看,為什么不這樣呢:
SnmpXDaemon()
{
//一些代碼
parseSNMPMessage();
//一些代碼
}
然后parseSNMPMessage()函式里面如SnmpXDaemon()一樣,這樣的話兩個好處,首先如同我上面說的,其次就是減少了橫向的縮進,閱讀代碼的時候還要用滑鼠拉代碼編輯器的橫向滾動條我覺得也是個比較糟糕的體驗。
2、復用性
多用小函陣列合的第二個好處我覺得就是復用性,如果一個邏輯功能被多處使用,封裝成小函式以提高靈活性,相信是寫過代碼的人共識,樓主的疑惑在于拆出來的函式只用了一次,沒有被別的地方使用,所以感覺沒有拆的必要。但是以我的經驗,這個是很必要的,因為你寫的時候確實可能只有一處呼叫,但是你后來維護的人,可能因為業務的發展變遷,功能擴展什么的,你那個函式就有可能會被多處呼叫,試想一下某天你擴展個功能,突然發現你需要的功能別人已經實作了,你只需要傳個引數呼叫一下,是多么讓人激動的事兒!
把代碼拆分成小函式間接呼叫會影響性能,這個不可否認,但是按照80-20法則,影響性能的只是那20%的代碼而已,所以在寫代碼的時候,優先保證結構性,可讀性(當然很明顯的問題,比如100w個資料你非得用個冒泡排序那種就不說了哈),等寫完除錯的時候,發現確實有性能上的問題,再來有針對性的優化,而且你要因為多了幾層間接呼叫影響性能了來優化代碼,優化到這個份上,我還從未見過...
uj5u.com熱心網友回復:
如果一個業務太龐大太復雜了,寫完整個流程需要5萬行,你是不是準備一個函式整5萬行?uj5u.com熱心網友回復:
趙4老師是跳出問題回答問題了吧。新的問題可能是:對于最后的問題,不同的人和團隊,回答可能是不一樣的。
uj5u.com熱心網友回復:
現在沒有復用、修改不代表以后也不會。所以,作為一個好習慣,就是總為復用、修改留下余地。你這個有點類似:現在馬路上又沒車,你為啥不闖下紅燈。對錯、好壞自己判斷。
uj5u.com熱心網友回復:
是,這是一種可能性,不過按照我的經驗,
1、寫代碼的人自己修改,那封裝和不封裝,差別很小,這只是個人習慣差別
2、對于修改他人的代碼,對于我來說,我不敢不去看封裝方法里邊的代碼,那相當于蒙眼狂奔。我的習慣都是,看明白了才能改。
3、多數情況下,閱讀代碼的時間大大多于修改代碼,所以閱讀代碼的方便性應該提升。
題外話,對于我舉的這個案例,它的七八個封裝函式,都沒有函式注釋,不除錯很難明確這些函式的功能。而且這些函式分別的反復操作一些全域變數,看起來封裝度也差。如果真的對代碼clean理解深入的人,不應該搞出這么分裂的現象。所以總體上,我覺得至少這個案例,七八個函式都是唯一呼叫,應該是封裝過度了。
uj5u.com熱心網友回復:
我的看法,是剛好相反的。這是我做產品時總結的:簡潔的設計,不要過度設計產品,讓產品生長,而不是設計。我個人認為代碼也應該如此,代碼的封裝不要過度設計,以為未來會如何如何復用,其實往往最后就沒有復用。這個案例就是如此,產品定型了,也沒有復用。
我認為好代碼也應該是,根據需求迭代出來的,那才是最簡潔的代碼。不要怕未來改代碼,未來在迭代時有復用需求了再復用。
uj5u.com熱心網友回復:
我這么說的意思,也不是說我自己想闖紅燈,其實我對代碼整潔也是有追求的,我只是覺得有些時候不應該過度設計。不是說要在路口闖紅燈,而是比如50年前,大家都在用手推車的時候,十字路口不需要設定紅燈。
uj5u.com熱心網友回復:
既然如此你不應該有疑問你的體驗就是都寫在一起方便你就都寫在一起
相信自己
uj5u.com熱心網友回復:
反正我的體驗是封裝了更方便 大家敬而遠之uj5u.com熱心網友回復:
覺得不用死守著超過50行就要拆,有的如果真的沒必要拆,就留著吧但是如果一個函式動不動就幾百行,這個肯定是不好維護的
uj5u.com熱心網友回復:
你認為已經過度了,大師們還嫌不夠。你認為以后可以重構/重用,大師們認為以后根本不會給你這個時間、機會。所以,這樣多討論是沒有結果的。
自己理解,自己成長,走自己的路。
uj5u.com熱心網友回復:
除了代碼重用,擴展,還有一個作用,就是方便單體測驗如果只改動了某個函式,單體測驗之要測驗該函式就可以了,寫在一起就要整體測驗(相當于結合測驗了)
當然,至于怎么劃分模塊合理,那就具體問題具體分析了。
uj5u.com熱心網友回復:
是的,路還是要自己走出來的。
uj5u.com熱心網友回復:
單元測驗,這點,不可否認。幾百行的代碼,確實對單元測驗來說是很糟糕的。如果有一票否決,那么這個理由比較充分。不過同時,做好單元測驗的程式員少之又少,及其優秀的專案,可能才會考慮到單元測驗這么完善。
uj5u.com熱心網友回復:
總結起來說,我也不是說就是不認可大師們的觀點,大師們這么做應該有他們的道理,只不過我從自己的經歷來說,不能體會到這樣做有那么多的好處,反而缺點挺多。而我也想想明白了再干,不想盲從。我能想到的幾點,需要短函式的原因是:
1、單元測驗:測驗單元越短越好。
2、低級語言:比如c/c++這種稍低級的語言,涉及到比較多的運算,代碼相對的不那么容易看懂,封裝有利于功能的模塊化和理解。而我使用c#等高級語言多了,這些高級語言表達力強,即使寫上100行,其閱讀性也還是非常強。所以相應的對短函式的要求降低了。
以后會嘗試寫短一些的函式和方法,更深入的體會一下短函式的優缺點。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/194742.html
標籤:模式及實現
上一篇:求斐波那契數列N項,求助!
下一篇:求大佬看看,
