dotNET Core:編碼規(guī)范

在項目開發(fā)過程中,由于時間緊、任務(wù)重,很容易導(dǎo)致面向功能編程。實現(xiàn)相同的功能,代碼可以寫的很優(yōu)雅,也可以寫的很晦澀和復(fù)雜。現(xiàn)在的工作,都需要進(jìn)行團(tuán)隊協(xié)作,代碼就需要有一定的規(guī)范進(jìn)行指引,因為我們需要寫出讓人可以輕易讀懂的代碼,而不僅僅是機(jī)器。
規(guī)范沒有絕對的標(biāo)準(zhǔn),遵循大部分人都認(rèn)可的一種方式就可以了,保持統(tǒng)一。比如在 dotNET Core 中,我們可以參考下 dotNET Core 的源碼,最終制定一個適合團(tuán)隊的規(guī)范即可。
下面是我理解的正確的一些規(guī)范:
基本準(zhǔn)則
1、命名的規(guī)范分為兩種:Pascal(大駝峰)和駝峰(小駝峰),示例如下:
? Pascal:UserName
? 駝峰:userName
2、命名要有意義,需要看到名稱知其含義。少用拼音和匈牙利命名法。
| 示例 | |
|---|---|
| Int price=20; | √ |
| UserInfo userInfo=GetUserInfo(userId); | √ |
| Int p=20; | × |
| Int intPrice=20; | × |
3、對于類的成員變量,用this關(guān)鍵字,增強(qiáng)代碼可讀性。
4、對于基類的成員變量,用base關(guān)鍵字,增強(qiáng)代碼可讀性。
名稱規(guī)范
好的名稱可以讓我們減少很多不必要的注釋,可以讓代碼閱讀者很容易就理解代碼的意思。但命名不是一件容易的事情,在命名的時候,通常伴隨著我們對代碼邏輯的思考。比如:如果你不能給一個函數(shù)很準(zhǔn)確地命名,那可能這個函數(shù)的職責(zé)不單一,做的事情太多,才導(dǎo)致一個名稱很難概括,意味著代碼可能需要重構(gòu)。好的命名是我們寫好代碼的基礎(chǔ)。
命名空間
命名空間采用Pascal命名法:
namespace?Fw.Application{}
namespace?Fw.SmartFlow.Acitivity{}
實際工作中,我們會將很多邏輯上屬于同一類的文件,在物理上分成不同的目錄,這時建議修改命名空間為相同的命名空間。
類
類采用Pascal命名法:
public?class?UserService{}
類是對屬性和方法的封裝,類有很多的種類:
跟數(shù)據(jù)庫表對應(yīng)的實體類
處理業(yè)務(wù)邏輯的業(yè)務(wù)類
提供擴(kuò)展方法的擴(kuò)展類
接口層的數(shù)據(jù)傳輸類
不同的種類可以約定俗成地進(jìn)行一些名稱的約束,比如擴(kuò)展類用 Extension 結(jié)尾、接口層的使用 Request、Response 結(jié)尾,等等,這樣在閱讀代碼時就知道什么類的職責(zé)是什么。
接口
接口采用大寫I+Pascal命名法:
public?interface?IUserService{}
方法
方法采用Passcal命名法:
public?string?GetUserName(){}
方法的命名需要比較具體,越抽象的名稱越難以理解。例如,InitData() 就是一個不太好的名稱,改成 InitConfiginfo() 會更好些。另外,方法的命名在同一類型的語義下要保持一致,在一些項目中看到查找類的方法,有 GetXXX、QueryXXX、FindXXX 等等。五花八門的方式會提升閱讀的成本。
變量
變量分為:類變量、靜態(tài)類變量、只讀變量、靜態(tài)只讀變量、方法變量。
類變量:
private?string?_userName;
類變量只能使用 private 修飾符,如果需要暴漏出去,或是給子類使用,使用屬性來進(jìn)行封裝。
靜態(tài)類變量、只讀變量、靜態(tài)只讀變量:
private?static?readonly?ResourceManager?_resourceManager;
public?static?readonly?IRouter?Instance?=?new?MockRouter();
? 修飾符為 private: _ + 駝峰命名法;
? 修飾符為 public: Pascal 命名法;
? 修飾符為 protected:Pascal 命名法;
方法變量:
bool?isCheck;
常量:
常量采用Pascal命名法:
public?const?string?AuthorizationFilter?=?"Authorization?Filter";
屬性:
屬性采用 Pascal 命名法:
public?BasicApiContext?DbContext?{?get;?}
參數(shù)
參數(shù)采用駝峰命名法:
public?List?GetBizAppList(string?userId,DeviceType?deviceType)
注釋規(guī)范
注釋是一把雙刃劍,當(dāng)代碼中存在大量的注釋的時候,我們第一反應(yīng)會先看注釋,并會默認(rèn)注釋中寫的內(nèi)容是對的,真實情況是注釋往往會給我們錯誤的指導(dǎo)。有兩個原因:
當(dāng)代碼邏輯發(fā)生變化時,只修改了代碼,注釋沒有調(diào)整;
不同的人員都對注釋進(jìn)行編輯,慢慢注釋會變得和代碼不匹配。
Martin Fowler 在他的經(jīng)典書籍 《重構(gòu)》 中也提到過多的注釋是一種壞味道的體現(xiàn),他認(rèn)為,當(dāng)你覺得需要寫注釋的時候,應(yīng)該先想想是不是可以進(jìn)行重構(gòu)。那是不是程序中就不應(yīng)該出現(xiàn)注釋呢?當(dāng)然不是,我認(rèn)為下幾種情況是需要寫注釋的:
時間緊急,臨時寫的一些 ”爛代碼“,需要寫注釋,說明原因,一般需要加上 TODO ;
復(fù)雜算法類的方法,可以寫注釋說明邏輯;
寫的是偏底層的類庫,對外暴露的方法需要寫注釋,調(diào)用方能方便在智能提示中顯示;
如果你的能力寫不好代碼,那還是先把注釋寫上吧。
異常規(guī)范
異常的目的是用來報告錯誤,這也是他的唯一目的,所以避免在返回值中來返回錯誤信息,所有的地方都應(yīng)該使用拋異常的方式來報告錯誤;
使用拋異常的方式可以防止錯誤的操作繼續(xù)執(zhí)行;
要能夠預(yù)估到會出現(xiàn)什么異常,知道是什么類型的異常,才 Try 住做相關(guān)的處理;
最終用戶友好和對開發(fā)者友好;
暴漏問題比隱藏問題要好,隱藏問題只會導(dǎo)致更嚴(yán)重的問題。
詳細(xì)的異常處理可以參考之前的文章:
dotNET:怎樣處理程序中的異常(理論篇)?
dotNET:怎樣處理程序中的異常(實戰(zhàn)篇)?
空行規(guī)范
空行規(guī)范是一個很簡單的規(guī)范,就是在每個方法中,代碼應(yīng)該按照不同邏輯的邏輯塊進(jìn)行分割顯示,雖然簡單,但如果不注意,還是會對代碼的閱讀帶來很大的障礙。下面看看 dotNET Core 的源碼 CreateDefaultBuilder 方法:
public?static?IHostBuilder?CreateDefaultBuilder(string[]?args)
{
????var?builder?=?new?HostBuilder();
????builder.UseContentRoot(Directory.GetCurrentDirectory());
????builder.ConfigureHostConfiguration(config?=>
????{
????????config.AddEnvironmentVariables(prefix:?"DOTNET_");
????????if?(args?!=?null)
????????{
????????????config.AddCommandLine(args);
????????}
????});
????builder.ConfigureAppConfiguration((hostingContext,?config)?=>
????{
????????var?env?=?hostingContext.HostingEnvironment;
????????config.AddJsonFile("appsettings.json",?optional:?true,?reloadOnChange:?true)
??????????????.AddJsonFile($"appsettings.{env.EnvironmentName}.json",?optional:?true,?reloadOnChange:?true);
????????if?(env.IsDevelopment()?&&?!string.IsNullOrEmpty(env.ApplicationName))
????????{
????????????var?appAssembly?=?Assembly.Load(new?AssemblyName(env.ApplicationName));
????????????if?(appAssembly?!=?null)
????????????{
????????????????config.AddUserSecrets(appAssembly,?optional:?true);
????????????}
????????}
????????config.AddEnvironmentVariables();
????????if?(args?!=?null)
????????{
????????????config.AddCommandLine(args);
????????}
????})
????.UseDefaultServiceProvider((context,?options)?=>
????{
????????var?isDevelopment?=?context.HostingEnvironment.IsDevelopment();
????????options.ValidateScopes?=?isDevelopment;
????????options.ValidateOnBuild?=?isDevelopment;
????});
????return?builder;
}
想想看,上面代碼中如果去掉空行會讀起來是什么樣的感受?
日志規(guī)范
在一個完整的系統(tǒng)中,日志非常的重要。在 dotNET Core 中自帶了日志功能,當(dāng)然我們也可以使用第三方的 NLog、Serillog 等。
這些日志框架都提供日志級別功能,比如:INFO、DEBUG、WARN 和 ERROR 等,這些級別對程序出錯時的排查非常有用,所以在記錄日志時一定不要都使用 INFO 或者都使用 ERROR 了。
除了級別,日志的類型有這么幾類:
操作日志
業(yè)務(wù)日志
錯誤日志
操作日志
系統(tǒng)中所有的操作的都記錄下來,包括登錄、數(shù)據(jù)的增刪改等,主要用來做審計,數(shù)據(jù)異常操作時的追責(zé)等。
隨著時間的推移,日志的數(shù)據(jù)量會越來越大,所以需要考慮存儲的方式,比如階段性地將歷史日志進(jìn)行存檔。
業(yè)務(wù)日志
用戶在界面中輸入數(shù)據(jù),點擊一個按鈕,程序中會進(jìn)行一系列的處理最終返回結(jié)果給用戶,在這個過程中對一些關(guān)鍵的業(yè)務(wù)信息進(jìn)行記錄,可以在系統(tǒng)出現(xiàn)問題時方便排查和追蹤。
錯誤日志
錯誤日志的主要目的就是排錯,所以記錄的信息一定要是對排錯有幫助的信息,盡可能地記錄詳細(xì),比如整個堆棧信息、調(diào)用鏈等。比如,你去進(jìn)行錯誤排查時,發(fā)現(xiàn)記錄的信息是”未將對象引用到對象的實例“,你依然不知道錯誤的原因是什么。
總結(jié)
談及代碼的時候,都會去聊架構(gòu)、模式,這些固然重要,但編碼習(xí)慣和規(guī)范也不可小視。一份產(chǎn)品的代碼怎樣才能變得越來越好,這需要團(tuán)隊每個人成員共同的努力,一個人掉鏈子,很容易就形成破窗效應(yīng),導(dǎo)致壞味道越來越多。
寫好的代碼,是每個有追求的技術(shù)人的使命和職責(zé)。
希望本文對您有所幫助。
