昨天的四個問句,問的都是函式庫。這一篇,我想把目光轉回自己,看看一路問到這裡,我讀程式碼的方式有沒有改變。
我想從一個數字開始。它在探索瀏覽器 API 時就出現過,印在我自己量出來的表格裡。
Day 18 探索瀏覽器 API 時,我讓三份 useLocalStorage 的臨摹,各加一個 sessionStorage 的版本,再執行計數測試,算出各自新增幾行程式碼:
| 實作 | 新寫的程式碼行 | 其中跟 localStorage 版相同 | 其中要改的 | 原封不動重用的 |
|---|---|---|---|---|
| react-use | 46 | 40 | 6 | 0 |
| usehooks-ts | 88 | 73 | 15 | 0 |
| @react-hookz/web | 20 | 13 | 7 | 162 |
隔天,在 Day 19,我把這張表濃縮成一句話:
薄封裝在你需要第二個變體時複製貼上,深模組只多 37 行。
這裡的 37 行,指的是原庫的門面,包含空行與註解;表格裡的 20 行,則是臨摹版本只計程式碼的結果。
這句話的前後兩半,是一組對比:一邊得複製,一邊不必。
為了寫這一篇,我回頭重跑了這張表,數字沒有變。只是這一次,我停在了 @react-hookz/web 那一列。
新寫的 20 行裡,有 13 行跟 localStorage 版一字不差。
那 13 行並不是巧合。替 @react-hookz/web 加 sessionStorage 版的時候,實際做的事是:打開 localStorage 版的門面,整份複製,再把 local 換成 session。
改過的 7 行,都是同一種改法。以其中三處為例:
-let IS_LOCAL_STORAGE_AVAILABLE: boolean;
+let IS_SESSION_STORAGE_AVAILABLE: boolean;
-export const useLocalStorageValue: <T>(
+export const useSessionStorageValue: <T>(
- ? (key, options) => useStorageValue(localStorage, key, options)
+ ? (key, options) => useStorageValue(sessionStorage, key, options)
usehooks-ts 那一列要改的 15 行,做的也是同一件事:函式與型別的名稱、自訂事件的名稱、警告訊息,以及 window.localStorage,都換成了 session 的版本。
所以兩邊都複製了,要改的地方也是同一種。
差別在於,複製過來的那一份裡有什麼。
usehooks-ts 複製過來的 73 行,帶著讀取、寫入、廣播與 handleStorageChange。之後如果要修這些邏輯,就得回頭檢查兩個檔案,看看是否都需要改。
@react-hookz/web 的 13 行,連同改過的 7 行,做的只是探測 sessionStorage 能不能用,再把它交給 useStorageValue。登記簿、storage 事件的處理函式,以及 Day 18 那一行擋下刪除的守衛,仍然只寫在一個地方。
原庫也是這樣。@react-hookz/web 的兩個門面各 37 行,彼此的差別,同樣只在 local 與 session 的名字上。
「只多 37 行」的那 37 行,本身也是一份複本。
第二個變體仍然需要複製。深模組讓讀寫、同步與事件處理留在共用的核心裡,第二份門面只負責檢查並接上另一種 Storage。
表格是我量的,那句話是我寫的,兩者就放在同一頁上。我卻把「只需要複製一個門面」,寫成了彷彿「不必複製」的對比。
「只多 37 行」沒有說錯。多出來的程式碼確實少,共用的核心也確實承擔了大部分工作。只是這個句子的寫法,讓複製本身從後半句裡消失了。
讀到「37 行的門面」時,「門面」這兩個字替我補上了「不必複製」。
就像系列的第一天,previous 這個字替我補上了「上一次改變時的值」。那一次,程式碼沒有反駁我,是另一份實作放在旁邊,差異才顯現出來。
這一次,讓我停下來的是自己量出來的那張表。
那個 13,是寫出第二份之後才量得到的數字。原庫的兩個門面一直都在那裡,即使不動手,也有機會把它們並排,看出第二個門面同樣是一份複本。只是這一次,親手複製、量出表格,替我留下了一份之後還能回頭檢查的證據。
所以一路問到這裡,我並沒有因此不再替文字補上意思。比較明顯的改變,是我手上多了一些能回頭檢查自己的東西。
系列的第一天,我問:只讀原始碼,我們究竟會漏掉什麼?
那時候的答案是,那裡有一個決定。走到這裡,我想替它補上另一半:漏掉的,有時候是原作者做過的決定;有時候,是我在讀的時候,自己替那幾個字補上的意思。
把另一種寫法放在旁邊,有機會看見原作者做過的選擇,也有機會看見自己讀進去的意思。這一次,我直到親手寫出第二份,又在最後一天回頭看那張表,才發現自己曾經把「複製得少」讀成了「不必複製」。
寫在再造輪子之前,我借亞里斯多德的話說,模仿不該止於機械複製。最後讓我看見這個落差的,卻是一次再機械不過的複製:打開檔案,整份貼上,把 local 改成 session。
再造輪子之後,我手上留下來的,是一個 13,和一句被它修正的話。
原庫的兩個門面是
@react-hookz/web26.1.0 的src/useLocalStorageValue/index.ts與src/useSessionStorageValue/index.ts,各 37 行(含空行與註解),逐行比對後,不同的 8 行都是 local 與 session 的命名差異(含一行註解與一行警告訊息),查核於 2026-10-15。