《重構英文》:第 8 個月
一句話總結
也許不是每個人都想加入我的焦點團體。
亮點
- 我發現,並非每位購買《重構英文》搶先體驗版的讀者都想針對草稿提供回饋。
- 我釐清了時間都花在哪裡,並思考減少時間耗損的方法。
- 我花了 10 小時從零重寫一個原本花了我 300 小時打造的網頁應用程式。
- 我持續用 Gleam 學習函數式程式設計,但我可能有點作弊。
目標成績
每個月月初,我都會宣告想完成的事。以下是我達成目標的情況:
與至少 10 位未曾交流過的讀者對談
- 結果:寄信給七位讀者,收到三封回覆,進行了一次線上對談
- 評分:C
在坐下來細數之前,我以為回覆率更差,但其實有不少讀者都有回覆。問題在於我主動聯繫的人還不夠多。
我可能在每封信上花了太多時間,想寫出獨特且明顯不是 AI 產生的內容,結果卻花了一小時去讀對方的部落格而陷入其中。
清空行銷點子的待辦清單
- 結果:完成了預定事項中約 70%
- 評分:C
這些任務大多已完成,不過一年前錄製的一場訪談我還沒發布,我希望能把它完成。
發布 Refactoring English(《重構英文》)的新章節
- 結果:發布了「高效電子郵件的冷門技巧」
- 評分:A
我很滿意這一章的成果,但我知道在社群媒體上分享會是一場賭注。結果在 Lobsters 上獲得正面迴響,但在 Hacker News 上沒有引起關注。
《重構英文》數據
| 指標 | 2025 年 6 月 | 2025 年 7 月 | 變化 |
|---|---|---|---|
| 不重複訪客 | 6,574 | 8,061 | +1,487 (+23%) |
| 預購收入 | $597.24 | $800.04 | +$202.80 (+34%) |
| 顧問收入 | $242.45 | $0.00 | -$242.45 (-100%) |
| 贊助收入 | $48.25 | $48.25 | $0.00 (0%) |
| 總收入 | $887.94 | $848.29 | -$39.65 (-4%) |
網站造訪次數上升,這是好現象,因為我並沒有發表什麼熱門新文章。我把它視為正向訊號,代表光靠現有的試閱摘錄就能維持健康的流量。
總收入略微下滑,但只是因為付費顧問案從一件變成零件,所以變化不大。我更開心的是看到預購收入比 6 月成長了 34%。
如果大家只是喜歡「買書的感覺」呢?
當我主動聯繫讀者並與他們進行視訊通話時,有一句回饋我一再聽到:「我還沒開始讀。」
這句話來自幾天前才購買的人,也來自已經擁有閱讀權限好幾個月的人。
我最大的擔憂是,這本書是「維他命而非止痛藥」。人們把好的寫作視為重要但不緊急的事。他們可能會在即將面試前去讀一本叫「如何通過下一次程式面試」的書,但增進寫作這種事卻很容易被無限期延後。
我在動筆前就預售這本書,部分動機是想看看是否有足夠多人願意付費。結果是有的,而且人們也持續購買中,但我擔心這或許不如我想的那麼具有預測性。
如果我的書屬於那種大家買了只是為了獲得「有在投資寫作」的感覺,卻不會真的花時間去做的東西呢?會不會就像 Planet Fitness,那家據說靠著讓人辦了會員卻從不去健身房來賺錢的知名連鎖健身房?

會不會我正在寫的是一本「書籍界的 Planet Fitness」?
照片由 Mike Mozart(麥克·莫札特)提供,依 CC-BY-2.0 授權使用
就算我能成為書籍界的 Planet Fitness,那也無法長久。我的書要能財務上可行,必須仰賴讀過並喜歡這本書的人口耳相傳。我希望受歡迎的部落客會引用我的書,說它是幫助他們的資源。當有開發者說想增進寫作能力時,我希望我的書就是大家會直覺推薦的那本。
也許顧客根本不想加入我的焦點團體
我預售這本書的另一個原因是,我預期預購的讀者會特別熱衷於在寫作過程中提供回饋。
從與同為顧客的朋友們交談中得知,他們大多表示預購是為了支持這個計畫,並想在書完成後閱讀,但不一定想參與焦點團體或對草稿提供回饋。他們只想讀完稿的版本,這完全可以理解。
我應該繼續一對一地主動聯繫
透過一對一聯繫顧客並安排線上對談,我發現有些讀者非常熱情且樂於提供回饋。我只找到少數幾位,但他們給了我非常棒的建議。
我發現最有效找到熱情讀者的方法就是一封一封親自寄信,所以我會繼續這麼做。
我的時間都去哪了?
每年 都會 有那麼一次,我回顧上個月完成了什麼,然後想:「等等,為什麼上個月只做了這些?我到底把時間花在哪了?」
七月就是這樣的一個月。我照常全職工作,但回顧這個月時卻想:「我怎麼只完成了一個新章節?」
所以,讓我回頭想想,當我沒在處理每月目標時,時間都花到哪去了。
在章節上過度投入
幾個月前,我意識到自己花太多時間琢磨字句,而不是在章節達到 80 到 90 分時就交給讀者。我的解決辦法是為每章設定時間上限,並在預算時間內盡量完成。
七月寫的電子郵件那一章,我的預算是五小時,但實際花了 17.5 小時。部分是故意的,因為我在設定目標後才意識到,這一章很適合作為網站上的獨立摘錄。要先寫成摘錄再整合回書中,會花更久時間。
但我現在寫的這一章也在做同樣的事。我已經花了 6.5 小時,超過了 6 小時的預算,而且大概還需要至少 3 小時才能寫到讓我敢分享給讀者的程度。我想這既是我修得太細的問題,也是我為這本書最重要、也是第一章的預算設得太低的問題。
解決方案:為會作為公開摘錄的章節預留額外時間,並將寫作時間限制在既定範圍內。
額外的部落格文章
我喜歡在剛學到新東西後就寫成部落格文章記錄下來,但就算每週花 20 小時寫部落格,也寫不完我想寫的所有文章。
有些部落格文章會吸引可能對我的書有興趣的讀者,有些則可能不會。我必須謹慎決定要在「純粹好玩」的部落格文章上投入多少時間。
上個月,我發布了一篇新部落格文章,「將 ZFS 儲存池從 RAIDZ1 遷移到 RAIDZ2」。我知道它對書沒幫助,但也以為只要花幾個小時就能寫完,而且我想解釋一件我覺得還沒人解釋清楚的事。
實際上,這篇 ZFS 文章花了七小時才寫完,所以對於「純粹好玩」的文章來說,投入的時間比合理的還多,尤其是在這個月沒有更能吸引大眾的《重構英文》章節可分享的情況下。
解決方案:對非書籍相關的部落格文章更挑剔,並優先撰寫能吸引同樣會讀我書的讀者的文章。
不良的社群媒體習慣
每當感到無聊或對困難的寫作或軟體工作缺乏動力時,我就會去刷社群媒體。我告訴自己只是快速看一下就回去工作,結果卻被一篇文章吸住。如果我留了言,就會一直強迫性地查看有沒有回覆。
如果我的文章在 Hacker News 或 reddit 上爆紅,我通常會留一天來回覆留言。我的 ZFS 文章沒有爆紅,但我還是花了一整天回覆留言。
有段時間我曾用一個叫 LeechBlockNG 的瀏覽器擴充功能來節制不良的社群媒體習慣,但它似乎會洩漏記憶體並拖慢整個瀏覽器,所以我就停用了。我現在又試著啟用,暫時還沒發現記憶體洩漏,也許這次能奏效。
解決方案:再試試生產力相關的瀏覽器擴充功能,為那些浪費時間的網站增加阻力。
從睡眠中斷中恢復
我的幼兒睡不好,這表示我和太太也睡不好。
睡眠中斷本身可能還算是小問題。更嚴重的是,我會拿睡眠中斷當作偷懶的藉口,例如跳過寫作時段或去刷社群媒體。我會想:「我昨晚睡得那麼差,今天不該那麼拚。」但實際上,我還是能發揮平常 80 到 90% 的效率,只是拿睡不好當作偷懶的藉口。
解決方案:別再把睡眠中斷當作偷懶的藉口。
拖延付費編輯工作
我一直有在做編輯工作,作為《重構英文》提供的服務之一。
我喜歡編輯別人的部落格文章,但也覺得這很耗心力。編輯自己的文章就已經很難了,幫別人編輯更難。寫自己的東西時,我可以憑感覺修改,不必解釋為何要這樣改。但當我給其他部落客編輯建議時,我必須清楚說明我在草稿中看到的弱點,以及為何我認為我的建議更好。
我發現自己會拖延編輯工作,就算一天內能完成,也會拖上好幾天。而當我拖延編輯工作時,也會連帶拖延自己的寫作,因為我想先以客戶的工作為優先。
解決方案:意識到拖延編輯會耗掉大量時間,並盡早著手處理。
副專案
用 10 小時以靜態網站產生器重寫原本花 300 小時的 Vue 應用程式
在 2019 年,我曾嘗試打造一個名為 What Got Done 的事業。那是一個讓團隊成員彼此分享每週工作摘要的應用程式。

What Got Done 是一個讓人們分享每週工作進度的網站。
當我在 Google 任職時,他們內部有一個做同樣事情的工具叫 Snippets,我很喜歡,即使離開 Google 後仍持續撰寫每週更新,即使當時我是一個人工作。
我始終找不到 What Got Done 的付費用戶,所以我把它開源,並在過去六年中當作興趣專案來維護。
我最初用 Vue、Firestore 和 AppEngine 打造 What Got Done,現在我非常不喜歡這些技術。我花了很長時間將 Firestore 替換為 SQLite、將 AppEngine 替換為 fly.io,但 Vue 仍留了下來,讓開發體驗很不愉快。
每週我都會在 What Got Done 上發布更新,並想著我更喜歡用 VS Code 和 Hugo 寫部落格的工作流程。所以,有個週末我就直接把 What Got Done 重寫成一個簡單的靜態網站,用 Hugo 產生,現在託管於 weeks.mtlynch.io。

我將 What Got Done 重寫為一個可用 Hugo 產生的靜態網站。
所以,六年來,我大概花了約 300 小時來實作與維護這個由 Go + Vue + SQLite + fly.io 組成的 What Got Done 應用程式。把它重寫為由簡單 Markdown 檔案與 Hugo 組成的靜態網站,只花了 10 小時。
因為新版本是只為自己做的應用程式,我可以加入個人化功能,例如從我的 git 提交紀錄自動預先填入每週更新。而且當然,因為它只是一個有版本控制的靜態網站,而不是一個前端、後端與資料庫各有不同技術堆疊的完整網頁應用程式,所以在託管、維護和備份上都簡單且便宜得多,可說是天壤之別。
將 What Got Done 退役
我不想永遠維護 What Got Done,尤其現在我自己已經不再使用它。
即使 What Got Done 只有少數活躍使用者,我也不想拋棄那些開始使用我提供服務的人,所以我試著讓 What Got Done 的退場體驗盡量友善:
- 我在網站上宣布 What Got Done 將於年底停止營運。
- 我新增了一個功能,讓使用者能以 Markdown 格式匯出他們的貼文。
- 反正我本來就需要這麼做才能把自己的資料移植到 Hugo,所以我想把這個功能直接做進網頁應用程式裡,讓任何使用者都能使用。
- 我新增了一項功能,讓使用者可以在 What Got Done 關閉後設定轉址。
- 例如,我已將我的個人頁面
whatgotdone.com/michael設定為永久重新導向至 weeks.mtlynch.io。
- 例如,我已將我的個人頁面
我在 Gleam 上開發 AIM 紀錄解析器的進度
我仍在透過改造一個解析我高中與大學時期舊 AIM 紀錄的程式來學習 Gleam 程式語言。最基本的紀錄看起來像這樣:
Session Start (AIM - DumbAIMScreenName:Jane): Mon Sep 12 18:44:17 2005
[18:44] Jane: hi
[18:55] Me: hey whats up
Session Close (Jane): Mon Sep 12 18:56:02 2005
解析時間戳
在六月,我的解析器已能在基本層面上運作,能將上述紀錄擷取為寄件者與訊息內容,像這樣:
[
Message(sender: "Jane", body: "hi"),
Message(sender: "Me", body: "hey whats up"),
]七月我有了進展,現在解析器也能理解時間戳,這有點棘手,因為它必須將工作階段中繼資料中的日期與訊息中簡單的 HH:MM 資訊結合。所以,我的紀錄解析器現在能將上述紀錄轉換為這樣:
[
Message(
timestamp: must_parse_rfc3339("2005-09-12T18:44:00-04:00"),
sender: "Jane",
body: "hi",
),
Message(
timestamp: must_parse_rfc3339("2005-09-12T18:55:00-04:00"),
sender: "Me",
body: "hey whats up",
),
]將詞法分析與語法分析合併為單一步驟
我也將解析器簡化為單次掃描,而非分開進行詞法分析與語法分析。
我原本以為把紀錄先切成詞彙(token)清單再進行解析會更正規、更優雅。所以,解析器不是直接看到像 Session Start (AIM - DumbAIMScreenName:Jane): Mon Sep 12 18:44:17 2005 這樣的一行就進行解析,而是希望詞法分析器先將它轉成一連串詞彙,像這樣:
[
SessionStart,
Word("(DumbAIMScreenName:Jane)"),
ColonSpace,
Word("Mon"),
Word("Sep"),
...
]但這意味著我需要一個秘密的前置步驟,將字串切成詞彙分析器能辨識的子字串,像是 ["Session", " ", "Start"],而且我必須自己實作字串分割邏輯,因為 Gleam 內建的函式庫沒有辦法在依子字串分割字串時同時保留分隔符號。例如,如果我先依換行符號再依空白分割,就會得到一串字串清單,但會失去分隔符號究竟是空白還是換行的資訊。
感覺上我其實把輸入解析了三次:一次是用自訂的字串分割、一次是詞法分析,以及一次是實際的語法分析。我原本以為這是因為我對函數式語言或文字解析器不夠了解,之後會找到更優雅的詞法分析與語法分析方法。
我的困惑成了我終於下手購買紙本Crafting Interpreters(《打造直譯器》)的藉口,這是我見過設計最精美的軟體書籍。

我的 Gleam 專案終於讓我有藉口購買紙本《打造直譯器》。
在閱讀該書的詞法分析章節後,我做出結論:我的 AIM 紀錄結構不夠規律,不適合做詞法分析。我嘗試將所有東西合併為一個逐字元讀取的解析器,感覺這樣更簡單。
逐字元解析的缺點是 Gleam 的樣式匹配看起來醜陋許多。在舊的實作中,我可以這樣尋找字串樣式:
case contents {
["Session", "Start", ..rest] -> tokenize_list(rest, [SessionStart, ..acc])
["Session", "Close", ..rest] -> tokenize_list(rest, [SessionClose, ..acc])
而現在則是更長、更雜亂的樣式匹配:
case state.remaining_graphemes {
[
"S",
"e",
"s",
"s",
"i",
"o",
"n",
" ",
"S",
"t",
"a",
"r",
"t",
...
我是不是硬把類別塞進 Gleam?
隨著解析器的進展,我發現自己一再撰寫簽名相同的函式:
fn parse_tokens_with_messages(
tokens: List(Token),
messages: List(Message),
) -> List(Message) {
我的一堆函式接受相同的參數並回傳相同的值,而且隨著程式碼增加,參數與回傳值的清單也越來越長。
所以,我建立了一個 ParseState 型別並改為傳遞它:
type ParseState {
ParseState(
last_timestamp: timestamp.Timestamp,
messages: List(Message),
remaining_graphemes: List(String),
)
}
fn parse_graphemes(state: ParseState) -> ParseState {
但那感覺就像是偷偷把物件導向的類別塞進 Gleam。因為如果這是 Go,程式碼會像這樣:
type Parser struct {
LastTimestamp time.Time
Messages []Message
RemainingGraphemes []rune
}
func (p Parser) Parse() {
函數式程式設計的達人們:我這樣算是作弊嗎?還是說這就是在函數式語言中傳遞狀態的正確方式?
總結
完成了什麼?
- 發布了「高效電子郵件的冷門技巧」並將擴充版寄給搶先體驗的讀者。
- 將《重構英文》中僅限網頁的內容全數遷移至電子書中。
- 發布了部落格文章「將 ZFS 儲存池從 RAIDZ1 遷移到 RAIDZ2」。
- 為 ScreenJournal 建立了更好的密碼重設流程。
- 為 PicoShare 新增了訪客用檔案到期選項。
- 以免費編輯一篇即將發布的部落格文章,換取將回饋公開作為我編輯服務的行銷素材。
- 為 What Got Done 制定了退役計畫,並將我的資料遷移至 weeks.mtlynch.io。
學到的教訓
- 並非每位《重構英文》的搶先讀者都想提供回饋,這沒關係。我可以持續主動聯繫讀者,找到少數願意更積極參與的人。
- 檢視自己浪費時間的方式並思考緩解方法,總是有幫助的。
下個月的目標
- 親自寫信給 20 位未曾交流過的讀者。
- 發布《重構英文》的新章節。
- 完成剩餘的行銷任務。
尋求協助
如果你是想增進寫作能力的開發者,或你認識這樣的人,歡迎與我聯繫。
隨機一篇部落格