軟體架構學習紀錄(一)

AI 工具雖然快速,但我仍偏好用書本學習一些需要深層理解的知識。軟體架構一直是我想補強的領域。平常工作中會嘗試從架構的角度理解問題,但我希望能讀些更嚴謹、系統化的書籍,從底層建立起紮實的認知框架。 軟體架構是什麼? 軟體架構可以類比為房屋的結構設計。建築中可能包含承重牆、樑柱與屋頂等結構元件,沒有這些結構,房子就無法成立。至於地板材質、顏色,甚至沙發要擺在哪裡,這些並不會出現在建築的結構圖中,因為它們與設計相關,而非結構。 類似地,軟體系統也有其結構,而我們對這種結構的理解,可以從四個維度來思考1: 架構特性(Architectural Characteristics):系統著重於效能或是妥善性? 架構決策(Architectural Decisions):例如系統的使用者介面是否允許與資料庫直接溝通,或是必續透過資料存取服務(如 API)來溝通? 邏輯組件(Logical Components):每個業務功能的原始碼集合(成立訂單、付款) 架構風格(Architectural Styles):微服務、分層架構等。 因此當有人說「我們的架構是微服務」這句話只定義了一個維度(架構風格),不足以描述整體架構。 架構角度 & 設計角度 上面提到,地板材質、顏色這些設計,對系統而言是設計角度問題。舉例來說,更改網頁的背景顏色,這種較偏向設計,而非架構。 但事實上架構與設計並沒有辦法那麼容易截然二分,他們是位於光譜的兩端。例如,將一個服務拆分為兩個獨立模組,這不僅涉及程式結構的重構與介面設計,也可能影響部署策略與服務間依賴關係。這類決策就處於設計與架構之間的模糊地帶。 待續 參《深入淺出軟體架構》, Raju Gandhi, Mark Richards & Neal Ford。 ↩︎

July 13, 2025 · 1 分鐘

Goals vs Systems

前言 今天重新打開 Algomonster 時,點開了之前沒特別注意的 Getting Started 章節。其中一句話讓我停下來反覆思考: At AlgoMonster, we love systems. We are big believers in systems over goals. – 在 AlgoMonster,我們熱愛系統。我們堅信系統比目標更重要。 這句話讓我產生好奇:「系統比目標更重要(systems over goals.)」這是什麼意思?點進連結後,發現是漫畫家 Scott Adams 的部落格文章1,內容來自他的一本書《How to Fail at Almost Everything and Still Win Big》2。這裡整理出我讀完後的理解與想法。 目標(Goals) Scott Adams 指出,目標是「限制性的 (limiting)」。如果你只專注於一個特定的目標,雖然達成它的機率可能比沒有目標時高,但你可能會「錯過可能遠比你的目標更好的機會」 (miss out on opportunities that might have been far better than your goal)」。 舉例來說, 減重多少公斤:這類目標很常見,但實際上很難持之以恆(我自己也經歷過)。 每週去健身房三到四次:這也是一個目標,對於不喜歡運動的人來說可能感覺像懲罰,導致最終因為感到痛苦而放棄。 系統 (Systems) 而 Scott Adams 認為,「系統」旨在將你從低勝算的情況轉移到高勝算的情況 (move you from a game with low odds to a game with better odds)。 透過系統,你不太可能因為過於專注於一個機會而錯過另一個 (less likely to miss one opportunity because you were too focused on another),並且總是在掃描任何機會 (always scanning for any opportunity)。 ...

July 12, 2025 · 1 分鐘

Safari Web Inspector 流量攔截延遲現象

前言 最近遇到一個需求,我們的 mobile app 需要用 web view 開啟某個網頁。因為該網頁的認證方式最近有調整,app 端也得跟著修改。也因此這次 iOS 也要從原本的 SFSafariViewController 換成 MKWebView 來實作內嵌網頁,結果問題就這樣冒出來了。 過程 事情大概是這樣: 當我在頁面上點擊某個按鈕,會跳轉到另一個網頁,然後這個新頁面會自動彈出一個視窗到最前面。奇怪的是,首頁本身載入沒問題,如果在首頁觸發彈窗也都正常。但只要是跳轉後的頁面,彈窗就會出狀況。 一開始我懷疑是 cookie 沒有正確傳遞,或是 header 有缺漏,所以重點都放在這些地方。用 Safari Web Inspector 看 network,也沒看到什麼明顯的錯誤,只是覺得很怪,怎麼 request、response 的紀錄都不太完整,像是 header 竟然是空的。當下還以為這就是正常現象,只好很麻煩地回到 app 裡面用 NSLog 確認內容。 後來不知道哪根筋突然不對,想說試試看手動按一下重新整理。沒想到這一按,network 頁面所有的請求紀錄都跑出來了,header、response 什麼資訊都有。這時才發現,原來是某個 JS 檔在執行時出錯。仔細追查才知道,是網頁開發者寫的「取得使用者瀏覽器類型」的 function 沒有考慮到 MKWebView 發出的 User-Agent,導致相關 UI 無法正常顯示。 那段程式碼還註解「若非不得已,盡量少用此方法」,是一個拿 14 年前 stackoverflow 上的解答的函式XD User-Agent:WKWebView 預設的 User-Agent 與 Safari 或 SFSafariViewController 不同,若網頁端有依賴 User-Agent 進行判斷,需特別注意。 關於 Safari Web Inspector 的觀察 這次讓我發現,Safari Web Inspector 其實有個不太直覺的地方。你必須先在手機上打開那個網頁,Inspector 的 develop 頁籤才會出現該網頁的選項。也就是說,當你點進去看的時候,網頁內容已經載入完成了,Inspector 只會顯示最後的狀態。過程中的流量、請求紀錄根本沒被捕捉到,除非你手動重新整理一次,才會看到完整的 network 資訊。 ...

July 11, 2025 · 1 分鐘

Azure ReplaceTokens 字串轉義問題

問題現象 在某個專案中,CI/CD 過程需要將設定以 token 替換的方式寫入 JSON 檔案,其中一個欄位包含多行文字內容。在 pipeline 中使用了 qetza.replacetokens.replacetokens-task.replacetokens@5 這個 Azure DevOps Task: - task: qetza.replacetokens.replacetokens-task.replacetokens@5 displayName: 'Replace token in xxx-config.json' inputs: targetFiles: '$(System.ArtifactsDirectory)\\drop\\$(Build.BuildId)\\xxx-config.json' 完成替換並部署到測試環境後,發現無法正常啟動。檢查部署產出的 JSON 檔案時,發現原本應為多行的字串中的 \n,在處理後變成了 \\n。 這導致程式在解析該欄位時,無法還原出原本的格式,進而發生錯誤。 問題原因 查閱文件後發現,ReplaceTokens 預設會對變數值進行 escape 處理。這對一般情況有幫助,但對於需要保留格式的多行字串(如憑證或編碼內容)就會導致格式錯誤。 解法:使用 noescape() 函數停用自動 escape ReplaceTokens 支援開啟轉換函數(Transformations)功能,可用來自訂變數處理方式。具體做法如下: 步驟 1:啟用 enableTransforms 在 ReplaceTokens 的 YAML 設定中加上 enableTransforms: true - task: qetza.replacetokens.replacetokens-task.replacetokens@5 displayName: 'Replace token in xxx-config.json' inputs: targetFiles: '$(System.ArtifactsDirectory)\\drop\\$(Build.BuildId)\\xxx-config.json' enableTransforms: true # 要加這行 步驟 2:修改 JSON,使用 noescape() 將目標檔案中的 token 改寫為: { "config_content": "#{noescape(multiline_config)}#" } 如此可讓 \n 在替換後仍保持原始格式,不會被轉成 \\n。 ...

July 9, 2025 · 1 分鐘

NuGet 套件遺失問題紀錄

Generated by Perplexity 問題說明 前陣子我遇到一個值得記錄的問題(至少對我而言):在維護一個有點舊的 .NET framework 專案時,發現透過 ProjectReference 方式依賴的 NuGet 套件無法自動還原。經過一番調查後,才發現這是 NuGet 在處理某些舊式專案時的一個行為特性。對 .NET 很不熟的我覺得可以記錄一下,以防未來又忘記。 專案結構與重現情境 MainApp.csproj ← 主專案,透過 ProjectReference 參考 SharedLibrary.csproj └── SharedLibrary.csproj └── <PackageReference Include="ICSharpCode.SharpZipLib" /> 我的 solution 中有兩個專案: MainApp.csproj:主應用程式專案 SharedLibrary.csproj:共用函式庫,透過 ProjectReference 被 MainApp 參考 在 SharedLibrary.csproj 中,使用了常見的壓縮解壓縮函式庫 ICSharpCode.SharpZipLib,並以以下方式引用: <PackageReference Include="ICSharpCode.SharpZipLib" Version="1.4.2" /> 然而在建置 MainApp.csproj 時,卻發現 SharpZipLib 的相關類別無法解析,沒有編譯出 .dll 檔。反觀其他套件如 NPOI 和 EntityFramework 則一切正常,讓我相當困惑(因為不論是在 prodution 或是 test 環境中都沒看到 .dll 缺失的問題)。 至於為什麼會發生這種事情,我想…就技術債這三個字吧 嘗試與排查過程 一開始我找問題的方式比較粗暴簡單,那就是找其他也有引用 SharedLibrary.csproj,但卻可以成功編譯的專案,看他們跟有問題的專案差在哪裡。於是我找到了其他專案多的這一行: <PackageReference Include="EntityFramework" Version="5.0.0" /> 神奇的事情發生了,SharpZipLib 居然也能正確還原了! 這讓我開始懷疑問題出在 NuGet 的套件還原邏輯上。經過研究,終於找到癥結所在。 ...

July 7, 2025 · 1 分鐘