為什麼要先談複雜度?#

複雜度(complexity)是精實組織與個人最大的敵人。本書其餘章節都在處理「如何攻擊複雜度」,但首先你必須認得它在哪裡、長什麼樣子。

本書的複雜度定義:「由多個部分組成、難以分析、理解或解釋的整體。」這個定義刻意寬鬆,因為複雜度幾乎無所不在——股市、社交網路、政治、十萬行的 Windows 原始碼,都是複雜度的舞台。

對程式設計師而言,複雜度特別容易在以下幾個面向襲來:

  • 程式專案的生命週期(Project Life Cycle)
  • 軟體與演算法理論
  • 學習新事物的過程
  • 業務流程
  • 社交網路
  • 日常生活

新手第一個敵人:選擇癱瘓#

「我該怎麼開始?」是初學者最常問的問題,背後就是被以下複雜度淹沒:

  • 該選哪個程式語言?
  • 該投入哪個開源專案?
  • scikit-learn、NumPy、TensorFlow 該用哪個?
  • 該押注 Alexa app、手機 app、瀏覽器 web app,還是 VR?
  • 編輯器要 PyCharm、IDLE,還是 Atom?

解法:以專案為中心的學習#

不要先讀書,挑一個簡單的實際專案,用你現有的能力硬著頭皮做完。每完成一個專案,自然會逐步學到:

  • 如何選編輯器
  • 如何安裝語言環境
  • 如何讀取檔案輸入
  • 如何把資料存進變數
  • 如何把輸入轉換成想要的輸出

作者舉的例子:一位學生想做投資組合回測 dashboard,過程中接觸 Python Dash、學了必要的 HTML/CSS,一年內不僅產品上線、還加入 Dash 開發團隊並寫了書。做中學,比讀完所有書再開始有效得多。

專案生命週期中的複雜度#

依照 IEEE 軟體工程標準,一個專案會經歷六個階段(敏捷開發只是把這些階段反覆跑得更快):

Figure 1-1: 軟體開發生命週期(SDLC)的六個階段。

  • Planning(規劃):決定要做哪些功能。需考量成本、風險、價值、行銷、可維護性、合規 ⋯⋯ 這是最該套用 80/20 思維的階段。
  • Defining(定義):把規劃轉成正式需求規格。「知識的詛咒(curse of knowledge)」會讓你高估別人能聽懂多少。
  • Designing(設計):規劃架構、模組與介面。設計者必須深諳各工具優缺點,例如:方便的函式庫往往執行慢;自製函式庫雖難但更快。
  • Building(建造):寫程式。即使前期準備充分,仍會出現外部 bug、效能問題、資料汙染、人為錯誤——一個拼字錯誤就可能毀掉整個產品。
  • Testing(測試):許多人主張測試驅動開發(test-driven development, TDD),先寫測試再實作。
  • Deployment(部署):發佈、行銷、修 bug、跨平台支援、長期維護。

測試的範例#

書中以一個計算 RGB 平均值的函式示範:

def average_rgb(pixels):
    r = [x[0] for x in pixels]
    g = [x[1] for x in pixels]
    b = [x[2] for x in pixels]
    n = len(r)
    return (sum(r)/n, sum(g)/n, sum(b)/n)

看似簡單,但實務上要問:

  • pixel tuple 只有兩個元素怎麼辦?
  • 出現非整數型別怎麼辦?
  • 浮點誤差會不會影響結果?

單元測試(unit test)就是在不同輸入下檢查每個函式的正確性。然而即使單元測試全過,整體互動測試與真實環境(例如自駕車跑數萬英里)還可能暴露新問題。複雜度在每一層都會反撲。

軟體與演算法理論的複雜度#

演算法複雜度(Algorithmic Complexity)#

研究演算法在不同輸入規模下的資源消耗。同一個排序問題:

  • Bubble sort:複雜度最差,資料量一大時執行時間爆炸。
  • Quicksort、Timsort:漸進複雜度相同,但 Timsort 常數較小,因此 Python 內建 sorted() 用 Timsort。

Figure 1-2: 兩種排序演算法的複雜度差異示意圖。

Figure 1-3: Bubble sort、Quicksort、Timsort 三者執行時間比較。

Figure 1-4: 拉近觀察 Quicksort 與 Timsort——後者隨資料量增長仍維持較佳效能。

演算法研究是人類最有價值的智慧資產之一——「我們站在巨人肩膀上(we stand on the shoulders of giants)」,用更少的資源解決同樣的問題。

圈複雜度(Cyclomatic Complexity)#

由 Thomas McCabe 於 1976 年提出,計算程式中線性獨立路徑的數量。每多一個 if,圈複雜度加一。

  • 它是「認知複雜度(cognitive complexity)」的良好替代指標——也就是程式有多難理解。
  • 但圈複雜度忽略了多層巢狀迴圈帶來的負擔,因此後來有 NPath complexity 等改良指標。

Figure 1-5: 兩段 Python 程式的圈複雜度對比。

學習中的複雜度:知識圖譜#

事實之間並非彼此獨立。Google 在 2012 年推出的**知識圖譜(Knowledge Graph)**就是把事實串成關聯網絡:

  • 圖靈(Alan Turing)→ 出生年 1912
  • 圖靈 → 博士指導教授 → 邱奇(Alonzo Church)
  • 邱奇 → 研究領域 → 電腦科學

可以推論出「圖靈博士指導教授的研究領域是什麼?」這類問題的答案。

Figure 1-6: 知識圖譜的節點與關係表示方式。

任何專業領域都只是這個巨大圖譜的一小塊。你想做交易機器人嗎?需要會 Python、NumPy、scikit-learn、ccxt、TensorFlow、Flask、機器學習、API、分散式系統、資料庫、交易策略、市場理論 ⋯⋯。

學得越多,越覺得自己不夠。 結果還沒開始就放棄。對抗這種焦慮的解方就是專注、簡化、降階、減法、極簡主義(minimalism)。

流程的複雜度#

流程的複雜度由動作數量、參與者、分支數決定。Uber 之前,從 A 到 B 要查電話、比費率、準備不同付款方式;Uber 把這些整合成一個 app,把流程徹底簡化。

Figure 1-7: 兩種流程對比——一人開發 vs. 團隊開發。

公司一複雜,創新就推不動,資源會在冗餘步驟中浪費。管理者常以「再加一個流程」應對問題,反而陷入惡性循環。極簡主義是正解:你不太可能把流程簡化過頭。

日常生活:千刀萬剮#

電腦科學家 Cal Newport 在《Deep Work》中指出:

  • 對深度思考的需求增加(程式、研究、醫學、寫作)
  • 但用於深度思考的時間因通訊與娛樂裝置普及而萎縮

即時滿足 vs. 延後滿足#

  • 深度工作帶來延後滿足(delayed gratification)——花幾週寫的程式終於跑起來。
  • 但人腦渴望即時滿足:滑訊息、看 Netflix、隨意閒聊。
  • 結果:你的專注力死於千刀萬剮(death by a thousand cuts)。

對症下藥的不是「自制力管理」,而是從根源動刀

  • 直接刪除社群 app,而不是試圖控制使用時間
  • 減少同時進行的專案數,而不是工作更久
  • 深耕一個程式語言,而不是在多個語言間切換

結論#

複雜度傷害生產力,會吞噬你最珍貴的資源——時間。人生終點時,你不會用「回了多少封 email、玩了幾小時電玩」來衡量人生意義。

學會駕馭複雜度、保持簡潔,就是建立競爭優勢的方式。下一章將進入第一個強力工具:80/20 原則——專注於關鍵少數,忽略瑣碎多數。