在項目開發過程中,由於時間緊、任務重,很容易導致面向功能編程。實現相同的功能,代碼可以寫的很優雅,也可以寫的很晦澀和複雜。現在的工作,都需要進行團隊協作,代碼就需要有一定的規範進行指引,因爲我們需要寫出讓人可以輕易讀懂的代碼,而不僅僅是機器。
規範沒有絕對的標準,遵循大部分人都認可的一種方式就可以了,保持統一。比如在 dotNET Core 中,我們可以參考下 dotNET Core 的源碼,最終制定一個適合團隊的規範即可。
下面是我理解的正確的一些規範:
基本準則
1、命名的規範分爲兩種:Pascal(大駝峯)和駝峯(小駝峯),示例如下:
• Pascal:UserName
• 駝峯:userName
2、命名要有意義,需要看到名稱知其含義。少用拼音和匈牙利命名法。
示例 | |
---|---|
Int price=20; | √ |
UserInfo userInfo=GetUserInfo(userId); | √ |
Int p=20; | × |
Int intPrice=20; | × |
3、對於類的成員變量,用this關鍵字,增強代碼可讀性。
4、對於基類的成員變量,用base關鍵字,增強代碼可讀性。
名稱規範
好的名稱可以讓我們減少很多不必要的註釋,可以讓代碼閱讀者很容易就理解代碼的意思。但命名不是一件容易的事情,在命名的時候,通常伴隨着我們對代碼邏輯的思考。比如:如果你不能給一個函數很準確地命名,那可能這個函數的職責不單一,做的事情太多,才導致一個名稱很難概括,意味着代碼可能需要重構。好的命名是我們寫好代碼的基礎。
命名空間
命名空間採用Pascal命名法:
namespace Fw.Application{}
namespace Fw.SmartFlow.Acitivity{}
實際工作中,我們會將很多邏輯上屬於同一類的文件,在物理上分成不同的目錄,這時建議修改命名空間爲相同的命名空間。
類
類採用Pascal命名法:
public class UserService{}
類是對屬性和方法的封裝,類有很多的種類:
- 跟數據庫表對應的實體類
- 處理業務邏輯的業務類
- 提供擴展方法的擴展類
- 接口層的數據傳輸類
不同的種類可以約定俗成地進行一些名稱的約束,比如擴展類用 Extension 結尾、接口層的使用 Request、Response 結尾,等等,這樣在閱讀代碼是就知道什麼類型的職責是什麼。
接口
接口採用大寫I+Pascal命名法:
public interface IUserService{}
方法
方法採用Passcal命名法:
public string GetUserName(){}
方法的命名需要比較具體,越抽象的名稱越難以理解。例如,InitData() 就是一個不太好的名稱,改成 InitConfiginfo() 會更好些。另外,方法的命名在同一類型的語義下要保持一致,在一些項目中看到查找類的方法,有 GetXXX、QueryXXX、FindXXX 等等。五花八門的方式會提升閱讀的成本。
變量
變量分爲:類變量、靜態類變量、只讀變量、靜態只讀變量、方法變量。
類變量:
private string _userName;
類變量只能使用 private 修飾符,如果需要暴漏出去,或是給子類使用,使用屬性來進行封裝。
靜態類變量、只讀變量、靜態只讀變量:
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; }
參數
參數採用駝峯命名法:
public List<BizApp> GetBizAppList(string userId,DeviceType deviceType)
註釋規範
註釋是一把雙刃劍,當代碼中存在大量的註釋的時候,我們第一反應會先看註釋,並會默認註釋中寫的內容是對的,真實情況是註釋往往會給我們錯誤的指導。有兩個原因:
- 當代碼邏輯放生變化時,只修改了代碼,註釋沒有調整;
- 不同的人員都對註釋進行編輯,慢慢註釋會變得和代碼不匹配。
Martin Fowler 在他的經典書籍 《重構》 中也提到過多的註釋是一種壞味道的體現,他認爲,當你覺得需要寫註釋的時候,應該先想想是不是可以進行重構。那是不是程序中就不應該出現註釋呢?當然不是,我認爲下幾種情況是需要寫註釋的:
- 時間緊急,臨時寫的一些 ”爛代碼“,需要寫註釋,說明原因,一般需要加上 TODO ;
- 複雜算法類的方法,可以寫註釋說明邏輯;
- 寫的是偏底層的類庫,對外暴露的方法需要寫註釋,調用方能方便在智能提示中顯示;
- 如果你的能力寫不好代碼,那還是先把註釋寫上吧。
異常規範
- 異常的目的是用來報告錯誤,這也是他的唯一目的,所以避免在返回值中來返回錯誤信息,所有的地方都應該使用拋異常的方式來報告錯誤;
- 使用拋異常的方式可以防止錯誤的操作繼續執行;
- 要能夠預估到會出現什麼異常,知道是什麼類型的異常,才 Try 住做相關的處理;
- 最終用戶友好和對開發者友好;
- 暴漏問題比隱藏問題要好,隱藏問題只會導致更嚴重的問題。
詳細的異常處理可以參考之前的文章:
- dotNET:怎樣處理程序中的異常(理論篇)?
- dotNET:怎樣處理程序中的異常(實戰篇)?
空行規範
空行規範是一個很簡單的規範,就是在每個方法中,代碼應該按照不同邏輯的邏輯塊進行分割顯示,雖然簡單,但如果不注意,還是會對代碼的閱讀帶來很大的障礙。下面看看 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;
}
想想看,上面代碼中如果去掉空行會讀起來是什麼樣的感受?
日誌規範
在一個完整的系統中,日誌非常的重要。在 dotNET Core 中自帶了日誌功能,當然我們也可以使用第三方的 NLog、Serillog 等。
這些日誌框架都提供日誌級別功能,比如:INFO、DEBUG、WARN 和 ERROR 等,這些級別對程序出錯時的排查非常有用,所以在記錄日誌時一定不要都使用 INFO 或者都使用 ERROR 了。
除了級別,日誌的類型有這麼幾類:
- 操作日誌
- 業務日誌
- 錯誤日誌
操作日誌
系統中所有的操作的都記錄下來,包括登錄、數據的增刪改等,主要用來做審計,數據異常操作時的追責等。
隨着時間的推移,日誌的數據量會越來越大,所以需要考慮存儲的方式,比如階段性地將歷史日誌進行存檔。
業務日誌
用戶在界面中輸入數據,點擊一個按鈕,程序中會進行一系列的處理最終返回結果給用戶,在這個過程中對一些關鍵的業務信息進行記錄,可以在系統出現問題時方便排查和追蹤。
錯誤日誌
錯誤日誌的主要目的就是排錯,所以記錄的信息一定要是對排錯有幫助的信息,儘可能地記錄詳細,比如整個堆棧信息、調用鏈等。比如,你去進行錯誤排查時,發現記錄的信息是”未將對象引用到對象的實例“,你依然不知道錯誤的原因是什麼。
總結
談及代碼的時候,都會去聊架構、模式,這些固然重要,但編碼習慣和規範也不可小視。一份產品的代碼怎樣才能變得越來越好,這需要團隊每個人成員共同的努力,一個人掉鏈子,很容易就形成破窗效應,導致壞味道越來越多。
寫好的代碼,是每個有追求的技術人的使命和職責。
希望本文對您有所幫助。