Ch2 複雜度(Complexity)| 可測試的 JavaScript (Testable JavaScript) 閱讀筆記

人為的「無法想清楚邏輯」或「複雜的演算法」往往導致寫出複雜的程式碼。因此解法即是透過辨別複雜的成因,來試著降低複雜度。

程式碼大小 (Code Size)

這裡指的「程式碼大小」是每個函式的大小,並非一個檔案的總行數或總字數,當然檔案內所包含的函式也應該盡量相關、少量。

為了精簡每個函式的大小,可用「命令查詢分離」來拆分程式碼,意即將函式拆分為命令 (command) 與查詢 (query)。

這樣的拆分方式除了程式的精簡、可讀性外,也能讓測試方向更為明確、聚焦、有彈性。

備註

在這裡書中提到幾個觀念…

JSLint

JSLint 是較早期的工具,目前業界較常使用 ESLint。

循環複雜度 (Cyclomatic Complexity)

循環複雜度是指測量程式碼中,無相依關聯 (independent path) 內容的數量,意即計算在所有程式碼中,所需撰寫最少單元測試的數量,等同於程式碼覆蓋率 (code coverage) 必須達成 100% 的單元測試數量。

如何計算循環複雜度

工具

在 VSCode 安裝 CodeMetrics,即可看到每個 function 的複雜度。

VSCode - CodeMetrics

注意

範例

if/then/else 判斷改寫為查找表 (lookup table),雖然不減少必要撰寫 unit test 的數量,但可維護性提高。

修改前

針對單一函式,為多個分支而寫多個單元測試。

const doSomething = (a) => {
  if (a === 'x') {
    doX();
  } else if (a === 'y') {
    doY();
  } else {
    doZ();
  }
};

單元測試範例。

describe('doSomething', () => {
  it('a as x....');
  it('a as y....');
  it('a is not x nor y....');
});

修改後

針對多個比較小的函式,撰寫單一的單元測試,清楚明瞭即是好維護。

const doAnotherThing = (a) => {
  const lookup = {
    x: doX,
    y: doY,
  };
  const def = doZ;

  lookup[a] ? lookup[a]() : def();
};

單元測試範例。

describe('lookup a is x', () => {
  it('doX', () => {
    /* ... */
  });
});

describe('lookup b is y', () => {
  it('doY', () => {
    /* ... */
  });
});

describe('def', () => {
  it('doZ', () => {
    /* ... */
  });
});

Demo


補上另一個範例,針對不同狀況顯示不同的樣版元件。

const renderer = (step) => {
  const lookup = {
    stepA: {
      component: TestA,
      config: { text: 'HelloWorld', number: 1 }
    },
    stepB: {
      component: TestB,
      config: { text: 'HelloWorld', number: 2 }
    }
  };
  const Comp = lookup[step].component;
  const config = lookup[step].config;

  return <Comp {...config} />;
};
renderer('stepA')

重複使用 (Reuse)

重複使用程式碼可減少應用程式的複雜度。

對於一個應用程式來說

因此,要降低程式碼的數量,要做到

扇出 (Fade-out)

扇出 (fade-out) 是測量函式 (function / method) 直接或間接依賴模組或物件的數量。

重構為可測試的程式

若函式的扇出數過高,表示和愈多模組或物件有關連,這樣在測試時要 mock 的東西就愈多,寫起測試大費周章 - 測試程式可能一點點,但前置作業非常累人。因此,重構此函式為可測試的程式。

扇入 (Fade-in)

扇入 (fade-in) 是測量函式使用其他模組或物件的數目,基本上愈大的扇入數愈好,表示愈多共用、重用性愈高。例如:寫 log、除錯 debug 紀錄等。

耦合 (Coupling)

耦合 (coupling) 表示模組或物件間的關聯狀況。

以下分六個等級,耦合度由高至低依序是

實體 (Instantiation)

相依性注入 (Dependency Injection)

定義

寫測試的步驟

依賴反轉原則

Ref: wiki

註解 (Comments)

使用註解說明要測試什麼、要怎麼測試,可用工具 - JSDocDocco/Rocco

人為測試 (The Human Test)

本章重點回顧


可測試的 JavaScript 單元測試 自動化測試 Testable JavaScript javascript Unit Test OOP 物件導向程式設計 VSCode 讀書會 閱讀筆記