一個軟件最后都會落實到代碼。而代碼,其背后的架構設計或設計思想或模式固然重要,但我覺得更重要的東西則是良好的命名。混亂或錯誤的命名不僅讓我們對代碼難以理解,更糟糕的是,會誤導我們的思維,導致對代碼的理解完全錯誤。相反,良好的命名,則可以讓我們的代碼非常容易讀懂,也能向讀者正確表達事物以及邏輯的本質,從而使得代碼的可維護性就大大增強,讀命名好的文章是非常流暢的,會有一種享受的感覺。
另外一點也許大家還沒感受到,那就是良好的命名,以及良好的命名習慣,由於我們總是對每個概念的名稱要求非常苛刻,我們會思考這個名稱所表達的概念是否正確,該名稱是否正確表達了事物的本質或正確反映了某個行為的邏輯。所以,這種對命名的良好思考習慣,可以反過來幫助我們糾正之前的一些錯誤設計和代碼實現;比如,你之前有一個地方可能命名不太准確,然后你發現后面有另一個地方需要用這個名字,且更合理。所以你會發現這個名字對前面的地方就不適合了,從而你會去思考前面的地方可能需要用其他的名字,或者你會發現前面的地方的設計根本就是有問題的。這種就是名字可以促使你思考你的設計是否正確的例子。
代碼命名混亂或錯誤的主要原因:
- 沒理解事物的本質;
- 理解了事物的本質,但不知道命名的重要性或者根本不屑於做好命名;
- 理解了事物的本質,也知道命名的重要性,但沒能力命名好事物;
養成良好的命名習慣的一些想法:
- 對自己的嚴格自律,自己寫代碼時要有一種希望把每個名稱都命名好的強烈意識和嚴格的自律意識;
- 要努力分析和思考當前被你命名的事物或邏輯的本質;這點非常關鍵,思考不深入,就會導致最后對這個事物的命名錯誤,因為你還沒想清楚被你命名的事物是個什么東西;
- 在有自律意識和一定的分析能力基礎之上,注意命名的方法技巧;要知道何時用動詞,何時用名詞;以及形容詞放哪里,動詞放哪里,名詞放哪里;也就是小學時的主謂賓要會用;
- 你的任何一個屬性的名字都要和其實際所代表的含義一致;你的任何一個方法所做的事情都要和該方法的名字的含義一致;
- 從代碼的命名可以看出寫代碼的人編程時思路是否清晰,如果你對一個名字的命名不准確,很可能體現出你還沒有理解這個名字背后的東西;
- 要讓你的程序的每個相似的地方的命名風格總是一致的。不要一會兒大寫,一會兒小寫;一會兒全稱一會兒簡寫;一會兒Pascal命名法,一會兒camel命名法或匈牙利命名法;
- 不要出現重復的命名;因為通常名稱都有嵌套關系,比如類在命名空間里,方法在類里,所有如果一個概念在命名空間里表達了,那就不必再類上再表達一次;
- 對於屬性或類名,應該總是名詞在最后面,名詞決定了這個屬性代表什么,前面的部分都是用於修飾這個名詞;比如,假如現在你有一個服務,然后又是一個關於訂單的服務,那就可以命名為OrderService,這樣命名就是告訴我們這是一個服務,然后是一個訂單服務;再比如CancelOrderCommand,看到這個我們就知道這是一個Command,即命令,然后是什么命令呢?就是一個取消訂單的命令,CancelOrder表示取消訂單;
- 對於方法,應該總是動詞開頭,名詞結尾;比如Order.AddItem(orderItem);這個,表示訂單類有一個添加訂單項的方法,Add是動詞,表示添加,Item是名詞表示訂單項;
- 在C#中,我們一般用camel以及Pascal命名法,而不是匈牙利命名法。我覺得主要是兩個原因:1)VS強大的智能感知提示的存在,我們沒有必要突出變量的類型了,但這個我覺得只是一個次要原因;2)真正的原因,我上面有提到,一個變量,名詞是放在最后的,這個名詞決定了這個變量代表什么。比如有個變量叫totalCount,我們一看就知道這是一個count,然后count一定是一個int或者long,所以就不需要在強調它的類型了。再比如,remotingRequest, httpRequest,這種,我們也一看就知道他們是請求,一個是remoting的請求,一個是http的請求。remoting,http是用來修飾request的。request決定了這個變量是什么(同時就意味着我們知道了他的類型了),然后remoting,http這種是進一步說明該request的業務含義或當前上下文。就像disabledButton,我們一看就知道這是一個button,然后是一個什么Button呢,就是一個已禁用的button。所以,好的名稱,本身就會讓我們很容易知道該名稱是什么東西,它的類型是什么,具有什么業務含義,所以沒有必要再加類型縮寫作為前綴;
- 多學習英文,多看國外優秀開源項目中的命名技巧,會對我們命名有很大幫助;
通過一些不太好的代碼命名來分析一些簡單的命名問題
以上代碼中,有很多問題,我們來一一分析:
- 方法的參數,第一個字母,一會兒大寫的P,一會兒小寫的p,不一致;
- 第二個參數后面出現多余的空格,不應該;
- _paramsTable這個參數為什么要出現下划線,而其他參數沒有下划線,不一致;
- publishRequest屬於camel命名法,而iSignCounter, sStageIsOK這種屬於另一種命名法,這種命名c++中用的多,不一致;
- foreach循環中,參數名叫instParam,但是后面的集合叫arrParams4SignActions,更對稱一點的,應該叫arrInstParam;
- 方法的最后兩行,出現多余的空格,導致代碼格式排版混亂;
從上面的代碼我們可以知道,僅僅是通過這些細節,就能發現很多問題。我們寫代碼時,只要多細心點,多注意點排版是否美觀一致、命名是否統一,那代碼寫出來就會漂亮很多了。下面我們再看看其他的代碼:
- 上面的代碼中,兩個參數的命名也不一致,projectid中,i是小寫,但是publishId參數,i卻是大寫,應該都統一為大寫;
- ViewData中的key,一會兒是全部大寫的UPDATE,一會兒是另一種命名,不一致;
- 上面的兩個紅框標出來的if,雖然都是只有一行代碼,但是一個有括號,一個沒有括號,不一致;且第二個if里出現了多余的空行,格式混亂;
- 上面的代碼中,函數中,一會兒用IList,是一個接口,一會兒用Dictionary,非接口,不一致;應該都用接口,或者都不用接口;
- listOriginal和receiverList命名不一致,要么全部list開頭,要么全部List結尾;
- foreach循環中,變量的類型叫TDMSOriginalRequirement,但是變量名卻叫originalItem,而集合名稱又叫listOriginal,應該三者統一;比如foreach (Assembly assembly in assemblies)
- +“...”這個地方沒有用空格,加號兩邊應該要空格,這屬於格式混亂,不嚴謹;
- createUser這個變量取的很不理想,create是動詞,createUser合起來就是創建用戶的意思,而他這里要表達的意思是創建人的意思,所以應該叫createdUser或者creator;
- 為何originalItemFormat和originalItem的意思可以等價,不合理,如果等價,一開始就要命名為originalItemFormat;而且format是一個東西,動詞放在最后,算個啥?
- 上面這個類的幾個私有字段中,有些帶命名空間,有些不帶,要么都不帶,要么都帶;一般命名空間都是在上面聲明,后續不需要出現;
- ILog logger;這一句有兩個問題:1)logger為何沒有下划線,不統一;2)為何類名叫ILog,而變量名叫logger,要統一,要么類名叫ILogger,要么變量名叫_log;
上面這兩個私有方法,一個是大寫開頭,一個是小寫開頭,不一致,混亂;應該要一致;
總結
通過上面的一些例子,我們知道,在我們不經意間,多寫了一個空格或者一個空行,或者一個字母的大小寫不一致了,都會導致命名的不一致;如果自己沒有養成這種平時注重代碼命名各種一致性的習慣,那寫出來的代碼很可能就是像上面那樣。我覺得是非常糟糕的。上面我舉的例子都只是簡單的命名方面的,更深層次的命名問題,比如如何做到名稱和其背后的實現內容一致,這個是需要我們平時不斷修煉的。不是短時間內就可以做到那個程度。
我覺得,要做好命名,歸根結底:
- 先要意識到命名的重要性;
- 要端正態度,要認真的寫代碼;
- 要努力推敲每個名稱和其實際做的事情是否一致,也就是命名的准確性;
- 要時刻注意命名的各種一致性;
養成良好的命名習慣不是為了別人,不是為了公司,而是為了提高自己的編程修養,提高自己認識事物的能力。