Clean Code: 技術筆記04 程式可讀性 Code Readability
#CleanCode #CodeReadability
Clean code是我正在學習的方向,因為程式中往往是"讀"比"寫"還要難,在沒有清楚的文件下,常常都會覺得自己是考古學家,在研究古早人的想法語言。
因此,clean code 也可讓我們減少程式的技術債(technical debt),重點是 Pluralsight: Clean Coding Principles in C# 課程的講者Cory House提到
"The 3 qualities of a good programmer are laziness, impatience and hubris"
這篇記錄下Pluralsight課程中提到常見的程式撰寫習慣,並以"可讀性"的角度去分辨coding的好壞。
Understanding the original programmer’s intent is the most difficult problem. . — Fjelstad & Hamlen 1979
1. Use the right tool
這裡的tool指的是各種語言及工具,而最常在各語言的"Boundary"中使用到錯誤的工具,例如在.html檔放JS stirng, css setting.
好的做法是盡量甚麼語言就放在甚麼檔案裡,這樣好處有:
- 增加可讀性,因為不同語言寫在一起,在顏色上也難以閱讀。
- 另外,這樣可以有更清楚的職責分工。
- 最後以JS in HTML為例,分開寫也會讓cached速度變更快,因為JS放在HTML的話,它會在每個page request一直被下載下來。
2. Avoid noise
直接看code,非常直覺的寫法,just keep in mind.
Worse: So much noise!!
#region -- InitCountryDreopdown Method --
///<summary>
///Summary description for CountryDropDownList
///</summary>
protected override void InitCountryDropdown(EventArgs e)
{
if(Items.Count==0)
{
this.DataSource = CountryTable();
this.DataTextField = "CountryName";
//CH 2012-4-5 - Adding separate data value field
//to fix bug #4444
//we're now storing the country ID instead
//of the country name is desired
if(useCountryName == true)
{
this.DataValueField = "CountryName";
}
else
{
this.DataValueField = "CountryID";
}
this.DataBind();
this.CssClass = "entryfield";
}
}
#endregion
Better:
protected override void InitCountryDropdown(EventArgs e)
{
if(Items.Count > 0) return;
this.DataSource = CountryTable();
this.DataTextField = "CountryName";
this.DataValueField = useCountryName? "CountryName" : "CountryID"
this.DataBind();
this.CssClass = "entryfield";
}
}
3.Naming
- 變數命名: 使用有意義的名詞
List<int> p = new List<int>(); // hard to know what is "p"
List<int> price = new List<int>(); // easy to know "price"
- 類別命名: 準則就是當你想要修改功能時,是否能夠快速找到要改的檔案?
//Worse
class WebSiteBo{}
class Common{}
class Info{}
//Better
class User{}
class Account{}
- 方法命名: 準則是一個方法只做一件事,如果命名裡面出現了"and, or, if"就要注意是否有太多邏輯在這個方法裡面。
//Worse
Go(){}
Get(){}
Login_and_userValidation(){}
//Better
GetRegisteredUsers(){}
isValidSubmission(){}
SendEmail(){}
4.Boolean
- Return true/false
//Worse
bool isPassed;
if(score>60)
{
isPassed = true;
}
else
{
isPassed = false;
}
//Better
bool isPassed = isPassed > 60;
- 用Boolean 管理 condition,利用有意義的變數名稱讓人更容易閱讀。
//worse
if(employee.Age > 55
&& employee.YesrsEmployed>10
&& employee.IsRetired == true
){}
//better
bool eligibleForPension = employee.Age > 55
&& employee.YesrsEmployed>10
&& employee.IsRetired == true;
if(eligibleForPension){}
- 封裝複雜條件
// Worse
if((fileExt == ".mp4"
|| fileExt ==".mpg"
|| fileExt == ".avi")
&& (isAdmin == 1 || isActiveFile))
//Better
private bool ValidFileRequest(string fileExtension,bool isActiveFile,bool isAdmin)
{
var validFileExtensions = new List<string>(){".mp4",".mpg",".avi"};
bool validFileType = validFileExtensions.Contains(fileExtension);
bool userIsAllowedToViewFile = isActive || isAdmin;
return validFileType && userIsAllowedToViewFile
}
5. 善用constants/enum
- 直接用string去hard code的話,如果拼錯字在runtime時才會出錯。
- 如果用變數,可以看到reference也易於更改
//worse
if(employee == "manager")
//Better
if(employee.type == EmployeeType.Manager)
- 避免直接用數字做判斷,因為難直接了解商業邏輯
//worse
if(age > 21){}
if(status == 2){}
//Better
const int legalDrinkingAge = 21;
if(age > legalDrinkingAge){}
6. 善用宣告式語言(Be Declarative)
//worse
List matchingUsers = new List<User>();
foreach(var user in users)
{
if(user.AccountBalance < minAccountBalance && user.Status == Status.Active)
{
matchingUsers.Add(user);
}
}
return matchingUsers;
//better
return users
.where(u=> u.AccountBalance < minAccountBalance)
.where(u=> u.status == Status.Active);
7. 避免箭頭式if/else
- 利用方法(Method)
- 調整邏輯順序
以上是一些常見的程式不好的寫法以及建議寫法,其實課程中還有提到很多其他情境,只是我簡單挑出一些我常犯的毛病。
要注意的是,這篇的好或不好是根據"可讀性"來判斷,最終還是要依照使用情境的不同要做多方考量(例如效能、可維護性等等),才能撰寫出最合適的程式。
Reference: