程式開發、哲學與神學的意象圖


前言


有些夜晚,程式像一座看起來安靜的城市。畫面上沒有爆炸,CPU 也沒有衝到滿載,甚至連 log 都只是平靜地往下流。可是你知道事情不對。某個請求慢了一秒,某個欄位忽然是 null,某個本來應該亮起來的按鈕,像被誰悄悄拔掉了電源一樣,無聲地黑著。那種不對勁很輕,卻也很準,像深夜裡忽然吹進房間的一陣冷風,提醒你系統裡有某個角落正在失序。

我以前以為寫程式主要是在跟語法、框架和工具對話。寫久了才發現,不是。更多時候,我是在跟人的有限性對話。跟自己會忽略細節的眼睛對話,跟團隊會誤解彼此的語言對話,跟那種「我應該已經懂了吧」的自信對話。很多 Bug 並不是從 IDE 裡長出來的,它們常常更早出現在會議室、文件、想像與假設裡,只是最後剛好在程式碼上開花結果。

也因此,我越來越覺得,程式開發其實不只是工程,它同時也是一種認識論訓練。你怎麼知道自己真的知道?你憑什麼相信這個系統會照你想的那樣運作?當你面對複雜系統時,是什麼支撐你繼續追查,而不是把一切推給運氣?問題問到這裡,工程就慢慢走到科學;科學再往下挖,就會撞見哲學;而哲學再往更深處走,常常又會碰到神學。

這篇文章想做的,就是把這條路慢慢走一遍。不是把寫程式神祕化,也不是拿神學替代技術,而是誠實地承認:很多工程師在深夜按下 Debug 的那一刻,其實已經站在一條比自己以為的更長、也更古老的路上。




1. 程式開發最常遇到的問題是什麼?


如果只從表面看,程式開發最常遇到的問題好像是 Bug。但如果再往下看一層,我會說,真正最常遇到的問題其實是「理解與現實之間的落差」。需求是一張地圖,規格是一張地圖,程式碼是一張地圖,測試案例是一張地圖,而使用者真正活著的現場,又是另一張地圖。工程師的日常,很多時候不是在寫世界本身,而是在努力把好幾張版本不同、比例不同、甚至互相矛盾的地圖硬疊在一起。

這也是為什麼我們常常以為自己在修技術問題,最後卻發現修的是語意問題。你以為「盡快」是三秒內,產品覺得是一分鐘內,使用者覺得是他按下按鈕時就應該完成。你以為欄位不會空,因為新表單已經設成必填,但資料庫裡還躺著三年前那批沒有驗證的舊資料。你以為某服務一定先回來,因為在你的電腦上永遠如此;結果正式環境多了真實流量、多了延遲、多了競爭,多出來的那一秒,就夠把系統裡原本藏得很好的裂縫全部撬開。

很多錯誤真正的源頭,並不在拋出例外的那一行。NullReferenceException 很少是在那一刻才誕生的,它更像是一封很晚才送到你桌上的通知書。真正的事情,可能早在資料建模、命名方式、團隊溝通、部署假設、甚至某次偷懶沒補 migration 的下午就已經開始了。到最後它只是選了一個最戲劇化的時間點,在螢幕上跟你見面而已。

所以如果問我,程式開發最常遇到的問題是什麼,我的答案會很簡單也很不浪漫:不是框架更新,不是語法太多,也不是 AI 太聰明,而是人總以為自己已經理解了。很多系統問題,最後都會回到同一句話上面:我以為這裡不會有問題。




2. 沒有人一次就能寫出完美的程式碼(全然敗壞)


這裡如果借用神學語言,我很想用「全然敗壞」這個詞,但我想先說清楚,我不是要把人寫程式的失敗直接等同成教義定義。我更想借這個詞去描述一種工程現場非常真實的狀態:人的理解沒有哪一塊是完全不受限制的。不是只有新手會錯,資深工程師也會錯;不是只有邏輯會錯,連判斷優先順序、估計風險、以為自己掌握脈絡這些地方,也都會錯。

也正因如此,我們發明了各種方法去抵抗自己的有限,但這些方法再好,也沒有一個能把有限性徹底取消。它們比較像是拐杖,不是救贖。下面這張表,大概最能說明這件事:

方法 它試著保護什麼 它仍然保護不了什麼
TDD 先把需求轉成可驗證行為,降低隨手亂寫的風險 你想不到的情境,它也測不到;錯誤的假設一樣能被漂亮地測綠
BDD 讓團隊用語言描述行為,減少彼此誤解 自然語言本身就含糊,同一句話常常能被三個人讀成四個版本
DDD 尊重領域知識,讓模型更貼近真實商業世界 真實世界不會永遠待在 bounded context 裡,邊界總會溢出、打架、變形
SDD 讓規格先行,避免實作失控 這兩年在 AI 輔助開發裡特別被強調,但規格不是世界本身,規格也會過時、會漏、會帶著撰寫者的盲點

TDD 很像在黑夜裡拿著手電筒走路。它當然有用,能幫你看見腳邊的坑、提早避開低級失誤。但手電筒的光束再穩,也只照得到你願意照的地方。你沒有想到那面牆後面還有一個洞,測試就不會替你想到。更殘酷的是,如果你一開始對需求的理解就歪了,TDD 甚至能很有效率地幫你把錯的東西寫得很漂亮,還附上完整測試證明它一路都沒出錯。

BDD 也一樣。它很像一群人圍著桌子描述同一場夢,大家都以為自己說的是同一件事。問題是,語言不是透明玻璃,它是有霧的。你說「使用者登入後應該很快看到首頁」,這句話裡的「很快」就足夠讓前端、產品、SRE 和後端各自活在不同宇宙。BDD 讓溝通變好,但它不能保證語言本身沒有陰影。

DDD 和 SDD 更讓我覺得工程很像人生。DDD 教我們尊重領域、命名、脈絡、邊界,這些都非常重要。只是現實世界不像白板那麼乾淨。真正的商業流動、組織權力、歷史包袱、跨系統共享欄位,常常會像暴雨一樣把你以為很清楚的界線沖成一片泥地。至於 SDD,則比較像這一兩年因為 AI 輔助開發而被重新強調的方法。它提醒人先把規格寫清楚,再讓實作跟著走,這個方向本身沒有錯,也確實能減少 AI 一路補完卻越跑越偏的風險。但它的限制仍然沒有消失。規格畢竟是紙上的世界,不是現場的世界;它寫下來的那一刻,就已經開始老化。

所以我的結論不是「這些方法都沒用」,恰恰相反,它們非常有用。我只是拒絕把它們當成工程界的贖罪券。只要程式還是人寫的,只要人還是有限的,那就沒有任何一個方法能保證第一次就完美。這件事聽起來不浪漫,卻很有保護力。因為真正危險的從來不是方法不夠強,而是人開始相信自己終於找到一套不會出錯的方法。




3. 我們遇到 Bug 常常處理的方式有哪些?


說到底,工程師遇到 Bug 的時候,通常會在好幾種角色之間來回切換。一下像偵探,一下像法醫,一下像歷史學家,一下又像清潔工。表面看起來只是坐在螢幕前查 log,實際上心裡在做的事比那複雜得多。

如果把它整理成比較像樣的流程,通常會長這樣:

  1. 重現問題。先證明它真的存在,而且不是只出現在某個人情緒最差的那個下午。
  2. 蒐集線索。看 log、stack trace、metrics、版本差異、監控圖,確認哪些是現場證據,哪些只是雜訊。
  3. 隔離範圍。下 breakpoint、做 binary search、用 git bisect、切換 feature flag,把嫌疑從整個宇宙縮到一個房間。
  4. 修復與驗證。補上修正、重跑案例、加上回歸測試,確認這次不是把灰塵掃到地毯下面。
  5. 留下制度性記憶。補文件、補監控、補測試、調整告警門檻,讓下次不要再用同一種方式受傷。

這裡面最重要的一點是,工程師很少真的只用一招。我們會看 log,但也會講給小黃鴨聽;會下斷點,但也會直接 rollback;會查最近的 commit,也會回頭看是不是其實需求一開始就被理解錯了。遇到複雜的 Heisenbug 時,我們甚至會放棄侵入式除錯,改用 dump、ETW 或低干擾監控,像法醫一樣對著「屍體」做判讀,而不是在病人還活著的時候一直把手伸進去亂摸。

我很喜歡把除錯想成一場靜下來的追查。你不會在第一分鐘就知道答案,但你會慢慢知道哪裡不是答案。哪些理所當然其實不成立,哪些「平常都正常」只是因為從來沒有真的看過它。這也是為什麼我一直覺得,好的除錯方法都帶著一種不太耀眼的美德:耐心。它們不帥,卻很可靠。它們把焦慮從一團霧,慢慢壓縮成一條能追的線。




4. 為什麼我們會有這些處理方式?


我們之所以會發明這整套除錯手勢,背後其實有一個很根本的信念:錯誤不是鬧鬼,它是有原因的。只要世界不是完全任性,線索就值得蒐集,重現就值得花時間,測試就不是形式主義。你不會為一個完全沒有因果的世界寫監控;你也不會為一個徹底無法理解的系統做回歸測試。工程方法之所以存在,是因為我們其實默默相信秩序存在。

但另一半的原因同樣重要:因為我們太容易忘記、太容易自信、也太容易替自己腦補。人類的大腦根本不是為了維護大型分散式系統而設計的。我們擅長講故事,不擅長在腦中同時穩定追蹤五百個狀態、三條執行緒、兩個部署環境與一個沒人更新的規格文件。所以我們才需要 log,需要 observability,需要 code review,需要版本控制。這些工具表面上在幫我們管理系統,本質上卻也在幫我們管理人自己的不可靠。

從這個角度看,工程團隊其實一直在做一件很有趣的事:替人的有限,建立外部記憶。監控圖是記憶,測試是記憶,Git history 是記憶,甚至同事在 PR 上那句「你確定這裡不會有 race condition 嗎」也是記憶。很多時候,所謂穩定的架構不是單靠某個超強工程師,而是一群人願意承認自己記不住、看不全、猜不準,於是把記憶外包給制度。

所以我們會有這些處理方式,並不是因為工程師特別愛麻煩,而是因為沒有這些方式,有限的人根本撐不起複雜系統。越成熟的工程文化,越知道方法的存在不是為了炫耀專業,而是為了在人不可靠的前提下,盡量讓系統還能可靠一點。




5. 這些方法其實源頭是什麼科學理論?


如果再往下追,你會發現除錯的很多動作,其實都很像科學實驗。工程師常常以為自己只是在「修東西」,但修的過程裡其實一直在做實驗設計。你重現問題,是在追求可重複性;你隔離模組,是在控制變因;你補測試,是把猜想寫成可驗證條件;你跑壓測,是在看系統在極端條件下會怎麼變形。說穿了,除錯不只是修補,還是驗證。

這件事可以很具體:

開發現場的動作 背後的科學觀念 它真正想確認的事
重現問題 可重複性 這不是幻覺,也不是一次性的巧合
隔離模組 變因控制 問題到底來自哪一層,而不是全部一起出錯
監控與度量 觀測與測量 系統現在到底發生了什麼,不靠感覺判斷
壓力測試 系統科學與排隊理論 當流量、資源與等待時間升高,系統會在哪裡先崩
回歸測試 假說驗證 這次修正是否真的有效,而且沒有引入新災情

如果系統只有單執行緒、單機、低流量,我們對世界的理解會比較接近古典物理:有因必有果,行為穩定,重跑就能再現。這也是為什麼有些 Bug 很像 Bohrbug,老老實實地待在那裡,等你來抓。但一旦進入多執行緒、高併發、分散式架構或實體硬體干擾,事情就開始變得像複雜系統科學。這時候你看到的已經不只是邏輯錯誤,還包括時序、資源競爭、觀測干擾與非線性後果。

Heisenbug 這個說法之所以迷人,不是因為工程師真的在把量子力學硬套到每個 bug 上,而是因為它抓到了一種很真實的經驗:觀測本身可能改變現象。你掛上 debugger,執行緒節奏被拉慢,原本的 race condition 消失了;你拔掉 debugger,上線後它又像野狗一樣跑回來咬人。這時候我們就會知道,單純相信「我在 IDE 裡看過了」已經不夠,必須改成低干擾的追蹤與事後分析。

所以我很同意草稿裡那種說法:找 Bug 的過程,本質上很科學。只是這種科學不是穿白袍站在實驗桌前,而是穿著連帽外套,凌晨兩點盯著監控圖、記憶體 dump 和某一條怎麼看都不太對的 queue latency 曲線。




6. 這些科學理論其實又是什麼哲學理論的應用?


但科學從來不是憑空冒出來的。它背後一直站著更早的哲學前提:世界真的存在,真理不是憑心情改寫,因果不是幻覺,而人的理性雖然有限,卻不是完全失效。沒有這些前提,科學根本不會開始;工程也不會開始。

在除錯現場,我們其實天天都在用哲學,只是平常不這樣叫它。下面這張表,是我覺得最貼近工程日常的版本:

推理方法 它在問什麼 工程上的常見樣子
演繹推理 如果原則為真,結論應該是什麼 根據 SOLID、型別系統或介面契約推論這段程式本來就不該這樣壞
歸納推理 這些案例背後有沒有規律 從大量 log、metrics、使用者回報中找出「只在凌晨兩點發生」的模式
溯因推理 以目前資訊,最可能的原因是什麼 看見 NullReferenceException 後,先猜設定讀取、DI 注入或序列化哪一段最可疑
類比推理 這個陌生問題像不像我以前見過的某種問題 把新的故障模式對照既有架構、既有事故、既有領域經驗來理解

除這些之外,工程師也很常使用幾把哲學上的「短刀」。奧卡姆剃刀提醒我們先查最簡單的可能,不要一看到 500 就懷疑宇宙射線;第一性原理逼我們把「我以為」全部拆掉,回頭只相信最基礎的事實;五問法則像蘇格拉底站在你旁邊,一直問到你再也不能拿表面理由敷衍為止。這些都是哲學,只是被工程現場磨得比較實用。

也因為這樣,我很喜歡那句草稿裡反覆出現的話:找 Bug 的前提是哲學,過程才是科學。因為你若不先相信世界是可理解的,後面的所有驗證都失去意義。你之所以願意一次一次重跑測試,不是因為你迷信儀式,而是因為你相信真相不會永遠躲在黑暗裡。這種相信,本身就是哲學。

甚至「自然哲學」這個舊詞,也很能提醒我們一件事:科學原本不是要取代哲學,而是從哲學裡長出來的。工程也是。IDE 沒有發明真理,測試框架也沒有發明因果;它們只是把更古老的思考方式,轉譯成了比較能在團隊與產品裡落地的形式。




7. 哲學理論又跟神學有什麼關係?


哲學已經很深了,但它再往下走,還是會遇到更底的問題。為什麼世界值得被理解?為什麼秩序不是偶然幻覺?為什麼真理對人有約束力,而不是一種方便時才拿來用的工具?如果宇宙只是雜訊,我們為什麼會對錯誤感到不安,甚至覺得「應該把它修好」?這些問題問到最後,已經不是單純的方法論,而會碰到存在、秩序、責任與盼望。

這也是神學會出現的地方。對我來說,神學不是來把哲學趕走的,而是給了哲學一套更深的文法。哲學讓我們問:人如何知道?神學進一步追問:人為什麼會在乎知道?哲學讓我們談因果;神學會問:這個世界為什麼不是完全混沌?哲學讓我們討論善與真;神學則讓「善」與「真」不只是抽象概念,而是跟受造秩序、人的位置與責任發生關係。

如果借用草稿裡常用的歸正傳統詞彙,有幾個比喻對工程現場真的很有解釋力。全然敗壞提醒我,人連在最擅長的領域裡都不是完全可靠的,所以我不該過度相信自己的第一判斷。護理這個詞則讓我想到,那些表面看來像隨機雜訊的事件,很多時候只是因為我的觀測尺度太短,還看不見更大的規律。至於恩典與光照,則很像外部提醒與校正:分析器、監控、code review、同事的提醒、事故後的檢討,常常都像一道不太舒服、卻真的照見盲點的光。

我當然知道,工程隱喻不能直接拿來等同神學本身。上帝不是編譯器,人也不是單純的 process,教義更不是拿來替架構設計背書的工具。但即使如此,神學仍然提供了一種很深的人性理解:人不是世界的中心,理性也不是最終主權。當你承認這件事,哲學就不容易失控成傲慢,工程也不容易失控成自我崇拜。

而我想,這可能就是為什麼《箴言》那句「敬畏耶和華是智慧的開端」,會讓這麼多工程師覺得刺耳卻誠實。因為它說的不是反智,而是先把人放回正確的位置。不是要你別思考,而是提醒你:你可以思考,但你不是神。




8. 神學又跟程式開發有什麼關係?


如果神學只停留在漂亮隱喻,它對工程師幫助其實不大。真正重要的是,它會不會改變你寫程式的姿態。對我來說,答案是會,而且是很實際地會。

當我承認人是有限的,我就更願意寫測試,因為我不相信自己的記憶能永遠正確;當我承認人會自欺,我就比較不敢吞 exception,也不敢用華麗命名掩飾模糊設計;當我承認世界會破損,我就知道 backup、rollback、observability、最小權限不是悲觀,而是清醒。很多看起來「不夠有自信」的工程實踐,其實都是對人性很誠實的回應。

這種影響甚至會一路延伸到團隊文化:

若我承認…… 我在工程上更傾向……
人的理解有限 寫測試、補文件、讓知識外部化,而不是只存在腦中
人會自欺 留下 trace、監控與稽核線索,不讓自己靠印象工作
系統終究會破損 建立 rollback、backup、最小權限與隔離機制
智慧需要共同體 接受 code review、架構討論與事後檢討,不迷信個人英雄

我很喜歡把 refactoring 想成一種悔改。不是因為原本的自己多可恥,而是因為你終於承認:這條路當初走歪了,現在應該回頭。這樣的語言對某些人來說可能太重,但對我來說,它能提醒我,修正不是丟臉,硬撐才是。團隊裡別人的提醒,也不必每次都被聽成挑釁。有時候它更像恩典,因為在真正上線炸開之前,先有人替你看見你沒看見的洞。

神學當然不會直接替我修掉 NullReferenceException,也不會自動讓 API latency 下降。但它會很持久地改變一個工程師最核心的東西:面對複雜系統時,是不是還願意保持謙卑。這份謙卑最後會變成很多實際選擇:你不會輕易相信「這裡應該沒事」;你會願意多做一步驗證;你會知道最危險的 Bug,常常不是藏在 log 裡,而是藏在「我一定是對的」那句話裡。

甚至連 AI 時代也一樣。AI 只是把人的有限放大得更快。它讓你更快寫出東西,也讓錯誤更快擴散。所以真正成熟的姿態從來不是「我終於不需要懷疑了」,而是「我現在更需要邊界、驗證與共同體了」。如果說神學對工程有什麼最實際的貢獻,我想就是這句話:不要把速度誤認成救恩。




9. 結論:敬畏與聰明的終極整合


所以,程式開發怎麼會跟哲學神學有關係?因為我們每天做的事,本來就在回答同一組問題:什麼是真的?人能知道多少?錯誤從哪裡來?我們怎樣在有限裡,仍然盡可能活得誠實?

聰明讓我們會抽象、會建模、會拆解複雜度,知道怎麼寫出好一點的介面、找出更乾淨的邊界、設計更穩健的流程;敬畏則提醒我們,模型不是現實,規格不是世界,工具不是救主,而人也不是神。少了聰明,工程會退化成口號與情緒;少了敬畏,工程則很容易變成傲慢、控制慾與自我神話。

《箴言》說敬畏是智慧的開端,《傳道書》則提醒人:著書多,沒有窮盡;讀書多,身體疲倦。這兩句話放在今天的工程世界裡,竟然一點也不舊。框架更新沒有窮盡,文件沒有窮盡,新的 AI 工具也沒有窮盡。如果我們把所有盼望都押在「再多學一點、再快一點、再強一點」,最後很可能只剩疲倦。真正讓人穩下來的,不是終於掌握了全部,而是承認自己掌握不了全部,於是開始更珍惜誠實、驗證、團隊與邊界。

對我而言,成熟的工程師不是最晚睡,也不是最快敲完程式的那一個,而是知道什麼時候該懷疑自己、什麼時候該重現問題、什麼時候該回退、什麼時候該請別人幫忙的人。這種成熟不是技術炫技,而是聰明和敬畏終於不再彼此拉扯,而能安靜地坐在同一張桌上。

所以下次當你在深夜裡盯著一個不肯消失的 Bug,也許可以先別急著咒罵。它未必只是來折磨你,也可能是來提醒你:人不是神,卻仍被邀請去理解秩序。而當敬畏與聰明終於在同一張桌上坐下來,寫程式這件事,就不再只是工作,而會慢慢變成一種比較誠實、也比較清醒的活法。


如果要讓這篇文章停在一個更穩的地方,我想還是停在經文裡:

箴言 第9章 第10節
敬畏耶和華是智慧的開端;認識至聖者便是聰明。

傳道書 第12章 第12~14節
我兒,還有一層,你當受勸戒:著書多,沒有窮盡;讀書多,身體疲倦。
這些事都已聽見了,總意就是:敬畏神,謹守他的誡命,這是人所當盡的本分(或譯:這是眾人的本分)。
因為人所做的事,連一切隱藏的事,無論是善是惡,神都必審問。