編寫高質量可維護的代碼:一目了然的注釋
前言
有一些人認為,好的代碼是自我解釋的。合適的命名和優秀的代碼的確可以減輕開發人員閱讀代碼的工作量,對于不是特別復雜的代碼可能確實可以做到自我解釋。但并不是所有場景都可以做到這一點,我們一起來了解一下“注釋”吧。
編程語言中對“注釋”的解釋
注釋就是對代碼的解釋和說明。注釋是開發人員在編寫程序時,給一段代碼的解釋或提示,有助于提高程序代碼的可讀性。注釋不會被計算機編譯。
要不要加注釋?為什么要加注釋?
注釋的存在就是為了方便自己的二次閱讀和代碼維護以及項目交接。可以更好的理解代碼,有助于提高協作效率,加快開發進程。
試想,你添加了一段邏輯較為復雜的代碼,幾個月后再看,還能不能迅速看懂?你剛剛接手一個老項目,項目里基本沒有注釋且邏輯復雜,你能高效率的看懂代碼和了解業務嗎?
所以添加注釋還是有一定必要滴。
基礎篇
快捷鍵 windows 系統: Ctrl+/
ios 系統: Command+/
注釋的分類
- HTML中的注釋
<div>
這是一行文字
<!-- 這是一行被注釋的文字 -->
</div>
-
CSS 中的注釋
- <style>
- div {
- /* color: #fff; */
- }
- </style>
- div {
- /* color: #fff; */
- }
- div {
- /* color: #fff;*/ /* 多行注釋*/
- // font-size: 14px; // 單行注釋
- background: #000;
- }
- 在 HTML 中
- 在 .css文件中
- 在 .less 或 .scss 文件中
-
JS 中的注釋
-
用法
- 可用于解釋 JavaScript 代碼,增強其可讀性。
- 也可以用于阻止代碼執行。
-
單行注釋(行注釋)—— 以
//
開頭。任何位于//
之后的文本都會被注釋
-
-
多行注釋(塊注釋)——以
/*
開頭,以*/
結尾。任何位于/*
和*/
之間的文本都會被注釋
- /*
- 這是多行注釋
- 定義一個數組
- */
- var ary = [];
- * 用注釋來阻止代碼執行 —— 被注釋的 JS 代碼將不被執行
- ```js
- //alert("123") // 執行時未彈出該信息
- alert("456") // 執行時彈出該信息
- ```
-
函數注釋
- 一般以
/**
開頭,以*/
結尾。任何位于/**
和*/
之間的文本都會被注釋
- 一般以
- /**
- * 提交
- *
- * @method onSubmit
- * @param {[Object]} 提交數據
- * @return {[Bollean]} [返回是否提交成功 ]
- */
- const onSubmit = (params = {}) => {
- const result = false;
- if (params) {
- result = true;
- }
- return result;
- };
- 特殊標記注釋
- TODO 在該注釋處有功能代碼待編寫,待實現的功能在說明中會簡略說明
- FIXME 在該注釋處代碼需要修正,甚至代碼是錯誤的,不能工作,需要修復,如何修正會在說明中簡略說明
- XXX 在該注釋處代碼雖然實現了功能,但是實現的方法有待商榷,希望將來能改進,要改進的地方會在說明中簡略說明
- NOTE 在該注釋處說明代碼如何工作
- HACK 在該注釋處編寫得不好或格式錯誤,需要根據自己的需求去調整程序代碼
- BUG 在該注釋處有 Bug
- // TODO功能未完成,待完善
- // FIXME 待修復
- // XXX 實現方法待確認
- // NOTE 代碼功能說明
- // HACK 此處寫法有待優化
- // BUG 此處有Bug
- const arr = []
Tips:
- 為什么
//
注釋可以在 .less 或 .scss 文件中使用,但是在 .html 和 .css 文件中不生效?- 在 MDN 中關于 CSS 注釋只有
/* */
一種語法。但是在 LESS 和 SCSS 中支持注釋的語法和 js 中保持一致,有單行注釋//
和多行注釋/* */
兩種。單行注釋編譯之后不會被保留。
- 在 MDN 中關于 CSS 注釋只有
- 單行注釋為什么有時候寫在代碼上方,有時候寫在代碼后方?
- 注釋可以書寫在代碼中的任意位置。個人理解,一般寫在代碼上方的時候意為對后面一段代碼的注釋,而寫在代碼后方的時候意為對本行代碼的注釋。
注釋寫法規范
-
文件注釋
- 位于文件頭部,一般包含概要、作者、版本改動信息以及修改時間等內容
- /*
- * 簡述當前文件功能
- * @author 作者名稱
- * @version 版本號 最近編輯時間
- * @description 該版本改動信息
- */
-
單行注釋
- 總是在
//
后留一個空格
- 總是在
- // 這是一行注釋
-
多行注釋
- 總是保持星號縱向對齊(結束符前留一個空格)
- 不要在開始符、結束符所在行寫注釋
- 盡量使用單行注釋代替多行注釋
- 注釋函數時,推薦使用多行注釋
- /*
- 這里有一行注釋
- 這里有一行注釋
- 這里有一行注釋
- */
-
函數注釋
- 其間每一行都以
*
開頭,且與第一行第一個*
對齊 - 注釋內容與
*
間留一個空格 - 必須包含標簽注釋。例: ```javascript /**
- 其間每一行都以
-
方法說明
-
@method 方法名
-
@for 所屬類名
-
@param {參數類型} 參數名 參數說明
-
@return {返回值類型} 返回值說明
-
/
注釋常用標簽用法
- @type {typeName}
*
表示任何類型?
表示可以為null
!
表示不能為null
[]
表示數組 ```javascript /**
- @type {number}
- / var foo1;
/**
- @type {*}
- @desc 任何類型
- / var foo2;
/**
- @type {?string}
- @desc string或者null
- / var foo3;
- * @param {<type>} name - some description
- * 非必傳參數需給參數名加上'[]'
- * 參數如有默認值需用'='表示
- * 如果參數是object,可繼續用`@param`對其屬性進行詳細說明
- * 若干個參數用`...`表示
- ```javascript
- /**
- * @func
- * @desc 一個帶參數的函數
- * @param {string} a - 參數a
- * @param {number} b=1 - 參數b默認值為1
- * @param {string} c=1 - 參數c有兩種支持的取值 1—表示x 2—表示xx
- * @param {object} d - 參數d為一個對象
- * @param {string} d.e - 參數d的e屬性
- * @param {object[]} g - 參數g為一個對象數組
- * @param {string} g.h - 參數g數組中一項的h屬性
- * @param {string} [j] - 參數j是一個可選參數
- */
- function foo(a, b, c, d, g, j) {}
- /**
- * @func
- * @desc 一個帶若干參數的函數
- * @param {...string} a - 參數a
- */
- function bar(a) {}
了解更多可查看 JSDoc
拓展篇
IE 條件注釋(IE5+)
IE 條件注釋分為以下幾種情況:
-
只允許 IE 解釋執行
<!--[if IE]><![endif]-->
-
只允許 IE 特定版本解釋執行
<!--[if IE 7]><![endif]-->
-
只允許非 IE 特定版本執行注釋
<!--[if !IE 7]><![endif]-->
-
只允許高于或低于 IE 特定版本執行注釋
<!--[if gt IE 7]><![endif]-->
- <head>
- <title>IE 條件注釋</title>
- <!-- 是 IE 時 -->
- <!--[if IE]>
- <link href="style.css" rel="stylesheet" type="text/css" />
- <![endif]-->
- <!-- 是 IE 7 時 -->
- <!--[if IE 7]>
- <link href="style.css" rel="stylesheet" type="text/css" />
- <![endif]-->
- <!-- 不是 IE 7 時 -->
- <!--[if !IE 7]>
- <link href="style.css" rel="stylesheet" type="text/css" />
- <![endif]-->
- <!-- 大于 IE 7 時 -->
- <!--[if gt IE 7]>
- <link href="style.css" rel="stylesheet" type="text/css" />
- <![endif]-->
- <!-- 小于 IE 7 時 -->
- <!--[if lt IE 7]>
- <link href="style.css" rel="stylesheet" type="text/css" />
- <![endif]-->
- </head>
# (井號)注釋 和 ''' (三引號)注釋
#
一般出現在各種腳本配置文件中,用法與 JS 單行注釋//
基本相同。Python 中也常常用到'''
是 Python 中的多行注釋語法,用'''
'''
包含被注釋的段落
- # python 的單行注釋一
- print("I could have code like this.") # python 的單行注釋二
print("This won't run.") # 被注釋的代碼
''' 被三引號包裹的段落 可以隨意折行 也可以注釋代碼 print("This won't run.") '''
- #### 注釋“被執行”了?
- 眾所周知,注釋的代碼是不會被執行的。但是小編在查資料時看到了一段比較有意思的代碼, Java 中的一行注釋“被執行”了?
- ```java
- public class Test {
- public static void main(String[] args) {
- String name = "趙大";
- // \u000dname="錢二";
- System.out.println(name);
- }
- }
這段代碼執行后的結果為 錢二
,也就是說在這段代碼中,“被注釋”的那行代碼生效了!
這段代碼的問題出在 \u000d
這串特殊字符上。 \u000d
是一串 Unicode 字符,代表換行符。Java 編譯器不僅會編譯代碼,還會解析 Unicode 字符。在上面這段代碼把 \u000d
給解析了,后面的代碼就到了下面一行,超出了被注釋的范圍(單行注釋的注釋范圍僅在當前行),所以執行結果為 錢二
而非 趙大
。(如下)
- public class Test {
- public static void main(String[] args) {
- String name = "趙大";
- //
- name="錢二";
- System.out.println(name);
- }
- }
所以本質上在代碼執行的時候 name="錢二"
并沒有被注釋,而是被換了行(奇怪的知識增加了)。 所以切記,注釋確實是不會被執行的哦!
注釋相關插件
在這里推薦幾個個人認為比較好用的注釋相關的Vscode插件,可在 setting.json
文件下自定義設置(可通過 '文件—首選項—設置',打開 Vscode 文件 settings.json
)
- koroFileHeader 在 vscode 中用于生成文件頭部注釋和函數注釋的插件
- 文件頭部添加注釋
- 在文件開頭添加注釋,記錄文件信息/文件的傳參/出參等
- 支持用戶高度自定義注釋選項, 適配各種需求和注釋。
- 保存文件的時候,自動更新最后的編輯時間和編輯人
- 快捷鍵:
window
:ctrl+alt+i
,mac
:ctrl+cmd+i
,linux
:ctrl+meta+i
- 在光標處添加函數注釋
- 在光標處自動生成一個注釋模板
- 支持用戶高度自定義注釋選項
- 快捷鍵:
window
:ctrl+alt+t
,mac
:ctrl+cmd+t
,linux
:ctrl+meta+t
- 快捷鍵不可用很可能是被占用了
- 可自定義默認參數
- Better Comments 通過使用警報,信息,TODO 等進行注釋來改善代碼注釋。使用此擴展,您將能夠將注釋分類為:
- 快訊
- 查詢
- 待辦事項
- 強調
- 注釋掉的代碼也可以設置樣式,以使代碼不應該存在
- 可自定義指定其他所需的注釋樣式
- TODO Highlight 突出顯示TODO,FIXME和任何關鍵字
- 高亮內置關鍵字,可通過自定義設置覆蓋外觀
- 也可自定義關鍵字
用事實說話
口說無憑,眼見為實。下面我們看下實際開發中的具體情況:
- 沒有注釋
- const noWarehousetemIds = beSelectSkucontainer.reduce((arr, itemId) => {
- const res = Object.keys(selectRowskey[itemId]).every((skuId) => {
- const sku = selectRowskey[itemId][skuId];
- return !!sku.warehouseCode || lodashGet(warehouses, '[0].code');
- });
- if (!res) {
- arr.push(itemId);
- }
- return arr;
- }, []);
- if (noWarehousetemIds.length > 0 || noStockItemIds.length > 0) {
- const itemIds = Array.from(new Set([...noWarehousetemIds, ...noStockItemIds]));
- const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);
- return Modal.warning({
- title: '錯誤提示',
- content: `“${itemNames.join(',')}”庫存信息未完善,請完善庫存信息`,
- });
- }
- 一般般的注釋
- // 遍歷當前所有選中的sku,查找出沒有庫存的itemId
- const noStockItemIds = beSelectSkucontainer.reduce((arr, itemId) => {
- const res = Object.keys(selectRowskey[itemId]).every((skuId) => {
- const sku = selectRowskey[itemId][skuId];
- return !!sku.stockQuantity;
- });
- if (!res) {
- arr.push(itemId);
- }
- return arr;
- }, []);
- // 有一條sku的庫存為空時進入校驗
- if (noStockItemIds.length > 0) {
- const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);
- return Modal.warning({
- title: '錯誤提示',
- content: `“${itemNames.join(',')}”庫存信息未完善,請完善庫存信息`,
- });
- }
- 更好的注釋
- // 遍歷當前所有選中的sku,查找出沒有庫存的itemId
- const noStockItemIds = beSelectSkucontainer.reduce((arr, itemId) => {
- // selectRowskey是一個對象,以itemId為key,sku對象作為value,sku對象以skuId作為key,sku作為value,只有selectRowskey下所有itemId下的sku都有庫存才算校驗通過
- /*
- 數據格式:
- selectRowskey: {
- 12345678: { // itemId
- 123456: { // skuId
- name: 'sku',
- }
- }
- }
- */
- const res = Object.keys(selectRowskey[itemId]).every((skuId) => {
- const sku = selectRowskey[itemId][skuId];
- return !!sku.stockQuantity;
- });
- // 只要有一條sku沒有庫存時,就塞到arr中,返回給noStockItemIds數組
- if (!res) {
- arr.push(itemId);
- }
- return arr;
- }, []);
- // 有一條sku的庫存為空時進入校驗
- if (noStockItemIds.length > 0) {
- // 根據id查找商品名稱
- const itemNames = itemIds.map(i => this.itemNameMap[i].itemName);
- Modal.warning({
- title: '錯誤提示',
- content: `“${itemNames.join(',')}”庫存信息未完善,請完善庫存信息`,
- });
- }
看到上面這段代碼可以很明顯的體會到有沒有注釋以及注釋寫的清不清楚的重要性。若是寫了注釋但仍然看不懂,那還不如不寫。
所以注釋也不是隨便寫一寫就可以的,要描述某段代碼的功能,注明邏輯,讓開發者可以”無腦“瀏覽。
之前在工作群中看到有人發過這樣一張圖(如下圖),個人認為是一個很好的代碼注釋的范例:
結語
看到這里,對于注釋的重要性各位已經有自己的認知。還有幾點是我們寫注釋時需要注意的:
-
注釋內容要簡潔、清楚明了。注釋簡述功能或實現邏輯即可,無需每行代碼都添加注釋
-
代碼若有修改,切記同步修改對應的注釋。不要出現過期的注釋,否則會起到反作用