ArgumentException:堅守方法契約的守護者
在一場喧囂退散後的深夜裡,你靜靜坐在螢幕前,思考著方法該接什麼參數、該怎麼寫文件、又該如何保護自己的程式不被誤用。窗外月光灑落,鍵盤發出細碎的聲響,像是在替你的思緒伴奏。
你忽然想起曾經看過的一個故事——
那天,一個 API 被錯誤呼叫,整個系統瞬間炸出 38 行紅字。工程師癱坐在椅子上,神情恍惚,喃喃自語:「我不是有寫說不能傳 null 嗎……我不是有寫說不能傳 null 嗎……」
但呼叫者沒看備註、沒讀文件,甚至忽略了參數欄位的提示
這不是你的錯 —— 也不是那個 API 的錯。錯的是,我們總是高估了世界的渾沌與人類的創意。
就在這時,ArgumentException 穿著制服,從方法深處走了出來,冷冷地看著錯誤的呼叫者說:
「喂,朋友,參數這樣傳,是違約行為你知道嗎?」然後果斷地丟出例外,把整個流程踢出場外,給了開發者一個痛快又明確的教訓。
你忽然醒來 —— 原來剛才是在打瞌睡。揉揉惺忪的眼睛,看了眼窗邊滴答作響的時鐘:嘛,還有兩個小時 deadline。你繼續搭搭的寫著那支尚未完成的 API。
我們寫方法、設計介面、開放 API 給他人呼叫時,這些方法本身,也該有一份堅定的契約。這份契約不是藏在 README 裡的一行小字,更不是大家「應該都知道吧」的默契。它應該是語言層級的、明確且無法被忽略的界線。
exception 只在關鍵時刻提醒你:「這不是你該給我的東西。」不是責備,不是崩潰,而是一種維護方法尊嚴的溫柔堅持
❔ArgumentException 是什麼?
核心 : ArgumentException 是「呼叫這個方法的開發者傳了錯誤的參數」,代表著「開發錯誤,不是使用者錯誤」。
換句話說,當你在呼叫一個 Service 或任何方法時,如果參數本身就「不該這樣」,ArgumentException 會立刻跳出來提醒你:「你這裡給錯了!」
👩🏫 考慮到方法的使用者
如果我已經知道參數要驗證,我在 API 層都加上 FluentValidation 驗掉,那我還要在 method 裡拋 ArgumentException 嗎?
你心中的答案可能是「不用了吧?我不都擋過了」。
但我們要思考的是,使用的角色不同、應對就會有所不同這件事
錯誤若來自使用者的輸入,如輸入太短密碼,那應該是應用層 (前端阻擋、Fluent Validation…) 處理沒錯,確實不該丟 ArgumentException
然而,要評估會走到你的核心邏輯的呼叫者不一定來自 Controller 或是說 client 端!
今後有開發者寫一個批次工具、測試工具甚至後台工具… 直接呼叫這個 Service,沒經過 Validator,怎麼辦?
➡ ArgumentException 就像是 method 自己「有骨氣」,不管你是不是走對流程,我都會檢查來者是否合格。
圖中整理可以看到,我們還有義務告知其他開發者,如果你使用了我的 Service,你可能觸犯了什麼方法契約,並回傳較為準確的異常顯示,用來標示「你這個 method 用錯了」,例如:
- ArgumentNullException
- InvalidOperationException
- NotSupportedException
➡ 這些都與 method 的合約(Contract)有關,代表「你給我的參數不合規」。
假如,今天有方法呼叫者傳了錯誤的資訊例如 null,我作為設計者不想拋 Exception,而是 return “不可為 null”,可以嗎?
這個問題提醒了我們另外一個 Exception 的本質 ! 讓錯誤「被發現」!
即我們的程式在各種流程中無法透過單元測試、自動化 CI 等快速被發現錯誤、單元測試無法偵測錯誤,因為你沒有拋 exception,測試只會收到一個字串。因此
➡️ 雖然你要怎麼處理都可以,但呼叫的開發者不知道怎麼應對,你給的回應沒有一個標準的共通性。在團隊開發、寫函式庫、寫 SDK 或共用模組時,你無法要求別人記住你 return 哪些錯誤字串。但 拋 Exception 出來是語言內建的東西,可以:
- 被 try-catch 捕捉
- 被 log 框架記錄
- 被 IDE 分析
- 被測試驗證
- 被文件工具自動掃出
這是「生態圈優勢」,是錯誤字串做不到的。因此更好的做法是,讓呼叫端更明確知道自己犯錯了請盡速修正!
1 |
|
1 |
|
還有一點是,最好讓整個 APP 知道整個操作行為已經被取消了,ASP.NET Core pipeline 也會知道這個 request failed,可以:終止 middleware 執行、回傳 HTTP 400/500、不會排入 background queue、CancellationToken 有機會提早通知底層取消作業
👼 ArgumentException 的家族成員
.NET 有提供許多細分類別:
ArgumentNullException:null 不允許
ArgumentOutOfRangeException:值超出範圍
ArgumentException:一般錯誤
✅ 建議永遠優先使用更具語意的子類別,這樣 catch 更精準,也更清楚目的。
🐎 當參數愈來愈多,檢查邏輯如何不失控?
當一個方法接收多個參數,逐一檢查的程式碼看起來可能像這樣:
1 |
|
為了避免重複並提高可維護性,我們可以把驗證責任抽到 Value Object 裡:
1 |
|
如此一來,AccountService.Register 只需專注在註冊邏輯,而 RegisterInfo 已替你把驗證「包好了」。
🌮 各層責任分工:誰該做驗證?
Presentation/API 層:擋使用者輸入錯誤,回 HTTP 400;
Validation 層(FluentValidation 等):統一管理複雜驗證規則;
Domain/Value Object:封裝屬性,確保任何呼叫端都透過同一驗證邏輯;
Service/Method:作為最後一道防線,拋出 ArgumentException 顯示契約違反。
☘️ 結語 : 讓例外成為開發的好朋友
你看著那段程式碼,緩緩呼了口氣。他察覺到你的注視,沒有多話,只是微微點了下頭,像是在說:「你寫得不錯,我會接手這裡。」而你也回了一個輕輕的點頭。
沒有掌聲、沒有煙火,只有一個工程師在凌晨 3 點,與一位忠誠的守門人,無聲地交換了信任。
你按下 Ctrl+S,合上筆電,窗外天空已微亮。那段方法將繼續在無數次呼叫中,被他守護。