Robert C. Martin寫的《Clean Code》是我讀過的最好的程式設計書籍之一,若沒有讀過,推薦你將它加入書單。
註釋就意味著程式碼無法自說明 —— Robert C. Martin
Martin在文中詳細討論了程式碼註釋,我不會完全重複他的話。簡而言之,他的意思就是,這些註釋是註定會過時的。程式執行時會忽視註釋,所以無法保證這些說明註釋會準確的描述程式碼作用。所以最好的方式是讓程式碼自說明,如此,按照程式碼邏輯,程式設計師和程式獲取到的資訊是一致的。
思考如下程式碼:
1 2 3 4 5 |
// Check to see if the employee is eligible for full benefits // 檢查員工是否有資格獲取全部福利 if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) { … } |
註釋有用麼?當然有用,但下面的方式可能更好:
1 2 3 |
if (employee.isEligibleForFullBenefits()) { … } |
程式碼需要“言行一致”,註釋是能夠被命名良好的函式或變數取代的。Martin的意思並不是說永不使用註釋,而是應該儘量避免寫註釋,註釋就意味著程式碼無法自說明。
那麼對CSS而言呢?
我非常贊同Martin關於註釋的看法。當涉及到宣告式的語言如CSS時,就發現了一些有趣的地方。宣告式語言式必須符合對應格式的,而CSS選擇器基本是由HTML結構決定的。對這種程式碼結構,我們能做的不多,這是否意味著CSS程式碼必須註釋滿天飛?
額,也許吧。有很多的理由使用註釋,且註釋的寫法也有很多。讓我們來看一些註釋,思考這些註釋是否應該新增。先從答案顯然的開始吧,然後一步步深入到不那麼好判斷的。
不好:多此一舉的註釋
任何語言,多此一舉的註釋都是多餘的,如下的示例出自Bootstrap3的早期版本:
1 2 |
// Addresses address {…} |
顯然,address是關於地址的選擇器
1 2 3 |
// Unordered and Ordered lists ul, ol {…} |
還有?
1 2 |
// Blockquotes blockquote {…} |
趕緊停!
千萬不要寫那種註釋,趕緊刪掉這些多餘的東西,它僅僅是在重複程式碼而已。當然,新版本的Bootstrap已經刪除掉大部分多此一舉的無用註釋了。
不好: 塊分隔註釋
對CSS而言,塊分隔註釋是非常特殊的,如下:
1 2 3 |
/* ----------------- * TOOLTIPS * ----------------- */ |
這種註釋能把我逼瘋。我能想到為什麼會寫下這種註釋:有時候我們的CSS會寫得非常長,當在超過千行的檔案內查詢時,就需要這種帶特殊標誌的註釋來幫助快速搜尋。
但事實上,很長很長的CSS檔案已經不再流行了。若你的專案確實需要這種很大的CSS檔案,它應該是由多個小的部分,通過CSS預處理工具組合而成的。
不好:解釋語法
又要用Bootstrap舉例了,以下程式碼出自 _tooltips.scss:
1 2 3 |
// Allow breaking very long words so they don't overflow the tooltip's bounds // 設定長單詞換行 word-wrap: break-word; |
這種方式和“多此一舉的註釋”類似,註釋解釋word-wrap
屬性的作用。這裡有一篇文章講到這種註釋為什麼不需要的原因,註釋應該解釋“為什麼”,而不是“是什麼”,即說明原因而不是說明作用(Why, not what)。
此處有一個例外,由於CSS有很多屬性,也許有些屬性是你完全不知道的,那麼你用這種註釋是正常的。
不好:對庫進行介紹
如下是Bootstrap tooltips.scss檔案的另一段註釋:
1 2 3 4 5 6 7 |
// Our parent element can be arbitrary since tooltips are by default inserted as a // sibling of their target element. So reset our font and text properties to avoid // inheriting weird values. // 由於提示框會被預設插入到目標元素後作為一個兄弟元素, // 所以需要重置提示框的字型屬性避免從父元素繼承樣式影響。 <a href='http://www.jobbole.com/members/include'>@include</a> reset-text(); font-size: $font-size-sm; |
這條註釋很有意思,看起來似乎並不違反“說明原因而不是說明作用?”規則,它表明由於可能會被一些意料之外的繼承字型屬性影響,所以用匯入的方式來重置字型屬性。
但進一步來看,顯然在檔案頭匯入重置樣式的唯一的解釋就是擔心被繼承樣式影響。
所以,我認為這種註釋也是不需要的,因為匯入函式名字已經說明用途了,儘量讓函式名切合作用,如reset-inherited-font
或類似的名字,不僅清晰說明了用途還是說明了原因。這個是一個函式呼叫,函式名已經足夠解釋了。優先用這種方式來說明用途可以替代一些註釋。
CSS前處理器讓CSS更接近傳統程式語言。儘可能使用命名良好且有意義的變數和函式,這樣能讓程式碼更清晰。
不好: 過時的註釋
1 2 3 4 |
.dropdown-header { … white-space: nowrap; // as with > li > a } |
“as with > li > a”是什麼意思?我第一反應就是也許在檔案中還有一個> li > a
的選擇器,而這行程式碼就是指那個選擇器。也許檔案中有一段註釋會專門解釋為何這樣寫,但我將檔案重頭到尾都看了一邊,發現並沒有這個選擇器。檔案只有一個.dropdown-item
選擇器下有一個nowrap
屬性,也許是就是指這個?或者也許這段註釋是指某行已經被刪除的程式碼或引入其他檔案中的程式碼?若想要徹底弄清楚這個註釋的作用,唯一的方法就是翻遍整個git記錄了吧。
這是一個過時的註釋,也許它以前是有用的,但卻長時間沒有用到,所以過時了。這也許就是為什麼Robert Martin對註釋的看法:若註釋對應的程式碼更新了註釋就沒用了,甚至更糟糕,註釋可能會將你引到錯誤的方向。若發現這樣的註釋,一定要刪掉。它完全沒用,而且會浪費時間去思考到底有啥用?
有時有用的:有特殊意義的註釋
如下是一段帶註釋的程式碼:
1 2 3 |
.dropdown-item { display: block; width: 100%; // For ` |
這樣的註釋就是有用的,它們能告訴我們,這些特定的屬性是為覆蓋樣式而寫的。這樣的註釋就是有用的,因為有時候程式碼的意圖不是那麼顯而易見的。
但此時也需要問一個問題:有什麼辦法能讓程式碼自說明呢?需要可以考慮將這些特定的屬性移到第二個選擇器中,專門為這些按鈕設定的選擇器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.dropdown-item { display: block; padding: $dropdown-item-padding-y $dropdown-item-padding-x; clear: both; font-weight: $font-weight-normal; color: $dropdown-link-color; white-space: nowrap; } button.dropdown-item { width: 100%; text-align: inherit; background: none; border: 0; } |
這樣就非常清晰且易於理解,但副作用就是:專門增加了一個特殊的選擇器。
而相反,我認為這種方式非常利於使用mixin混入模式。重構為一個函式,該函式能在其他地方定義,並且讓程式碼更清晰。考慮如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
.dropdown-item { <a href='http://www.jobbole.com/members/include'>@include</a> remove-button-styles; display: block; width: 100%; padding: $dropdown-item-padding-y $dropdown-item-padding-x; clear: both; font-weight: $font-weight-normal; color: $dropdown-link-color; white-space: nowrap; } |
這段程式碼沒有用任何註釋,但其功用很清晰,因為它使用的公用函式在其他模組也能用到。我將width:100%
保留下來而不是移到函式中,因為若將函式混和程式碼時,width:100%
可能會引起一些其他問題。
在我開始發現“程式碼異味(Code Smell)”之前,一開始.dropdown-item
程式碼有十行,我非常喜歡用mixin,mixin是一個能極大減少程式碼行數的好東西,它能讓我們快速的知道程式碼的大致用途。
雖然使用函式重構程式碼並不是都這樣有效,但儘量多用。
好:註解難懂的補丁性的程式碼
我對註釋也不是總那麼苛刻的,比如我就很難找到下面的註釋的問題,若你曾看過normalize.css的原始碼,你一定會注意到它滿滿的註釋,不得不說,真是“極好的”註釋。
欣賞一番:
1 2 3 4 5 6 7 8 9 10 11 |
/** * 1. Add the correct box sizing in Firefox. * FF下正常的盒子模型 * 2. Show the overflow in Edge and IE. * 在Edge和IE下overflow為visble */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } |
若沒有這些註釋,你永遠不知道為何這樣寫。修復特定瀏覽器bug的程式碼往往是晦澀難懂的,常常會被當做無用程式碼刪掉。
由於Normalize庫的目標是提供一個完全一致樣式環境,所以需要很多這樣的註釋。選擇器都是型別和屬性選擇器,沒有任何class名,同時由於不是可命名的class名,所以自文件非常困難。
如下為另一段Bootstrap的註釋:
1 2 3 4 |
/* Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245 */ select { background: #fff !important; } |
一個Github連結,非常有用。即使不開啟連線也能知道這兒是一個bug,而且有可能是一個非常難定位的bug。若有需要,可以通過連結獲取更多資訊。最棒的是,因為沒有大段大段的文字去解釋bug,所以它並不會打亂程式碼邏輯,同時也告訴我們哪裡可以獲取更多資訊。若使用專案與事務跟蹤工具如JIRA,那麼可以直接在註釋中與編號關聯起來。
當然,不是每個打補丁的程式碼都要這樣註釋,但若bug不是那麼容易發現,而且與瀏覽器怪癖有關,那麼還是這樣註釋吧。
好:指令式註釋
一些工具如KSS , 會在CSS檔案中建立一些樣式規範。如下:
1 2 3 4 5 6 |
/* Alerts An alert box requires a contextual class to specify its importance. 一個警告資訊框需要與語境有關的的類來指定其重要性 Markup: |
1 2 3 4 5 6 7 |
alert-success - Something good or successful 好的或成功的 alert-info - Something worth noting, but not super important 不那麼重要的 alert-warning - Something to note, may require attention 需要被提示並記錄,需要引起注意的 alert-danger - Something important. Usually signifies an error. 非常重要的,常用於錯誤 Styleguide Alerts */ |
這不僅僅是註釋,這是規範,它能被KSS解析並用於生成HTML。這已經算是專案文件的一部分了,而且不得不說,這比手動建立一個分離的HTML檔案要好很多,因為其在同一個檔案內且始終與程式碼相匹配。
另外一種指令式註釋為許可資訊,當使用第三方庫並在註釋中註明許可資訊時,一般都需要包含。
而我貼出Robert Martin關於註釋的話時 Robert Martin 的話 ,似乎應該解釋一下,但沒有那麼做。因為我認為這是一句容易理解的話,若你還在程式碼中到處寫註釋,那麼請先思考是否合理。