關於程式碼可讀性之簡報介紹 vol. 1 : “導入與原則” 篇

前言

大家好,我是通訊 App「LINE」Android 團隊的石川,前陣子發表了關於程式碼可讀性的簡介 (https://speakerdeck.com/munetoshi/code-readability)。

今後我將不定期在此部落格上,逐一連載關於此簡報的短篇解說,本次要先解說此簡報的概要,以及第一個章節 “導入與原則”。

關於本簡報

本簡報為提升程式碼可讀性用的方法總整理,由以下 8 個章節構成:

  • 導入與原則:高可讀性程式碼的重要性、程式開發原則
  • 命名:名稱呈現的內容、文法與用語的選擇
  • 註解:文件、行內註解
  • 狀態:管理狀態變動、刪除無用狀態
  • 程序:函數與方法流程的明示化與分離
  • 依賴關係 I:兩種類型間的依賴關係強度
  • 依賴關係 II:依賴關係的方向、重複、明示性
  • 審查:委託審查程式碼、審查註解

將所有章節整合後,總份量竟然超過 700 頁。而製作如此龐大簡介的原因,在於我隸屬的通訊 App「LINE」之 Android 開發團隊,希望將其打造成 “更能夠持續研發之環境”。

LINE 自 2011 年開始研發以來,為了提供更好的使用者體驗,不斷開發與改善功能至今。隨著專案不斷成長,Android 用戶的程式碼,連同模組在內,也膨脹到 150 萬行,團隊成員更是擴充至超過 50 人以上的規模。為了讓如此龐大的專案得以繼續進行研發,必須維持程式碼的可讀性、持續償還技術債的環境。

但光靠某位特定人士持續重構程式碼,無法實現這樣子的環境。在龐大的專案中,必須將 ”維持程式碼的可讀性、持續償還技術債” 這個工作本身,變得更加規模化 (Scalable) 。也就是所有參與研發的人員,皆須意識到可讀性與技術債。

撰寫高可讀性的程式碼一事,大多比單純實作功能的作業更加困難。因為其要求瞭解何種程式碼較容易閱讀的知識,以及有效率地重組程式結構之技術。因此為了讓所有人皆能共享這些知識與技術,我們把審查程式碼時修正的經典範例,以及在重構程式碼的過程中發現之低可讀性程式碼特徵,匯整成此簡報。

第一章:導入與原則

在本章中將介紹需要高可讀性程式碼的原因,以及所需的程式開發原則。

需要高可讀性程式碼的原因

如同前一節所述,為了能持續開發專案,必須將程式碼的可讀性維持在高水準。例如:多思考 5 分鐘,調整變數名稱,撰寫合適的註解,或許就能避免其他研發人員 (也包含未來的自己) 煩惱 1 個小時。重要的不是短時間內個人的生產力,而是在產品整個生命週期內,團隊整體的生產力。反過來說,若該程式碼的壽命極短,且使用的範圍受到限定時,考慮可讀性的必要性就非常薄弱。例如:試驗性質的實作,或是只執行一次的腳本語言 (Script),就符合這種情況。

在判斷是否應重視可讀性的損益分歧點時,其中一個判斷標準為,整體時間中用來 “閱讀程式碼並向他人解說的時間”,以及 “撰寫程式碼的時間” 兩者中,何者的時間較長。但此時須注意一個重點,那就是如果在撰寫程式碼的過程中曾參照其他程式碼,以及須實施程式碼審查等情況,亦須將其計入 “閱讀程式碼的時間” 內。

程式開發原則

要撰寫強健且可讀性高的程式碼時,遵循程式開發原則的作法大多有效。但未考慮實際狀況而套用過多的原則時,有時反而會造成程式碼的可讀性下降。例如:在第六章中,就解說了套用過多 command-query separation 時的不良影響。而在本章中,我們篩選出 5 個不僅在考慮程式碼可讀性時特別重要,而且就算套用過多時,反效果也不至於太糟糕的 5 個程式開發原則。

The boy scout rule

此規則是由 Robert C. Martin 將童軍運動創始者 Robert Baden-Powell 的格言,套用在軟體的開發中。雖然原意與前後文的邏輯性,與 Robert C. Martin 的主張不同,但主張的精神卻是相同,那就是變更程式碼時,必須讓變更後的程式碼變得更加簡潔。遵守此原則的範例,例如:在變更某個程式碼時,順便把名稱改成更加簡明易懂,或是增加註解與測試,刪除目前未使用的程式碼等。這個原則也可解釋為 “不要把已經夠髒的程式碼搞得更髒”。例如:對龐大的 switch 新增條件 (Case) 的作法,就違反了此原則,在新增條件前,必須先使用策略模式等方法重構程式碼。

YAGNI 原則 (You Aren’t Gonna Need It)

此原則主張功能與程式碼唯有在有必要時才進行實作,不該因為未來可能需要的這種理由而實作。不需要被撰寫的程式碼:像是目前未使用的 utility functions、頂多只有一個實作的 abstract layer、和只需要輸入固定值的參數等。這種為了將來而預作準備的功能及程式碼,不僅實際未使用的可能性極高,更會在因為其他原因而需變更程式碼時造成困擾。當打算新增條件分支時,若存在某些無用的分支,可以預想得到,最終的條件分支將會變得更加複雜。由這點也能看出,希望能更加彈性因應未來的變更時,避免實作無用的功能,將設計單純化這點極為重要。

但此原則是以程式碼能輕易變更為前提。至於程式碼難以變更的範例,則有對外公開的 API 與函式庫, 以及不易遷移的資料庫 schema 等。在這種情況下,可能會被要求以考慮未來使用情況的方式,進行設計與實作。

KISS 原則 (Keep It Simple Stupid)

此原則的原意是應將設計單純化,以簡化設備維護。將此涵義套用於軟體開發時,則變為 “將功能與規格單純化”,以及 “將實作手法單純化”。尤其是關於實作手法,須注意避免將目的與手段搞錯。所謂程式開發原則和規範、程式碼的美觀與一致性、設計方式等,應該是提升程式碼可讀性與強健性用的手段,但將其本身作為目的時,反而會傷害程式碼的可讀性與強健性。例如:在 reactive programming 設計中,可將包含常數值的所有數值設為 observable,但這種作法雖然在一致性的觀點上 “美化”程式碼,但相對地卻降低了程式碼的可讀性與提高了除錯的難度。

單一職責原則(Single responsibility principle)

此原則被稱為 SOLID 的物件導向用原則之一,其涵義可翻譯成 ”每個類別被更改的原因,應該只有一個”。例如:以一個類別來包含使用者相關資訊 (名稱、郵件地址) 及使用者集合 (已註冊的使用者清單) 時,變更此類別的理由將變為 2 個 (希望變更使用者資訊項目的情況,以及希望變更使用者註冊方法的情況)。此時將變得無法保證某個變更不會影響其他功能,因此一個類別的責任與關注的範圍,應該縮小為 1 個。

過早最佳化是萬惡之源 (Premature optimization is the root of all evil)

此原則主張不應該進行效果不大的最佳化處理。造成程式碼變得更加複雜的最佳化處理,只應在效果極大時執行,因此性能分析與評估極為重要。但能讓程式碼的可讀性提高的最佳化應不在此限。例如:將使用迴圈,搜尋 ArrayList 元素的程式碼,代換成 HashMap 時,或許會造成記憶體的使用量增加,但不僅能讓計算量減少,程式碼也會變得更加簡潔。

結語

本次作為 “導入與原則” 篇,介紹了本簡介的整體與第一章的內容。

下回將作為 “命名與註解” 篇,介紹第二章與第三章的內容。