Clean Code: 技術筆記04 程式可讀性 Code Readability

Joyce Hsiao
Hsiao’s Blog
Published in
9 min readJan 12, 2023

#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的好壞。

Photo by Evgeniy Surzhan on Unsplash

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.

source: Clean Coding Principles in C#
source: Clean Coding Principles in C#

好的做法是盡量甚麼語言就放在甚麼檔案裡,這樣好處有:

  • 增加可讀性,因為不同語言寫在一起,在顏色上也難以閱讀。
  • 另外,這樣可以有更清楚的職責分工。
  • 最後以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:

--

--