Cache - 從頭開始
在系統村裡,有位小學徒 Yoyo,每天最常被叫去做的工作,就是幫村裡人查詢「住戶資料」。
「Yoyo,去問資料庫大師,這位用戶住哪裡?」「Yoyo,再去查一次,那筆我剛剛沒記住。」「Yoyo,再去……」
一開始,Yoyo 也不以為意。畢竟這是學徒的日常。可重複個幾百次後,他終於受不了了:「為什麼我每次查完,都要重跑一樣的路?」
直到有天,他開始在口袋裡放一本小筆記本,把查過的資料寫在上面。從此以後,他只要看看自己的筆記本,就能直接回應問題,不必每次都跑一趟。
這本小筆記本,就是 Yoyo 發明的「快取系統」。
這篇文章,就是 Yoyo 筆記本的成長紀錄——從一開始只是單純的記錄,到後來要考慮資料會變、要支援多人同時翻閱、還要知道什麼時候該撕掉舊頁重寫。
如果你也曾為重複查資料煩惱過,那就一起打開這本「Cache 筆記」,從第一頁開始吧。
🥥 第一階段:打造屬於自己的「最簡快取機制」
在這一階段,我們要實作一個最基礎、卻實用+的快取系統。這是認識快取背後原理的第一步,也是許多大型系統背後的關鍵核心。
快取的真正意義:避免重複做「同一件事」在真實世界的應用程式中,我們常 ...
Asynchronous Programming - 第三章:非同步中的等待藝術
小遙家裡有一台很貴的全自動咖啡機。只要放入咖啡豆,按下按鈕,五分鐘後,一杯完美的拿鐵便會端坐在托盤上,溫度與比例都恰到好處。
但她還是習慣站在一旁等。
她不是完全相信機器;在咖啡流出前的幾十秒,她總會打開上蓋,觀察豆槽有沒有卡住,再輕拍一下;聽見磨豆的聲音時,也會試著微調參數、瞄一下壓力表。她甚至曾試著在機器運作時,手動幫忙攪拌牛奶。
這一切看起來像是「更專業的沖煮」,但其實只是「更不安的等待」。直到有一天,她打翻了一杯還沒加糖的咖啡,才驚覺:其實什麼都不做,才是真的在等那杯咖啡完成。
🎵 Task.Run() 背後到底做了什麼?Task.Run(…) 的本質是這樣
12345678public static Task Run(Action action){ return Task.Factory.StartNew( action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); ...
組合的力量與設計哲學
在上一篇文章中,我們看到繼承帶來的限制與設計陷阱。本篇將把鏡頭轉向另一個更自由、更靈活的設計哲學 —— 組合(Composition)。
組合就像是把系統拆解成可重組的積木,每個模組只負責一個任務,清晰、精準、可插拔,讓程式不僅易於擴充,也更能回應未來的變化。
🌸 組合怎麼解決問題?用組合代替繼承,解決行為歧義(Ambiguity of Behavior)讓戰鬥法師「擁有」戰士和法師的能力,而不是「繼承」它們。
12345678910public class Battlemage{ private Warrior _warrior = new Warrior(); private Mage _mage = new Mage(); public void MeleeAttack() => _warrior.Attack(); public void CastSpell() => _mage.CastSpell();}
用 Interface 來分離行為,避免多繼承,解決屬性重複(Duplicate State)讓 Warrior ...
從克德族說起:繼承為何讓人生卡關?
好久好久以前,有兩個著名的家族——
克德族,是一個信奉血統主義的家族。他們深信純正血脈的力量,嚴格遵循世代傳承的家族傳統,認為唯有優良基因的延續,才能孕育出最優秀的後代。起初,這種方式的確造就了不少傑出人才。克德家的子女大多具備出色的音感,未來幾乎都走上了音樂的道路。
但隨著時間流逝,問題逐漸浮現。某一天,家族中一位孩子心懷成為機械技師的夢想,卻發現家族的傳統成了束縛。他無法脫離音樂的陰影,也難以獲得從事機械所需的技能,陷入深深的痛苦與迷惘。
海的另一端,是萊雅族。這是一個秉持開放思想的家族。他們相信卓越不僅來自血統,更來自個人的選擇與學習。他們鼓勵每位成員自由探索,組合各種不同的技能與知識。
這樣的理念帶來了極大的彈性。萊雅族的子女可以依興趣學習,不論是音樂、機械或其他領域皆不受限制。他們甚至可以同時成為音樂家與技師。
然而,自由也帶來困惑。因選擇過多,部分成員在成長過程中感到迷惘,沒有明確的方向。不僅如此,因為每個人都走著截然不同的路,外人甚至難以辨別他們是否屬於同一族群。
克德族的困境,就像物件導向中的繼承(Inheritance)。子類別(Subclass)往往難以擺脫父類 ...
Exception Handling - 拋出路徑
那是一瞬間的閃光,一道能讓你看見錯誤源頭的光。
💤 什麼是 Exception 的處理路徑?
當程式碼發生錯誤,例如除以零、存取 null、開檔失敗… 這種錯誤叫做「例外(Exception)」,.NET CLR(Common Language Runtime)會:
丟出 Exception
從錯誤發生的地方開始,一層層往上找有沒有 try-catch 處理它
找不到時,會讓應用程式崩潰(Unhandled Exception)
這整個過程中 debugger(偵錯器)會被通知兩次機會,稱為:
First Chance Exception(第一次通知)
Second Chance Exception(第二次通知,如果沒被處理)
💤 當 Exception 發生時,CLR 做了什麼?
.NET CLR 會馬上建立一個 Exception 物件,並啟動所謂的 stack unwinding(堆疊回溯)。
💡 語言層面來看當你用 throw new Exception(“錯誤”),C# 編譯器會轉成 IL(中繼語言)碼,類似這樣:
12newobj inst ...
InvalidOperationException:丞相 還沒起風啊
時間已經到傍晚 6 點,此刻的你正如同洩了氣的氣球軟軟的攤在桌上
主管 : 等一下可以幫我釐清…
你 :
12345System.InvalidOperationException: Operation is not valid due to the current state of the object. at System.Collections.Generic.List`1.Enumerator.get_Current() at Me.Program.Main(String[] args) in C:\Me\Program.cs:line 12
主管 : ???
你 : (。ŏ_ŏ)
💤 InvalidOperationException 的本質是什麼?
「目前物件的狀態,不允許你執行這個動作。」
這種錯誤不是語法錯、也不是參數錯,而是「物件處於此狀態,你要求它做不該做的事。」
💤 常見觸發情境
1️⃣ 修改集合時,同時在迭代(foreach)12345var list = new List<int> { 1, 2, 3 ...
ArgumentException:堅守方法契約的守護者
在一場喧囂退散後的深夜裡,你靜靜坐在螢幕前,思考著方法該接什麼參數、該怎麼寫文件、又該如何保護自己的程式不被誤用。窗外月光灑落,鍵盤發出細碎的聲響,像是在替你的思緒伴奏。
你忽然想起曾經看過的一個故事——
那天,一個 API 被錯誤呼叫,整個系統瞬間炸出 38 行紅字。工程師癱坐在椅子上,神情恍惚,喃喃自語:「我不是有寫說不能傳 null 嗎……我不是有寫說不能傳 null 嗎……」
但呼叫者沒看備註、沒讀文件,甚至忽略了參數欄位的提示這不是你的錯 —— 也不是那個 API 的錯。錯的是,我們總是高估了世界的渾沌與人類的創意。
就在這時,ArgumentException 穿著制服,從方法深處走了出來,冷冷地看著錯誤的呼叫者說:
「喂,朋友,參數這樣傳,是違約行為你知道嗎?」然後果斷地丟出例外,把整個流程踢出場外,給了開發者一個痛快又明確的教訓。
你忽然醒來 —— 原來剛才是在打瞌睡。揉揉惺忪的眼睛,看了眼窗邊滴答作響的時鐘:嘛,還有兩個小時 deadline。你繼續搭搭的寫著那支尚未完成的 API。
我們寫方法、設計介面、開放 API 給他人呼叫時,這些方法本身,也該有一份堅定 ...
NullReferenceException:一場與不存在的對話
窗外開始下雨了,輕輕敲在窗上的雨聲,像記憶敲著玻璃,提醒你今天又過去了一天。
房間裡只亮著一盞檯燈,桌上的咖啡溫度早已散去。你盯著螢幕,一行行程式碼像星辰般閃爍,帶著某種秩序,也藏著未解的謎題。
這時候,它又來了。
System.NullReferenceException, Object reference not set to an instance of an object.
我滄桑的搖了搖頭,示意自己又大意了一回
🏔️ NullReferenceException當你試圖透過一個根本沒有指向任何物件的參考(null)來存取資料或方法,.NET 就會丟出 NullReferenceException。
每一個「物件變數」本質上是一個指向記憶體位置的參考(Reference)。
舉例來說:
12345Person person = new Person();Person person = null;person.Name //// ❌ NullReferenceException。
person 就像你手上拿是地址(像 Google Maps 上的定位點),而 p ...
HttpContext 失效時,我們還能說什麼
那是一個午後,城市被陽光輕輕包裹著。我走過熟悉的街口,沒有目的地,只是順著記憶走。
咖啡廳的窗戶閃著熟悉的光,我下意識地望進去 —— 她,就坐在靠窗的位置。那個多年不見的身影,仍舊筆挺,肩上落著光,就像記憶裡的那樣。
我愣了一下,彷彿穿越了幾年的時間。當正要推開門的時候,門內走來一名男子,笑著坐在她對面,手中拿著兩杯咖啡。
她抬頭接過咖啡,對他微笑,那笑容熟悉卻已不再屬於你。
片刻後,一個小男孩蹦蹦跳跳地衝進店裡,喊著「爸爸!」男人張開手臂將他抱起,她則伸手輕拍孩子的頭髮。
你靜靜地站在門外,手輕輕從門把上放下。你沒有走進去,也沒有發出聲音。因為你知道,那個 context,早已不在了。
你轉身離開,步伐輕而穩,那封從未送出的請求,就此作廢。
愛必須在對的 context 中發送。
...
有時候,我們的痛苦來自「沒有 AOP」的人生
每天睜開眼,我們都在寫程式,不是敲在鍵盤上,而是敲在時間裡、選擇裡,生活的每一個 if 和 else 裡。
我們總說要專注主線任務,要成為自己人生的主角。但又說,我們在這宇宙間只是滄海一粟,只是一粒塵埃,現實從不按劇本走,許多事情總不由我不所控,因此人生更像一個超複雜的專案管理系統,Bug 叢生,就連自己也無法 Debug,我們活得像一個沒掛 Filter 的 Controller,誰都能 Call 你,還不需授權。結果不是 Timeout,就是 500 Error
所以啊,到了這個時候,我們可以嘗試為人生加上一點 AOP
讓 Aspect-Oriented Programming 靜靜守護主線任務AOP 到底是什麼?它解決了什麼問題?
本質上,AOP 是一種設計概念,它幫助我們把與主功能沒關係,但到處都要用到的邏輯,例如「紀錄 Log」、「驗證權限」、「計時」等等,從主邏輯中分離出來,集中管理。
把「共通邏輯(Cross-Cutting Concerns)」從每個類別或方法中分離出來,讓主功能更乾淨、可讀性更高,而且這些共通邏輯可以集中維護。它幫你把那些不該佔據腦海的瑣事抽離出來, ...