聞香識程式碼,什麼是衡量程式碼質量的終極標準?

Zilliz發表於2021-11-03

? 小聲提醒 ?

關注 Zilliz 微信公眾號並回復「clean code」

獲取《程式碼整潔之道》超詳細思維導圖

圖片

我們為什麼要追求整潔的程式碼?

《程式碼整潔之道》(Clean Code)提出,程式碼質量與其整潔度成正比。

只是讓程式碼跑起來是不夠的,因為需求不斷增長,程式碼需要與時俱進:增加新的特徵、修改已有的功能,優化效能……大工程量的專案需要由團隊共同維護程式碼。

在團隊協作中,冗餘的邏輯和混亂的程式碼會讓浪費大量的時間和資源,畢竟誰都不想捏著鼻子維護一座「??⛰️」。因此,優秀的軟體工程師追求程式碼上的「潔癖」,以寫出賞心悅目、可擴充套件、易維護的程式碼為己任。

圖片

《程式碼整潔之道》這本書正是一本程式碼清潔指南。這本書的作者是軟體行業泰斗 Robert Martin,他是設計模式和敏捷開發的先驅,被後輩程式設計師尊稱為「Uncle Bob」。2001 年,他和另外十六位頂級軟體行業領軍人物共同簽署了《敏捷開發宣言》,為高效、可協作的軟體開發流程建立了核心價值觀,而《程式碼整潔之道》幫助敏捷開發打下基礎,提供了一套行之有效的操作指南。

無論你是開發人員、軟體工程師,還是專案經理、團隊領導,如果你希望保持專業、不斷精進,都應該讀一讀這本書。

你將從這本書中獲得:

  • 學會如何區分程式碼的好壞,快速識別「髒亂」程式碼
  • 學會優化程式碼,讓程式碼正確、簡潔、優美、可持續
  • 讓程式碼易於閱讀和理解,幫你的隊友節約時間
  • 養成使用自動化測試和單元測試的習慣,避免惡性迴圈
  • 理解真正的專業精神

整潔的程式碼長啥樣?

第一章中,作者引用了 C++ 語言發明者 Bjarne Stroustrup 的話:

程式碼邏輯應當直截了當,叫缺陷難以隱藏;儘量減少依賴關係,使之便於維護;依據某種分層戰略完善錯誤處理程式碼;效能調至最優,省得麻煩別人做沒規矩的優化,搞出一堆混亂來。整潔的程式碼只做好一件事。

除此之外,整潔的程式碼還應該:

  • 便於閱讀,可由作者之外的開發者增補
  • 有單元測試和驗收測試
  • 使用有意義的命名
  • 依賴關係儘量精簡
  • 程式碼應通過其字面表達含義
  • 沒有重複程式碼

如何「清理」你的程式碼?

作者以自己走過的彎路為例,給出大量「清理程式碼」的案例。通過一個個具體的案例,作者手把手帶領讀者將問題的程式碼庫轉化為一個健壯和高效的程式碼庫。

讓我們花一兩分鐘看一個案例,下面的程式碼,你能看懂多少?

程式碼清單 3-1 HtmlUtil.java (壞例子)
public static String testableHtml(
  PageData pageData,
  boolean includeSuiteSetup
) throws Exception {
  WikiPage wikiPage = pageData.getWikiPage();
  StringBuffer buffer = new StringBuffer();
  if (pageData.hasAttribute("Test")) {
    if (includeSuiteSetup) {
      WikiPage suiteSetup =
      PageCrawlerImpl.getInheritedPage(
              SuiteResponder.SUITE_SETUP_NAME, wikiPage
      );
      if (suiteSetup != null) {
      WikiPagePath pagePath =
        suiteSetup.getPageCrawler().getFullPath(suiteSetup);
      String pagePathName = PathParser.render(pagePath);
      buffer.append("!include -setup .")
            .append(pagePathName)
            .append("\n");
      }
    }
    WikiPage setup =
      PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
    if (setup != null) {
      WikiPagePath setupPath =
        wikiPage.getPageCrawler().getFullPath(setup);
      String setupPathName = PathParser.render(setupPath);
      buffer.append("!include -setup .")
            .append(setupPathName)
            .append("\n");
    }
  }
  buffer.append(pageData.getContent());
  if (pageData.hasAttribute("Test")) {
    WikiPage teardown =
      PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
    if (teardown != null) {
      WikiPagePath tearDownPath =
        wikiPage.getPageCrawler().getFullPath(teardown);
      String tearDownPathName = PathParser.render(tearDownPath);
      buffer.append("\n")
            .append("!include -teardown .")
            .append(tearDownPathName)
            .append("\n");
    }
    if (includeSuiteSetup) {
      WikiPage suiteTeardown =
        PageCrawlerImpl.getInheritedPage(
                SuiteResponder.SUITE_TEARDOWN_NAME,
                wikiPage
        );
      if (suiteTeardown != null) {
        WikiPagePath pagePath =
          suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
        String pagePathName = PathParser.render(pagePath);
        buffer.append("!include -teardown .")
              .append(pagePathName)
              .append("\n");
      }
    }
  }
  pageData.setContent(buffer.toString());
  return pageData.getHtml();
}

在上面的程式碼中,有太多不同層級的抽象、奇怪的字串、函式呼叫,混以雙重巢狀、用標識來控制的 if 語句等,令人頭疼。只要做幾個簡單的方法抽離和重新命名操作,加上一點點重構,就能在接下來的幾行程式碼中搞定:

程式碼清單 3-2 HtmlUtil.java(重構之後)
public static String renderPageWithSetupsAndTeardowns(
  PageData pageData, boolean isSuite
) throws Exception {
  boolean isTestPage = pageData.hasAttribute("Test");
  if (isTestPage) {
    WikiPage testPage = pageData.getWikiPage();
    StringBuffer newPageContent = new StringBuffer();
    includeSetupPages(testPage, newPageContent, isSuite);
    newPageContent.append(pageData.getContent());
    includeTeardownPages(testPage, newPageContent, isSuite);
    pageData.setContent(newPageContent.toString());
  }

  return pageData.getHtml();
}

這段程式碼比第一個例子清晰多了,你大概能明白,這個函式要把一些設定和拆解頁放入一個測試頁面,再渲染為 HTML。

但這樣優化還不夠整潔,作者說,「函式的第一規則是要短小。第二條規則是還要更短小。」

再次重構後,程式碼終於一目瞭然:

程式碼清單 3-3 HtmlUtil.java(再次重構之後)
public static String renderPageWithSetupsAndTeardowns(
  PageData pageData, boolean isSuite
) throws Exception {
  if (isTestPage(pageData))
    includeSetupAndTeardownPages(pageData, isSuite);
  return pageData.getHtml();
}

需要說明的是,《程式碼整潔之道》中的案例都是用 Java 語言寫的,有一部分案例專門針對 Java。如果你使用其他語言,那麼可以移花接木,理解書中的核心思想即可。

小編在 GitHub 上找到了一個用 JavaScript 去詮釋「Clean Code」理念的專案,該專案給出大量 good / bad 兩面的範例,並輔以註釋作為說明,幫助讀者理解「Clean Code」的理念 ???

圖片

Clean Code Javascript 程式碼地址:https://github.com/ryanmcderm...

氣味和啟發

如何「聞香識程式碼」?作者總結了一些「壞氣味」和「好氣味」。「壞氣味」指的是程式碼中某些疏漏、某些小錯誤,開發人員可以通過這些細節上的徵兆在程式碼中追捕到更大問題;「好氣味」指的是一些應該遵守的規則。

以寫註釋為例,「壞氣味」包括:

  • 冗餘的註釋
  • 被註釋掉的程式碼(應該及時刪除這些程式碼)
  • ……

對於命名而言,「好氣味」的例子包括:

  • 採用描述性的名稱
  • 名稱無歧義
  • 使用標準命名方法
  • 說明副作用
  • ……

這份清單很難窮盡所有的規則,清單背後所體現的價值體系才是「程式碼整潔」的目標。

想要了解更多詳細內容?關注 Zilliz 微信公眾號並回復書名「clean code」,即可獲得小編整理的《程式碼整潔之道》高清思維導圖。

福利時間

你是如何提升程式碼水平的?

你最想吐槽哪些不好的程式碼習慣?

對技術感興趣的你,有哪些心頭好書?

歡迎在微信評論區留言你的想法

我們將選取一位小夥伴,

贈送紙質版《程式碼整潔之道》一本!

積累程式碼量很重要,
讀書、讀好書也很重要。
「Zilliz 好書推薦」欄目,
旨在與你分享技術成長相關的書籍,
與你一起先把書讀厚,再把書讀薄。

Zilliz 以重新定義資料科學為願景,致力於打造一家全球領先的開源技術創新公司,並通過開源和雲原生解決方案為企業解鎖非結構化資料的隱藏價值。

Zilliz 構建了 Milvus 向量資料庫,以加快下一代資料平臺的發展。Milvus 資料庫是 LF AI & Data 基金會的畢業專案,能夠管理大量非結構化資料集,在新藥發現、推薦系統、聊天機器人等方面具有廣泛的應用。

相關文章