關于<<提高代碼質量系列>>
這是我新開的一個系列,旨在記錄我對整個編碼規范,代碼風格,語法習慣,架構設計的一些思考,感悟和總結.
前言
不知道大家會不會覺得我的標題很噱頭,不是一般應該提倡寫注釋的么?首先我得解釋下,我這句話有兩個意思!
1,絕非提倡不寫注釋,而是不要寫不必要的注釋.
2,命名規范的作用大于注釋
好吧,這么一說,其實還是有點噱頭的感覺的,因為我這篇文其實重心更放在強調命名規范和設計規范上面,良好的規范,讓你的代碼有自釋性,省去了注釋的步驟.
還要強調下的是:這個觀點絕非我自己主觀臆斷,憑空瞎想出來的. 而是實實在在由項目開發里面總結出來的.
為什么我有這個想法呢?請繼續看我的蛋疼經歷.
正文
先上段奇葩代碼
/// <summary> /// 根據產品ID獲取產品列表 /// </summary> /// <param name="columnID">關鍵字</param> public DataTable GetColumnInfoByColumnID(int columnID) { return DALColumn.GetColumnInfoByColumnID(columnID); } /// <summary> /// 根據產品名稱獲取產品列表 /// </summary> /// <param name="columnID">關鍵字</param> public DataTable GetColumnInfoByColumnID(string columnName) { return DALColumn.GetColumnInfoByColumnID(columnName); } |
這是我現在維護的一個老項目了,經手的人比較多,代碼寫的比較垃圾,我們先不吐槽這種純粹是脫褲子放屁的所謂三層和明明返回的是dataTable又扯什么info,就說這兩個函數,tm功能應該是不一樣的吧,為啥名字一模一樣?這是在鬧哪樣?
其實,造成這種情況的原因,我們都知道,就是某類程序員的ctrl c+v大法,這兩個函數底層邏輯比較相似,懶得重構,直接copy了改改多快!copy就copy吧,好歹名字改一下啊!也許這位前輩會說,我不是寫了注釋了嗎,看到注釋,不就知道這個函數是干嘛的了?但問題是,其他調用的人,首先看到的,肯定是函數名啊,GetColumnInfoByColumnID,多直觀 通過id來查找唄.雖然這里有兩個比較違和的地方,一是參數默認名字是columnName,二是參數類型不是int而是string,但反正這項目的代碼不規范,如果調用的人也夠粗心,那么,一個隱晦的bug,就這么產生了.
如果改下名字,叫 GetTableByColumnName,這種錯誤發生的概率無疑會減少很多了,
Ps:其實現在ide功能這么強大,只要你確定沒有反射調用這個函數的地方,完全可以使用全局重命名的方法,一步到位.
看到這里讀者朋友們可能會說,這是命名不規范嘛,和寫不寫注釋有什么關系呢?
我們可以假想一種這樣的情況,同樣是拷貝代碼,拷貝者改了函數名,這個名字語義清晰,表意清楚, 但他卻忘記改注釋了,結果函數是新的函數簽名,函數的注釋卻是其他一段莫名其妙的注釋.
或者再假想一種情況.
原來的一個函數,名字和注釋是對應的,隨便舉個例子,叫GetTypeByColumnId吧,注釋為"通過產品id取得產品類型"
一切ok,是吧! 但是現在邏輯變了,比如說Type和產品無關了,需要通過生產批次來確定,于是一個代碼維護者,將函數重寫了一下功能ok,滿足新需求!!同時他比上個人好一點,他記得改函數名,然后他改為了GetTypeBySerialId,這名字也很ok,一切也都看起來很好.但偏偏他漏掉了注釋,但代碼仍然運行的很ok,畢竟注釋可不受.net的元數據的支持,ide也不可能知道你這里犯了這么一個錯誤是吧!
然后,接下來的場景大家很容易就可以聯想到,
如果是細心的調用者:
尼瑪!函數注釋讓我傳"產品id",但函數名和參數默認名字又是"SerialId"(流水號),這尼瑪鬧哪樣?
如果是粗心的調用者呢?兩種情況唄:
1.看了函數名字和默認參數名字 ,沒注意到注釋,ok,算這個粗心的小伙伴幸運,
2.看了注釋,然后這小伙伴不認識SerialId這個單詞的意思. 然后,一個悲傷的故事就這么發生了!!
其實我以上舉例的一些函數,都是功能比較具體,業務比較單純,也容易用幾個單詞描述.對于這些函數,我個人的意見是,完全不需要去寫注釋,
有以下幾個原因:
1.浪費精力去寫,
2.調用的人需要把一段話讀兩遍(函數名和注釋)
3.寫了還需要人去維護,(改了代碼,得同步去改注釋)
4.如果強類型的參數傳遞不匹配,ide或者resharper插件會馬上指出你的錯誤,但如果注釋和代碼不匹配,則除了通過人力CodeReview,沒有其他任何辦法去找出這種錯誤.
其中尤其是4,完全就是埋在項目中的地雷,除非你踩到了,不然很難排查.
當然,如果是反之,邏輯復雜,甚至有調用的前置約束,那肯定該寫注釋還是得寫了.
同時,這里還提一個觀點,注釋比代碼更有價值,因為代碼畢竟大部分還是在講怎么做(how do),而注釋是講做什么(do what)?抽象程度更高,對于比較復雜的代碼,如果有注釋,調用者一般都會優先去閱讀注釋,而不是去閱讀代碼,所以:輕易不寫注釋,但如果你寫了,請一定要對你的注釋負責,它比代碼更需要你的細心呵護!!
好吧,我會說這篇文最大的作用,其實是可以讓我吐槽發泄一下么?
我感覺自己現在就是在這種地雷坑里,天天過著提醒吊膽的日子.
以上例子皆非我刻意編造,來源于工作中的真實經歷,只是稍作修飾,隱去了和業務有關的信息.
根據回復的補充:
相信很多博友有這樣的經歷,這個函數好復雜呀!我必須寫注釋啊,不然一段時間之后,我自己都看不明白了,
有些時候是確實很復雜,但也有時候是設計的問題,這時候,寫注釋其實是一種逃避了.逃避你需要繼續深入思考這個函數的業務和功能的設計是否合理.
其實我這標題還有第三個意思:
在盡可能少寫注釋的前提下,如果一個函數的注釋仍然超過了2處,那么我認為這個函數的設計是有問題的.
這個函數是否太大,是否是反模式里面提到的萬應靈或屠龍術?
所以有時候說的 "因為業務復雜,很難用幾個單詞去描述,所以很難命名",就是如此.
你的業務抽象粒度是否太大?導致一個業務模塊承擔了過多的業務.
你是否把幾個流程放在了一個節點上?導致引入了額外的邏輯判斷甚至是多余的邏輯分支.
如果是這樣,你想用幾個單詞來描述這么復雜的邏輯,當然是不可能了.
這點我會在后續的重構系列里面談談我自己的理解.
補充下自己的理解.
我始終認為好的設計,大部分情況下,函數名就足以解釋它的功能,如果你遇到了兩三個單詞不能解釋函數功能的情況----說明你該分解函數了!
比如一個大函數,OutPutMetaData,輸入是源數據路徑,使用的模板 返回解析之后的元數據
流程大概是 采集數據->分析數據->匹配模板->生成MetaData
代碼是大約1-2k行,如果寫在一個函數里面,當然也似可以的,但你想用注釋解釋清楚,必須在每個流程的關鍵節點寫注釋,遇到數據有前后關聯關系的,還得思維反復跳來跳去.
但如果寫成下面這種模式的代碼,基本就像是閱讀英文說明(注釋),一樣閱讀代碼了
public MetaData OutPutMetaData(string sourcePath, MetaTemplate template) { var metaDataFactory = new MetaDataFactory(); if (!metaDataFactory.CheckInput(sourcePath)) { throw new ErrorSourceException(); } if (!metaDataFactory.TransformSourceData(template)) { throw new ErrorFormatException(); } return metaDataFactory.CreateMetaData(); } |
這樣寫雖然多了很多的類和方法,還要額外定義一些中間數據的實體類型和自定義異常,但是經過合理的封裝和命名之后,整個結構非常清晰,定位錯誤和修改流程也方便.
代碼閱讀速度基本和描述語句(注釋)的閱讀速度相當, 這就是代碼即注釋.
最后總結:
其實我認為最關鍵是要形成自己的編碼規范,這個"規范"不僅僅指的是狹義的命名規則和代碼格式,縮進,文件組織結構等.更關鍵的是,要形成一套有邏輯性,能自洽,有良性導向的一套思維模式,并時刻堅持遵守它,思考它,改進它.
這套思維模式你可以自由的去擴展,只要不偏離它的中心思想.比如我給自己擴展的一些要求:
1.函數一律使用動賓結構,如InitFactory,而不用FactoryInit,其實這兩者沒什么優劣,僅僅只是讓自己習慣,以后思考和閱讀自己代碼的時候,能更快的帶入過去的自己的思維,更快的理解自己的代碼,同時找api也能節約一點時間.
2.html標簽樣式id小寫開頭,class大寫開頭,同理,其實也沒啥原因,就是個習慣.
3.描述一個事物的時候要區分是what it is(名詞,形容詞)還是what it can do(動詞,動名詞,動賓短語)比如doClose, 是某個事物的動作,closing,和 closed則代表它的狀態
再就是結構設計上的一些感覺了,這個比較抽象,不好用文字很準確的描述,大致意思就是我會從一些緯度對功能進行切分,access business viewModel show interaction 等,不一定都能分的非常清楚,也不強求一個區分度很高的邊際,但至少要有個模糊的定位.
如果能長期堅持下來,以后閱讀自己的代碼是非常容易的,即使是沒有(少量)注釋.
Ps:錘煉自己的這套思維模式的方法也很簡單,也就三點
多看優秀代碼,自己動手多寫,多思考總結.
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters