昨天講了資料從哪裡來。今天講它被整理成什麼。
結果是一份大約 5 MB 的檔案,裡面有 1,183 張票卡,來自兩套不同的系統。
但這篇不是要講「我把資料存成 JSON」——那沒什麼好說的。
要講的是每一條事實上面掛了哪些欄位,以及為什麼。因為那份欄位清單,基本上就是前面十天所有教訓的總和。
拿掉真實內容之後,一條紀錄大概是這樣:
id T:6a82c54bcbdb8e46e07a9573
source trello
board 某某看板
key gUCZ1m0s
title [某人][某產品][前端] 某某欄位在某情況下沒有帶到
list [DEV] Code Review
status active
url https://…/c/gUCZ1m0s/…
summary 8/17 某次同步會的結論。某功能在某情況下……
pages []
keywords []
owners 某人, 某人
commentCount 2
month 2026-08
created 2026-08-17T08:24:43Z
lastActivity 2026-08-23T13:01:11Z
comments [{date, author, text}, …]
links [外部連結, …]
sourceRaw fetch/trello/(cards.json + actions/6a82c…)
看起來很平常。但其中有五個欄位是踩過坑之後才加上去的,那五個才是這篇的重點。
sourceRaw —— 這條東西是從哪個檔案來的這是整份結構裡我最在意的一欄。
它記的不是網址,是這條紀錄是從我本機的哪一個原始檔案、哪一段解析出來的。
為什麼需要這個?因為 Day 11 那五個追問:
你是怎麼認定的?這是最新的資訊嗎?有沒有其他票卡有附註?
那五個問題有一個前提——要能翻回去看原文。
如果一條事實只留下「結論」而沒有留下「它從哪一段長出來的」,那它就變成了一個沒辦法被質疑的東西。你只能選擇相信或不相信,不能查。
而 Day 17 我寫過,這正是我不走 fine-tune 的關鍵理由:焊進去的知識沒有來源,追問就失效了。
所以 sourceRaw 存在的意義只有一個:
任何一條事實,都必須能夠一路指回它的原文。
它不是為了 debug 方便。它是為了讓「不相信」這個動作有地方可去。
pages —— Day 9 那個缺掉的維度這一欄是空的。
我指的是上面那個例子裡,pages 是 []——那張卡沒有被判定屬於任何頁面。
而這一欄之所以存在,整個原因就是 Day 9 和 Day 10 那兩篇:
原本在 A 頁面的甲功能,被寫進了 B 頁面。
因為那張卡上,根本沒有「這是哪一頁」這個資訊。
所以我在資料結構裡硬生生開了這一欄。
而有意思的是:它常常是空的,而這正是它的價值。
如果我沒有這一欄,那個資訊的缺席是看不見的——資料讀起來完整、通順、每一句都成立(Day 10 那本「只記幾點沒記哪一天」的行事曆)。
有了這一欄之後,缺席變成了一個可以被數的東西:我可以直接問「有多少張卡沒有頁面歸屬?」然後得到一個數字。
而我後來真的去數了。在這篇之後又重建過幾輪的那份語料裡(單位是合併過的變更單元,不是原始票卡數):
787 個單元裡,有 381 個沒有任何頁面歸屬——48%。
將近一半。
我想強調這個數字本身沒有好壞:它不代表那些卡開得不好。它代表的是一件更具體的事——這份資料回答不了將近一半的「這是哪一頁」。
而在有這一欄之前,那件事是完全看不見的。我不會知道是一半,還是百分之五。
把「不知道」變成一個欄位,它才會從隱形變成可見。
這是我做這份結構時學到最重要的一件事。一個缺漏如果沒有位置可以放,它就會被當成不存在。
(而 keywords 和 gap 也是同樣的設計——前者是檢索用的抓手,後者記的是「這張卡自己講了什麼還沒做」。兩個都是寧可空著,也要有位置。)
comments —— 完整留著,不摘要每一則留言都留:日期、作者、原文。
這一條看起來理所當然,但它其實是一個取捨。留言是整份資料裡最肥的部分——1,183 張卡的留言加起來,佔掉這 5 MB 的絕大部分。
摘要一下不行嗎?省空間、也好讀。
不行,而理由是 Day 2 那篇:
留言區是理由的所在地。 卡片的標題和描述講的是「要做什麼」,而「為什麼這樣做、試過什麼、為什麼放棄另一個方案」全部在留言裡。
而摘要這個動作本身,就是在做判斷——決定哪些重要、哪些可以丟。
那個判斷如果在這個階段做,就是不可逆的。 被摘掉的東西再也回不來了,而我當下沒有辦法知道下個月誰會需要哪一句。
在建置的最前面那一段,能不壓縮就不壓縮。
壓縮要留到最後、留到我知道要回答什麼問題的時候再做。
lastActivity 和 month —— 讓時間可以被比Day 8 最嚴重的那個錯誤,是「卡片說還沒修,但東西早就修好了」——在時間上不可能成立的一句話。
而當時要抓出那個錯,我得自己去比日期。
所以這份結構裡,每一條都帶著自己的時間座標:什麼時候建立的、最後一次有動靜是什麼時候、歸在哪一個月。
這不是為了排序好看,是為了讓「哪一個是現在的說法」這個問題有辦法用算的。
Day 10 講過,兩個例子缺的維度不一樣:一個缺空間(哪一頁),一個缺時間(哪個版本是現在)。
pages 補的是前者,時間欄位補的是後者。
status 和 owners —— 誰還記得這件事最後這兩欄比較樸素,但它們是 Day 18 那篇的直接後果。
status 告訴我這件事現在是不是還活著。owners 記的是當時誰在上面。
而 owners 的用途,不是為了究責。是為了回答一個很現實的問題:
如果這一條有疑問,我現在還能去問誰?
Day 18 寫的那張已經流失的架構圖,教訓就在這裡:知識的存續,跟「還有沒有人記得」綁在一起。 那份資訊在系統裡有價值,前提是還有一個人可以確認它。
所以這一欄其實是一個保存期限的提示。當上面的名字都不在了,那條紀錄就從「可以查證的」變成「只能相信的」。
除了票卡本身,這份檔案還帶著三張索引表:
| 索引 | 大小 | 做什麼 |
|---|---|---|
| 頁面 | 52 個 | 從一個頁面,找到所有跟它有關的卡 |
| 月份 | 4 個 | 從一個時間範圍,找到那段期間動過的東西 |
| 關鍵字 | 12 組 | 從一個詞,找到相關的卡 |
它們存在的理由只有一個,就是 Day 16 那句話:「先不要全讀,需要時再查。」
要能「需要時再查」,就得先知道去哪裡找。這三張表就是那個「哪裡」。
而注意它們的規模——52、4、12。這不是什麼精密的向量索引,就是三張很土的對照表。
但它們夠用,而且它們不用花錢、不用模型、查詢時間是零。
寫到這裡我才意識到,可以換一個角度看這整份設計:
| 欄位 | 它擋的是哪一種錯 |
|---|---|
sourceRaw |
講了一句沒辦法查證的話(Day 11) |
pages |
屬性被放到錯的地方(Day 9、10) |
comments 完整保留 |
理由在壓縮的時候被丟掉(Day 2) |
| 時間欄位 | 拿舊的說法當成現在的(Day 8) |
owners |
沒有人可以確認了(Day 18) |
每一欄都對應一次我踩過的坑。
我一開始以為設計資料結構是一件工程的事——想清楚要存什麼,然後存下去。
現在我覺得它比較像在寫一份防禦工事:你不是在設計「要放什麼進去」,你是在設計「哪些錯誤要讓它沒有地方躲」。
這 5 MB 是完整的、可追溯的、有索引的。
而它有一個很致命的問題:它太大,而且它的單位是錯的。
一張票卡,不是一個適合被引用的單位。有些卡上面有五個決定,有些卡只有一句話。
明天要講的就是這件事——要把事實切成可以引用的顆粒,而最難的部分不是決定切多細。