clean-code-dotnet
Table of Contents
介绍
这是来自 Robert C. Martin’s 所写的 Clean Code 《中文版》 本文适用于.NET 和 .NET Core的软件工程实践。它不是一个单纯的编码规范,而是让你创建一个可读写,可重用,可重构的.NET软件开发规范。
当然并非所有的规范都必须严格遵守,甚至有些规范都没有得到普遍认同。这些只是《Clean Code》的作者在多年的实践经验里总结出来的指导方针。
本文灵感来自 clean-code-javascript 和 clean-code-php
我也fork了clean-code-javascript
命名
命名是一个很难,很花时间的事情,但是花点时间取一个好名字还是非常值得的。它会在未来帮你(以及其他看你代码的人)在阅读代码上节约很多时间。命名应该要反映它要做什么,以及和上下文的关系。
个人观点
关于命名,用英文对于我们很大一部分开发者来说,会更困难一些,在一些场合要找到一个合适的英文单词做名字会比较困难。我的建议,要么好好学英文,要么,用好中文变量名(是中文变量名,不是拼音缩写,也不是拼音)。一些词不达意的命名还不如中文,当然前提是项目仅限内部交流的项目。
Bad:
int d;
Good:
int daySinceModification;
避免无效信息命名
程序员必须避免使用无效信息命名,变量的名称应该以反映出我们想用它做什么。
Bad:
var dataFromDb = db.GetFromService().Tolist();
Good:
var listOfEmployee = _employeeService.GetEmployeeListFromDb().Tolist();
使用可以读得出来的名字
如果我们不能读出变量、函数,会发生什么呢?我们会花很多时间在追查这些变量所包含的意思(有时会让我们像个傻瓜一样讨论它)。
PS
原文是发音,翻译为能读出来。demo的例子是变量名用来某些特定缩写,导致无法正确的读出来。我们很多的开发都会有一些这样不好的习惯,在一些场合使用单词缩写做前缀。
Bad:
public class Employee
{
public Datetime sWorkDate { get; set; } // 工作日期是要说明什么
public Datetime modTime { get; set; } // 同样,这是啥意思
}
Good:
public class Employee
{
public Datetime StartWorkingDate { get; set; }
public Datetime ModificationTime { get; set; }
}
使用驼峰命名法(Camelcase)
变量和函数参数使用 驼峰式 进行命名。
Bad:
var employeephone;
public double CalculateSalary(int workingdays, int workinghours)
{
// some logic
}
Good:
var employeePhone;
public double CalculateSalary(int workingDays, int workingHours)
{
// some logic
}
使用领域名称
读我们代码的也是程序猿,我们可以使用一些模式,算法等编程领域内的专有名词,来对变量和函数命名。这样我们就不用花很多时间来解释这些变量与函数的用途,进而节约阅读者的理解时间。
Good
public class SingleObject
{
//create an object of SingleObject
private static SingleObject _instance = new SingleObject();
//make the constructor private so that this class cannot be
//instantiated
private SingleObject() {}
//Get the only object available
public static SingleObject GetInstance()
{
return _instance;
}
public string ShowMessage()
{
return "Hello World!";
}
}
public static void main(String[] args)
{
// illegal construct
// var object = new SingleObject();
// Get the only object available
var singletonObject = SingleObject.GetInstance();
// show the message
singletonObject.ShowMessage();
}
变量
使用有意义的和可发音的变量名 :page_facing_up:
Bad:
var ymdstr = DateTime.UtcNow.ToString("MMMM dd, yyyy");
Good:
var currentDate = DateTime.UtcNow.ToString("MMMM dd, yyyy");
对同一类型的变量使用同一个词汇 :page_facing_up:
Bad:
GetUserInfo();
GetUserData();
GetUserRecord();
GetUserProfile();
Good:
GetUser();
使用可搜索(理解)的名词 (part 1) :page_facing_up:
我们读代码的时间比写代码的要多。因此写出一个好读好理解的代码非常重要。如果无法从变量里帮助我们了解程序的含义,会让我们很受伤。
所以,请取一个能理解的变量名吧。
Bad:
// What the heck is data for?
var data = new { Name = "John", Age = 42 };
var stream1 = new MemoryStream();
var ser1 = new DataContractJsonSerializer(typeof(object));
ser1.WriteObject(stream1, data);
stream1.Position = 0;
var sr1 = new StreamReader(stream1);
Console.Write("JSON form of Data object: ");
Console.WriteLine(sr1.ReadToEnd());
Good:
var person = new Person
{
Name = "John",
Age = 42
};
var stream2 = new MemoryStream();
var ser2 = new DataContractJsonSerializer(typeof(Person));
ser2.WriteObject(stream2, data);
stream2.Position = 0;
var sr2 = new StreamReader(stream2);
Console.Write("JSON form of Data object: ");
Console.WriteLine(sr2.ReadToEnd());
使用可搜索(理解)的名词 (part 2) :page_facing_up:
Bad:
var data = new { Name = "John", Age = 42, PersonAccess = 4};
// What the heck is 4 for?
if (data.PersonAccess == 4)
{
// do edit ...
}
Good:
public enum PersonAccess : int
{
ACCESS_READ = 1,
ACCESS_CREATE = 2,
ACCESS_UPDATE = 4,
ACCESS_DELETE = 8
}
var person = new Person
{
Name = "John",
Age = 42,
PersonAccess= PersonAccess.ACCESS_CREATE
};
if (person.PersonAccess == PersonAccess.ACCESS_UPDATE)
{
// do edit ...
}
使用解释型的变量 :page_facing_up:
Bad:
const string Address = "One Infinite Loop, Cupertino 95014";
var cityZipCodeRegex = @"/^[^,\]+[,\\s]+(.+?)\s*(\d{5})?$/";
var matches = Regex.Matches(Address, cityZipCodeRegex);
if (matches[0].Success == true && matches[1].Success == true)
{
SaveCityZipCode(matches[0].Value, matches[1].Value);
}
Good:
通过解释型的变量名,来帮助理解正则表达式。
const string Address = "One Infinite Loop, Cupertino 95014";
var cityZipCodeWithGroupRegex = @"/^[^,\]+[,\\s]+(?<city>.+?)\s*(?<zipCode>\d{5})?$/";
var matchesWithGroup = Regex.Match(Address, cityZipCodeWithGroupRegex);
var cityGroup = matchesWithGroup.Groups["city"];
var zipCodeGroup = matchesWithGroup.Groups["zipCode"];
if(cityGroup.Success == true && zipCodeGroup.Success == true)
{
SaveCityZipCode(cityGroup.Value, zipCodeGroup.Value);
}
避免深层嵌套并及早返回 :page_facing_up:
太多的 if else 嵌套会让你的代码难追踪,显式好于隐式。
Bad:
public bool IsShopOpen(string day)
{
if (string.IsNullOrEmpty(day))
{
day = day.ToLower();
if (day == "friday")
{
return true;
}
else if (day == "saturday")
{
return true;
}
else if (day == "sunday")
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
Good:
public bool IsShopOpen(string day)
{
if (string.IsNullOrEmpty(day))
{
return false;
}
var openingDays = new string[] {
"friday", "saturday", "sunday"
};
return openingDays.Any(d => d == day.ToLower());
}
Bad:
public long Fibonacci(int n)
{
if (n < 50)
{
if (n != 0)
{
if (n != 1)
{
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
else
{
return 1;
}
}
else
{
return 0;
}
}
else
{
throw new System.Exception("Not supported");
}
}
Good:
public long Fibonacci(int n)
{
if (n == 0)
{
return 0;
}
if (n == 1)
{
return 1;
}
if (n > 50)
{
throw new System.Exception("Not supported");
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
避免内心映射 :page_facing_up:
不要强迫阅读你代码的人自己去转换出变量的真正含义。
显式好于隐式。
Bad:
var l = new[] { "Austin", "New York", "San Francisco" };
for (var i = 0; i < l.Count(); i++)
{
var li = l[i];
DoStuff();
DoSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `li` for again?
Dispatch(li);
}
Good:
var locations = new[] { "Austin", "New York", "San Francisco" };
foreach (var location in locations)
{
DoStuff();
DoSomeOtherStuff();
// ...
// ...
// ...
Dispatch(location);
}
不要添加不必要的上下文 :page_facing_up:
如果你的类/对象的名称告诉你一些东西,就不要出现在变量名里了。
Bad:
public class Car
{
public string CarMake { get; set; }
public string CarModel { get; set; }
public string CarColor { get; set; }
//...
}
Good:
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public string Color { get; set; }
//...
}
使用默认参数来替代一些参数验证判断 :page_facing_up:
Not good:
这个版本不好,因为 breweryName
可能需要为 NULL
.
public void CreateMicrobrewery(string name = null)
{
var breweryName = !string.IsNullOrEmpty(name) ? name : "Hipster Brew Co.";
// ...
}
Good:
这个版本比上面的容易理解,因为他能更好的说明变量的默认值。也允许传入空值
public void CreateMicrobrewery(string breweryName = "Hipster Brew Co.")
{
// ...
}
避免魔法字符串
魔法字符串是指在应用程序里影响程序运行的特定字符串值。通常这些字符串会在系统里不停的复制,并且无法通过重构工具进行自动更新,所以当对某些字符串进行修改,但又没有全部更改时,日常BUG就这样出现了。
Bad
if(userRole == "Admin")
{
// logic in here
}
Good
string ADMIN_ROLE = "Admin"
if(userRole == ADMIN_ROLE)
{
// logic in here
}
我们只需要修改这一个地方,其它的都会变化。