編寫高質量可維護的程式碼:一目瞭然的註釋

政採雲前端團隊發表於2020-10-01
71 篇原創好文~
本文首發於政採雲前端團隊部落格:編寫高質量可維護的程式碼:一目瞭然的註釋

編寫高質量可維護的程式碼:一目瞭然的註釋

前言

有一些人認為,好的程式碼是自我解釋的。合適的命名和優秀的程式碼的確可以減輕開發人員閱讀程式碼的工作量,對於不是特別複雜的程式碼可能確實可以做到自我解釋。但並不是所有場景都可以做到這一點,我們一起來了解一下“註釋”吧。

程式語言中對“註釋”的解釋

註釋就是對程式碼的解釋和說明。註釋是開發人員在編寫程式時,給一段程式碼的解釋或提示,有助於提高程式程式碼的可讀性。註釋不會被計算機編譯。

要不要加註釋?為什麼要加註釋?

註釋的存在就是為了方便自己的二次閱讀和程式碼維護以及專案交接。可以更好的理解程式碼,有助於提高協作效率,加快開發程式。

試想,你新增了一段邏輯較為複雜的程式碼,幾個月後再看,還能不能迅速看懂?你剛剛接手一個老專案,專案裡基本沒有註釋且邏輯複雜,你能高效率的看懂程式碼和了解業務嗎?

所以新增註釋還是有一定必要滴。

基礎篇

快捷鍵 windows: ctrl+/ mac: command+/
註釋的分類

一、 HTML 中的註釋

<div>
      這是一行文字
      <!-- 這是一行被註釋的文字 -->
</div>

二、CSS 中的註釋

  • 在 .html 檔案中
<style>
  div {
      /* color: #fff;  */
   }
</style>
  • 在 .css 檔案中
div {
    /* color: #fff;  */
}
  • 在 .less 或 .scss 檔案中
div {
    /* color: #fff;*/  /* 多行註釋*/
    // font-size: 14px; // 單行註釋
    background: #000;
}

三、JS 中的註釋

  • 用法

    • 可用於解釋 JavaScript 程式碼,增強其可讀性。
    • 也可以用於阻止程式碼執行。
  • 單行註釋(行註釋)—— 以 // 開頭。任何位於 // 之後的文字都會被註釋
// 定義一個空陣列
var ary = [];
var ary2 = []; // 又定義一個空陣列
  • 多行註釋(塊註釋)——以 /* 開頭,以 */ 結尾。任何位於 /**/ 之間的文字都會被註釋
/*
    這是多行註釋
    定義一個陣列
 */
var ary = [];
  • 用註釋來阻止程式碼執行 —— 被註釋的 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 中保持一致,有單行註釋 // 和多行註釋 /* */ 兩種。單行註釋編譯之後不會被保留。
  • 單行註釋為什麼有時候寫在程式碼上方,有時候寫在程式碼後方?

    • 註釋可以書寫在程式碼中的任意位置。個人理解,一般寫在程式碼上方的時候意為對後面一段程式碼的註釋,而寫在程式碼後方的時候意為對本行程式碼的註釋。
註釋寫法規範
  • 檔案註釋

    • 位於檔案頭部,一般包含概要、作者、版本改動資訊以及修改時間等內容
  /*
   * 簡述當前檔案功能
   * @author 作者名稱
   * @version 版本號 最近編輯時間
   * @description 該版本改動資訊
   */
  • 單行註釋

    • 總是在 // 後留一個空格
  // 這是一行註釋
  • 多行註釋

    • 總是保持星號縱向對齊(結束符前留一個空格)
    • 不要在開始符、結束符所在行寫註釋
    • 儘量使用單行註釋代替多行註釋
    • 註釋函式時,推薦使用多行註釋
  /*
    這裡有一行註釋
    這裡有一行註釋
    這裡有一行註釋
   */
  • 函式註釋

    • 其間每一行都以 * 開頭,且與第一行第一個 * 對齊
    • 註釋內容與 * 間留一個空格
    • 必須包含標籤註釋。例:
/**
* 方法說明
* @method 方法名
* @for 所屬類名
* @param {引數型別} 引數名 引數說明
* @return {返回值型別} 返回值說明
*/
註釋常用標籤用法
  • @type {typeName}

    • * 表示任何型別
    • ? 表示可以為 null
    • ! 表示不能為 null
    • [] 表示陣列
/**
* @type {number}
*/
var foo1;

/**
* @type {*}
* @desc 任何型別
*/
var foo2;

/**
* @type {?string}
* @desc string或者null
*/
var foo3;
  • @param {<type>} name - some description

    • 非必傳引數需給引數名加上 []
    • 引數如有預設值需用 = 表示
    • 如果引數是 Object,可繼續用 @param 對其屬性進行詳細說明
    • 若干個引數用 ... 表示
/**
 * @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 中的一行註釋“被執行”了?

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中用於生成檔案頭部註釋和函式註釋的外掛
  • 檔案頭部新增註釋

    • 在檔案開頭新增註釋,記錄檔案資訊/檔案的傳參/出參等
    • 支援使用者高度自定義註釋選項, 適配各種需求和註釋。
    • 儲存檔案的時候,自動更新最後的編輯時間和編輯人
    • 快捷鍵:windowctrl+alt+imacctrl+cmd+ilinuxctrl+meta+i

  • 在游標處新增函式註釋

    • 在游標處自動生成一個註釋模板
    • 支援使用者高度自定義註釋選項
    • 快捷鍵:windowctrl+alt+tmacctrl+cmd+tlinuxctrl+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(',')}”庫存資訊未完善,請完善庫存資訊`,
    });
}

看到上面這段程式碼可以很明顯的體會到有沒有註釋以及註釋寫的清不清楚的重要性。若是寫了註釋但仍然看不懂,那還不如不寫。

所以註釋也不是隨便寫一寫就可以的,要描述某段程式碼的功能,註明邏輯,讓開發者可以”無腦“瀏覽。

之前在工作群中看到有人發過這樣一張圖(如下圖),個人認為是一個很好的程式碼註釋的範例:

結語

看到這裡,對於註釋的重要性各位已經有自己的認知。還有幾點是我們寫註釋時需要注意的:

  • 註釋內容要簡潔、清楚明瞭。註釋簡述功能或實現邏輯即可,無需每行程式碼都新增註釋
  • 程式碼若有修改,切記同步修改對應的註釋。不要出現過期的註釋,否則會起到反作用

    有任何意見歡迎下方評論區留言討論~

參考文獻

為什麼要寫註釋
js/javascript程式碼註釋規範與示例
程式碼中的特殊註解 -- TODO、FIXME、XXX的作用
註釋的作用, 以及如何寫註釋
你確定Java註釋不會被執行嗎?80%的人都不知道
IE 瀏覽器條件註釋詳解
關於CSS中對IE條件註釋的問題

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的“老”兵,也有浙大、中科大、杭電等校的應屆新人。團隊在日常的業務對接之外,還在物料體系、工程平臺、搭建平臺、效能體驗、雲端應用、資料分析及視覺化等方向進行技術探索和實戰,推動並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個結果,卻不需要你;如果你想改變你想做成的事需要一個團隊去支撐,但沒你帶人的位置;如果你想改變既定的節奏,將會是“5 年工作時間 3 年工作經驗”;如果你想改變本來悟性不錯,但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業務騰飛的過程,親手推動一個有著深入的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我覺得我們該聊聊。任何時間,等著你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章