<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>首頁 · Tommy Cheese</title>
    <link>https://tommycheese.github.io/zh-TW/</link>
    <description>Tommy Cheese 的個人部落格，記錄軟體工程、人工智慧與學習實踐。</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-TW</language>
    <lastBuildDate>Sun, 13 Sep 2026 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/zh-TW/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>OpenCode v2 擴充機制研究與接入說明</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/opencode-v2-extensions/</link>
      <pubDate>Sun, 13 Sep 2026 00:00:00 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/opencode-v2-extensions/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;研究日期：2026-09-13。本文描述 OpenCode v2 的通用外掛能力，包括外掛型別、載入機制、能力註冊、執行鉤子、許可權邊界與生命週期，供外掛開發和嵌入式整合參考。&lt;/p&gt;
&lt;p&gt;程式碼基線為所研究的 OpenCode 倉庫 &lt;code&gt;v2&lt;/code&gt; 分支，提交為 &lt;code&gt;2308db16387c9b59e88d732a93ec9bac54462b03&lt;/code&gt;。原始碼引用採用該固定提交內的倉庫相對路徑；文中的介面與行為以此版本為準，不代表其他 OpenCode 版本或舊版外掛 API 的相容承諾。&lt;/p&gt;
&lt;p&gt;本文是原始碼研究與能力說明。程式碼示例用於解釋介面和呼叫方式，未經獨立執行驗證。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="plugin-扩展入口"&gt;Plugin：擴充套件入口&lt;/h2&gt;
&lt;h3 id="什么是-plugin"&gt;什麼是 Plugin&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;OpenCode 的擴充套件通過註冊能力和執行鉤子接入已有執行流程。&lt;/strong&gt; Extension 在當前 OpenCode v2 實現中主要對應 Plugin 機制。外掛可以提供助手、工具、技能、模型、命令等能力，也可以在指定執行節點調整行為；會話執行、模型請求、工具結果和執行記錄仍由 OpenCode 核心處理。&lt;/p&gt;
&lt;figure class="plugin-flow"&gt;&lt;img src="https://tommycheese.github.io/blogimages/zh-TW/opencode-v2-plugin-flow.svg" alt="外掛載入後註冊能力，會話準備上下文，經請求鉤子呼叫模型；工具呼叫經執行鉤子儲存結果並返回模型，最終回答結束本次執行。" width="429" height="722"&gt;&lt;figcaption&gt;Plugin 從載入、能力註冊到會話執行的流程&lt;/figcaption&gt;&lt;/figure&gt;
&lt;details&gt;&lt;summary&gt;檢視 Mermaid 流程圖原始碼&lt;/summary&gt;&lt;pre&gt;&lt;code class="language-mermaid"&gt;flowchart TD
    A[配置文件、本地插件或 SDK 注册] --&amp;gt; B[插件加载与生命周期管理]
    B --&amp;gt; C[构建助手、工具、技能等能力]
    C --&amp;gt; D[会话选择助手]
    D --&amp;gt; E[准备上下文与工具快照]
    E --&amp;gt; F[请求阶段钩子]
    F --&amp;gt; G[调用模型]
    G --&amp;gt; H{返回内容}
    H --&amp;gt;|工具调用| I[工具执行与钩子]
    I --&amp;gt; J[保存结果与执行事件]
    J --&amp;gt; G
    H --&amp;gt;|最终回答| K[本次执行结束]
&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;h3 id="plugin-的分类"&gt;Plugin 的分類&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;內部外掛、外部外掛和 SDK 外掛按接入來源區分，最終進入同一套執行管理。&lt;/strong&gt; 三者都可以註冊助手、工具和執行鉤子。區別主要在於誰提供外掛定義、如何裝入宿主，以及宿主是否額外注入核心內部服務。這裡討論的是服務端外掛；終端介面的 TUI 外掛屬於另一個擴充套件入口。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;比較項&lt;/th&gt;
&lt;th scope="col"&gt;內部外掛&lt;/th&gt;
&lt;th scope="col"&gt;外部外掛&lt;/th&gt;
&lt;th scope="col"&gt;SDK 外掛&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;提供者&lt;/td&gt;
&lt;td&gt;OpenCode 原始碼與發行版本&lt;/td&gt;
&lt;td&gt;專案或獨立外掛包的維護者&lt;/td&gt;
&lt;td&gt;嵌入 OpenCode 的應用宿主&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;入口&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PluginInternal&lt;/code&gt; 的 &lt;code&gt;pre&lt;/code&gt; / &lt;code&gt;post&lt;/code&gt; 集合&lt;/td&gt;
&lt;td&gt;目錄發現、配置中的檔案或包&lt;/td&gt;
&lt;td&gt;嵌入式宿主的 &lt;code&gt;opencode.plugin(definition)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;裝載形式&lt;/td&gt;
&lt;td&gt;核心程式碼直接匯入定義&lt;/td&gt;
&lt;td&gt;解析模組並讀取預設匯出&lt;/td&gt;
&lt;td&gt;在記憶體中直接傳入外掛物件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可用介面&lt;/td&gt;
&lt;td&gt;公開 Context，加上宿主注入的內部服務&lt;/td&gt;
&lt;td&gt;公開 Plugin Context&lt;/td&gt;
&lt;td&gt;公開 Plugin Context；可通過閉包使用宿主主動傳入的依賴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主要配置方式&lt;/td&gt;
&lt;td&gt;原生配置、內部服務或內建預設值&lt;/td&gt;
&lt;td&gt;&lt;code&gt;plugins[].options&lt;/code&gt; → &lt;code&gt;ctx.options&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宿主程式碼與閉包；註冊介面沒有單獨的 &lt;code&gt;options&lt;/code&gt; 引數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;更新來源&lt;/td&gt;
&lt;td&gt;跟隨 OpenCode 程式碼版本；部分外掛自行監聽配置變化&lt;/td&gt;
&lt;td&gt;配置變化、受支援的本地入口變化或包配置變更&lt;/td&gt;
&lt;td&gt;宿主再次註冊外掛，遞增內部修訂號&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;例項範圍&lt;/td&gt;
&lt;td&gt;每個 Location 獨立啟用&lt;/td&gt;
&lt;td&gt;按各 Location 的配置分別啟用&lt;/td&gt;
&lt;td&gt;同一宿主共享定義，每個 Location 獨立啟用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;適用場景&lt;/td&gt;
&lt;td&gt;實現原生助手、工具、供應商與配置處理&lt;/td&gt;
&lt;td&gt;給已有 OpenCode 服務增加專案或業務能力&lt;/td&gt;
&lt;td&gt;在自己的 JS/TS 應用中嵌入並管理 OpenCode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Effect 和 Promise 是外掛的兩種編寫介面方法。一個採用公開介面的 Effect 外掛物件，既可以預設匯出後由配置載入，也可以直接交給嵌入式 SDK 註冊；核心能力可以複用，但載入順序和配置傳入方式會發生變化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;內部外掛把 OpenCode 自身的一部分功能組織成外掛。&lt;/strong&gt; 它們不是使用者安裝到目錄裡的特殊檔案，而是由核心原始碼靜態匯入，並在 &lt;code&gt;PluginInternal&lt;/code&gt; 中明確列入集合。&lt;code&gt;PluginInternal.list()&lt;/code&gt; 獲取當前 Location 所需的服務，通過 &lt;code&gt;Effect.provide(context)&lt;/code&gt; 注入內部外掛，再交給統一載入器。&lt;/p&gt;
&lt;p&gt;這裡有兩層 Context：&lt;code&gt;effect(ctx)&lt;/code&gt; 的引數仍然是公開 Plugin Context；內部外掛通過 Effect 環境另外取得 &lt;code&gt;Config.Service&lt;/code&gt;、&lt;code&gt;Permission.Service&lt;/code&gt;、&lt;code&gt;Shell.Service&lt;/code&gt;、&lt;code&gt;Location.Service&lt;/code&gt; 等服務。因此，“內部外掛有更多能力”的依據是宿主明確注入了依賴，不是外掛 ID 以 &lt;code&gt;opencode.&lt;/code&gt; 開頭。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;內部外掛示例&lt;/th&gt;
&lt;th scope="col"&gt;所處集合&lt;/th&gt;
&lt;th scope="col"&gt;實際職責&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opencode.agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pre&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;註冊原生助手的基礎定義&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opencode.tool.shell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pre&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;註冊 Shell 工具，結合原生執行、許可權和執行狀態服務&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原生供應商、搜尋及其他工具外掛&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pre&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提供模型、搜尋和工具的基礎能力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opencode.config.agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;post&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;讀取助手配置和相關 Markdown 檔案，把結果應用到助手清單&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置供應商、技能、策略及模型變體外掛&lt;/td&gt;
&lt;td&gt;&lt;code&gt;post&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在前面貢獻的能力上應用配置或後續處理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;這也解釋了自定義助手與內部外掛的關係：使用者編輯助手配置，真正負責讀取、監聽和應用這些配置的是 &lt;code&gt;opencode.config.agent&lt;/code&gt;。每個自定義助手是一份 Agent 定義，並不需要對應一個獨立外掛。外部外掛或 SDK 外掛也可以註冊 Agent，之後再由配置外掛調整。&lt;/p&gt;
&lt;p&gt;內建外掛同樣參與配置中的啟停選擇。例如 &lt;code&gt;-opencode.config.agent&lt;/code&gt; 會讓負責應用助手配置的外掛退出當前啟用集合，相應配置能力也會受到影響。是否保留某個內建外掛，應根據其實際職責判斷。&lt;/p&gt;
&lt;p&gt;需要增加依賴 Core 私有服務的原生功能時，通常要修改 OpenCode 原始碼，把外掛加入內建集合；若新增服務依賴，還需要調整相應服務裝配。這類變更隨 OpenCode 一起構建、釋出和升級。內部外掛適合引擎自身功能，普通業務工具優先通過公開介面接入。&lt;/p&gt;
&lt;p&gt;依據：內部外掛集合與服務注入（&lt;code&gt;packages/core/src/plugin/internal.ts&lt;/code&gt;）、原生助手外掛（&lt;code&gt;packages/core/src/plugin/agent.ts&lt;/code&gt;）、助手配置外掛（&lt;code&gt;packages/core/src/config/plugin/agent.ts&lt;/code&gt;）、Shell 工具外掛（&lt;code&gt;packages/core/src/tool/plugin/shell.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外部外掛通過檔案或包給現有 OpenCode 服務增加能力。&lt;/strong&gt; “外部”指程式碼來源不在內建集合中，執行時仍然裝入 OpenCode 程序。載入器向它提供公開 Plugin Context，不會像內部外掛一樣額外注入 Core 服務。它仍能使用執行環境允許的檔案、網路等能力；這不是一個獨立程序或安全沙箱。&lt;/p&gt;
&lt;p&gt;外部外掛有兩條發現路徑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自動發現&lt;/strong&gt;：掃描原生配置目錄下的 &lt;code&gt;plugin/&lt;/code&gt; 和 &lt;code&gt;plugins/&lt;/code&gt;。當前實現直接識別 &lt;code&gt;.ts&lt;/code&gt;、&lt;code&gt;.js&lt;/code&gt; 檔案，也支援解析包目錄及符合條件的符號連結。包目錄會嘗試 &lt;code&gt;package.json&lt;/code&gt; 中字串形式的 &lt;code&gt;exports&lt;/code&gt;、&lt;code&gt;module&lt;/code&gt;、&lt;code&gt;main&lt;/code&gt;，再嘗試 &lt;code&gt;index.ts&lt;/code&gt;、&lt;code&gt;index.js&lt;/code&gt;；不能把這裡的簡單發現邏輯等同於支援所有複雜包匯出規則。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顯式配置&lt;/strong&gt;：&lt;code&gt;plugins&lt;/code&gt; 接受相對路徑、絕對路徑、檔案 URL 和可解析的包。相對路徑按配置檔案所在目錄解析；本地路徑進入模組載入，包目標交給包解析器，並嘗試 &lt;code&gt;server&lt;/code&gt; 子入口或包根入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自動發現結果先進入操作列表，顯式配置隨後應用，因此可以在配置中關閉自動發現的外掛。自動發現並不直接列舉 &lt;code&gt;.mjs&lt;/code&gt; 檔案；使用此類入口時，需要顯式配置其路徑，並由執行環境完成模組載入。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  "plugins": [
    {
      "package": "./plugins/example.ts",
      "options": {
        "serviceUrl": "https://api.example.com"
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述路徑是配置格式示例。外掛通過 &lt;code&gt;ctx.options&lt;/code&gt; 讀取 &lt;code&gt;serviceUrl&lt;/code&gt;，並自行檢查必填項、取值範圍和地址約束；傳入物件不代表業務配置已經完成校驗。&lt;/p&gt;
&lt;p&gt;外部模組必須預設匯出一個帶有 &lt;code&gt;id + effect&lt;/code&gt; 或 &lt;code&gt;id + setup&lt;/code&gt; 的外掛物件：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;編寫方式&lt;/th&gt;
&lt;th scope="col"&gt;初始化入口&lt;/th&gt;
&lt;th scope="col"&gt;接入方式與清理&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Effect&lt;/td&gt;
&lt;td&gt;&lt;code&gt;effect(ctx)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;由宿主在外掛 Scope 內執行；資源繫結 scoped 生命週期或 finalizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promise&lt;/td&gt;
&lt;td&gt;&lt;code&gt;setup(ctx)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;載入器通過 &lt;code&gt;fromPromise&lt;/code&gt; 轉為 Effect 外掛；可以返回 cleanup 函式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;@opencode-ai/plugin/effect&lt;/code&gt; 提供 Effect API，包根入口 &lt;code&gt;@opencode-ai/plugin&lt;/code&gt; 提供當前分支的 Promise API。舊版外掛 API 有獨立的 &lt;code&gt;v1&lt;/code&gt; 入口，不能僅憑“同樣是 OpenCode 外掛”假定介面相容。&lt;/p&gt;
&lt;p&gt;外部外掛的典型載入鏈路為：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;配置目录 / plugins 配置
  → ConfigPluginSource 生成有序操作与本地文件时间戳
  → PluginSupervisor 解析路径或包、导入模块、校验默认导出
  → 适配 Promise 定义，并注入该来源的 options
  → Plugin.Service 创建 Location 内的插件实例
  → 注册助手、工具、钩子及需要清理的资源
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;檔案或配置變化可以觸發重新生成外掛集合；這是發現與監聽支援範圍內的更新，不是任意依賴檔案都能即時重新整理。特別是顯式配置目錄入口時，當前實現不保證目錄內部檔案變化會觸發過載。包版本的升級也應通過明確的部署或配置變更管理。&lt;/p&gt;
&lt;p&gt;模組匯入或匯出格式校驗失敗會記錄載入警告，該次來源被跳過；進入啟用階段後的初始化失敗則由統一外掛管理處理。診斷時需要分別確認“檔案已被發現”“模組已成功解析”“外掛已啟用”和“能力已註冊”，僅看到配置項不足以證明工具可用。&lt;/p&gt;
&lt;p&gt;依據：來源發現與本地監聽（&lt;code&gt;packages/core/src/config/plugin/source.ts&lt;/code&gt;）、模組載入與配置注入（&lt;code&gt;packages/core/src/plugin/supervisor.ts&lt;/code&gt;）、公開包入口（&lt;code&gt;packages/plugin/package.json&lt;/code&gt;）、Promise 介面卡（&lt;code&gt;packages/plugin/src/promise/adapter.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SDK 外掛由嵌入式宿主直接註冊，適合把 OpenCode 作為應用內部的執行引擎。&lt;/strong&gt; 本文的 SDK 特指當前分支的 &lt;code&gt;@opencode-ai/sdk-next&lt;/code&gt; 嵌入式宿主。&lt;code&gt;OpenCode.create()&lt;/code&gt; 在應用程序內建立執行環境和記憶體中的 HTTP 路由呼叫鏈，不需要為這條內部呼叫鏈開啟網路監聽；工具、模型和業務服務仍可發起各自的網路請求。&lt;/p&gt;
&lt;p&gt;宿主通過 &lt;code&gt;opencode.plugin(definition)&lt;/code&gt; 直接提交 Effect 外掛物件，省去外部模組發現、包解析和預設匯出校驗。這不是向一個已經啟動的遠端 OpenCode 服務上傳程式碼。普通 HTTP 客戶端和外掛 Context 中的 &lt;code&gt;ctx.plugin.list()&lt;/code&gt; 也不等於這個嵌入式註冊入口。&lt;/p&gt;
&lt;p&gt;下例展示嵌入式宿主的外掛註冊與查詢方式，需使用與研究基線匹配的依賴版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ts"&gt;import { AbsolutePath, Location, OpenCode } from "@opencode-ai/sdk-next"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const opencode = yield* OpenCode.create()

  yield* opencode.plugin({
    id: "example.reviewer",
    effect: (ctx) =&amp;gt;
      ctx.agent.transform((draft) =&amp;gt; {
        draft.update("reviewer", (agent) =&amp;gt; {
          agent.description = "检查代码并给出修改建议"
          agent.system = "分析代码质量，并说明建议的依据。"
          agent.mode = "primary"
        })
      }).pipe(Effect.asVoid),
  })

  const location = Location.Ref.make({
    directory: AbsolutePath.make(process.cwd()),
  })
  return yield* opencode.plugin.list({ location })
})

const result = await Effect.runPromise(program.pipe(Effect.scoped))
console.log(result.data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例中的助手只定義用途和提示詞，許可權需要另行配置。示例執行結束會關閉擁有宿主的 Scope；真實嵌入式應用應讓該 Scope 覆蓋整個服務生命週期。&lt;/p&gt;
&lt;p&gt;SDK 外掛的範圍和更新方式需要分兩層理解：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;層次&lt;/th&gt;
&lt;th scope="col"&gt;管理內容&lt;/th&gt;
&lt;th scope="col"&gt;生效範圍&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;宿主登錄檔&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Map&amp;lt;plugin.id, Versioned&amp;gt;&lt;/code&gt;，儲存定義和遞增修訂號&lt;/td&gt;
&lt;td&gt;同一個嵌入式宿主共享；不同宿主擁有各自的登錄檔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location 啟用集合&lt;/td&gt;
&lt;td&gt;該目錄上下文內的外掛例項、Transform、Hook 與 Scope&lt;/td&gt;
&lt;td&gt;每個 Location 分別啟用與清理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;每次註冊都會發布 &lt;code&gt;sdk.plugin.updated&lt;/code&gt;。已啟動的 Location 收到更新後重新生成外掛集合；之後新啟動或被回收後重新啟動的 Location，從宿主登錄檔讀取當前定義。因此，“宿主只註冊一次”不意味著外掛初始化只執行一次。閉包中的可變物件也可能被同一宿主的多個 Location 例項共享，不能預設把它當作單個會話的狀態。&lt;/p&gt;
&lt;p&gt;同一 SDK 登錄檔內再次提交相同 ID，會替換定義併產生新修訂號；不同宿主之間互不覆蓋。註冊返回僅表示定義已寫入併發布更新，不代表所有 Location 已同步完成啟用。需要確認生效時，應檢查目標 Location 的實際外掛和能力清單，並在必要時等待啟用事件。&lt;/p&gt;
&lt;p&gt;當前 SDK 登錄檔只提供 &lt;code&gt;register&lt;/code&gt; 與 &lt;code&gt;all&lt;/code&gt;，沒有獨立的 &lt;code&gt;unregister&lt;/code&gt; 介面。各 Location 的配置仍可以通過外掛 ID 停用其啟用，但這不會從宿主登錄檔刪除定義。關閉宿主會清理執行資源；重新建立宿主需要重新註冊外掛，原來的記憶體註冊不是持久安裝記錄。&lt;/p&gt;
&lt;p&gt;SDK 註冊入口接收 Effect 外掛，外部載入器對 Promise 外掛的自動適配不會在這裡自動發生。需要複用 Promise 定義時，應顯式使用對應的 &lt;code&gt;fromPromise&lt;/code&gt; 介面卡。SDK 路徑沒有額外的 &lt;code&gt;options&lt;/code&gt; 注入，公開 Context 預設提供空物件；宿主通常用工廠函式或閉包傳入自己的配置與業務客戶端。&lt;/p&gt;
&lt;p&gt;SDK 外掛也不會自動取得內部外掛的 Core 服務環境。宿主可以主動傳入依賴，但直接依賴 Core 私有服務意味著承擔額外的版本耦合。研究基線中的 &lt;code&gt;sdk-next&lt;/code&gt; 還是 &lt;code&gt;private: true&lt;/code&gt; 的過渡包，不能把這個示例視為已釋出穩定 npm API 的安裝承諾。&lt;/p&gt;
&lt;p&gt;依據：嵌入式宿主入口（&lt;code&gt;packages/sdk-next/src/opencode.ts&lt;/code&gt;）、SDK 登錄檔（&lt;code&gt;packages/core/src/plugin/sdk.ts&lt;/code&gt;）、SDK 包狀態（&lt;code&gt;packages/sdk-next/package.json&lt;/code&gt;）、公開外掛宿主（&lt;code&gt;packages/core/src/plugin/host.ts&lt;/code&gt;）。嵌入式測試原始碼（&lt;code&gt;packages/sdk-next/test/embedded.test.ts&lt;/code&gt;）包含跨 Location 更新、Location 回收後重新啟用及宿主間隔離的用例；本文核對了這些用例，未獨立執行測試。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三種來源會合併成一個有順序的啟用集合。&lt;/strong&gt; 在啟用項確定之後，當前順序是：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;内部 pre → SDK 注册插件 → 外部文件 / 包插件 → 内部 post
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConfigPluginSource&lt;/code&gt; 負責外部來源與配置操作；&lt;code&gt;PluginSupervisor&lt;/code&gt; 把它們與內部、SDK 定義合併；&lt;code&gt;Plugin.Service&lt;/code&gt; 負責最終的重複 ID 檢查、Scope、初始化與替換。不要把不同來源理解為三套獨立執行引擎。&lt;/p&gt;
&lt;p&gt;這個順序意味著外部和 SDK 外掛貢獻的助手等能力，仍可能被後置配置外掛調整。需要確定助手的最終提示詞、模型或許可權時，應讀取最終能力清單，而不是隻看某個外掛中的初始定義。&lt;/p&gt;
&lt;p&gt;外掛 ID、包名和入口路徑承擔不同職責。比如配置通過檔案載入一個匯出 ID 為 &lt;code&gt;example.reviewer&lt;/code&gt; 的外掛，停用時應寫 &lt;code&gt;-example.reviewer&lt;/code&gt;。選擇器支援精確 ID、&lt;code&gt;prefix.*&lt;/code&gt; 和 &lt;code&gt;*&lt;/code&gt;；配置操作按順序執行，後續操作可以重新啟用已有定義。&lt;/p&gt;
&lt;p&gt;有兩個容易混淆的重名規則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同一 SDK 登錄檔內重複註冊相同 ID&lt;/strong&gt;：更新該條定義，屬於登錄檔支援的替換操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不同有效來源最終貢獻相同 ID&lt;/strong&gt;：啟用階段按重複 ID 拒絕這一輪集合，不是按來源優先順序靜默覆蓋。外部檔案與 SDK 註冊不要同時提交同一外掛定義。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，當前 &lt;code&gt;Plugin.Service&lt;/code&gt; 在整個有序集合的 ID 與版本完全一致時跳過啟用；一旦集合發生變化，會遍歷新集合，清理並重新初始化其中已有的外掛。因此，一個來源發生更新也可能讓其他未修改的外掛重新初始化。三類外掛都應正確釋放資源，避免把初始化當作只執行一次的業務動作。替換失敗會嘗試恢復該外掛舊版本，但無法回滾已產生的外部業務副作用。&lt;/p&gt;
&lt;p&gt;依據：來源合併與啟停順序（&lt;code&gt;packages/core/src/plugin/supervisor.ts&lt;/code&gt;）、統一啟用與替換（&lt;code&gt;packages/core/src/plugin.ts&lt;/code&gt;）、配置與載入順序測試原始碼（&lt;code&gt;packages/core/test/config/plugin.test.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;h2 id="plugin-核心实现一-transform"&gt;Plugin 核心實現一：Transform&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Transform 用來宣告能力，Hook 用來處理一次執行。&lt;/strong&gt; 選擇擴充套件點時，應先確定需求屬於哪種領域。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;需求&lt;/th&gt;
&lt;th scope="col"&gt;原生擴充套件介面&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;註冊、修改或移除助手&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.agent.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;註冊實際可執行工具&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.tool.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;提供技能說明與資源入口&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.skill.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;增加命令&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.command.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;調整供應商、模型目錄與預設選擇&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.catalog.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;提供參考資料來源&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.reference.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置認證和連線方式&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.integration.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;提供搜尋後端&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.websearch.transform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改本輪上下文與可見工具&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.session.hook("context", ...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;檢查工具輸入或整理結果&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.tool.hook(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;調整模型 SDK 或模型例項&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.aisdk.hook(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;調整模型 HTTP 請求或響應&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.session.hook("http.request" / "http.response", ...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;調整命令建立引數&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.shell.hook("create.before", ...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;觀察即時事件&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.event.subscribe()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;建立、投遞、等待或中斷會話&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.session.*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Agent、Catalog、Command、Integration、Reference、Skill 等狀態領域儲存活動 Transform，並從基礎狀態按順序重建。解除安裝某個外掛後，對應 Transform 被移除，再重新生成結果。Transform 應只編輯本次回撥提供的 Draft，不應儲存 Draft 引用，也不適合在重建時產生外部業務副作用。需要外部資料時，先載入並儲存資料，再觸發相應領域的 &lt;code&gt;reload()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Tool 的底層實現採用與 Scope 繫結的工具註冊和請求快照。它與上述領域具有相同的生命週期歸屬，但不能推斷所有 &lt;code&gt;transform&lt;/code&gt; 都使用完全相同的狀態容器或返回型別。&lt;/p&gt;
&lt;h2 id="plugin-核心实现二-hook"&gt;Plugin 核心實現二：Hook&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hook（執行鉤子）是宿主在執行流程中預留的擴充套件點。&lt;/strong&gt; 外掛向某個擴充套件點註冊回撥函式，OpenCode 執行到對應節點時，便自動呼叫這些回撥，並傳入該節點的上下文資料。外掛可以按介面約定讀取資料、調整引數或處理結果，從而參與模型請求、工具執行等流程。&lt;/p&gt;
&lt;p&gt;Hook 的使用分為註冊和觸發兩個階段：外掛初始化時，通過 &lt;code&gt;ctx.session.hook(...)&lt;/code&gt;、&lt;code&gt;ctx.tool.hook(...)&lt;/code&gt; 等介面宣告要參與的節點；實際執行到該節點時，宿主才執行註冊的回撥。註冊一次後，回撥可以隨著對應節點再次執行而多次觸發。宿主會等待回撥完成，再按該介面的約定繼續處理；例如 &lt;code&gt;tool.execute.before&lt;/code&gt; 允許通過 &lt;code&gt;Tool.Error&lt;/code&gt; 拒絕這次工具執行。&lt;/p&gt;
&lt;p&gt;Transform 負責構建可用能力，Hook 負責調整能力在具體執行節點的行為。例如，註冊一個工具使用 &lt;code&gt;ctx.tool.transform&lt;/code&gt;；檢查某次工具呼叫的輸入，可以使用 &lt;code&gt;ctx.tool.hook("execute.before", ...)&lt;/code&gt;；調整即將傳送給模型的上下文，可以使用 &lt;code&gt;ctx.session.hook("context", ...)&lt;/code&gt;。Hook 參與當前呼叫鏈，而 &lt;code&gt;ctx.event.subscribe()&lt;/code&gt; 用於訂閱事件並觀察執行情況。&lt;/p&gt;
&lt;p&gt;執行 Hook 按註冊順序序列執行，後註冊的 Hook 可以看到前面的修改。多個外掛修改同一欄位時，需要檢查最終載入順序；內建外掛分為前置與後置集合，不能簡單假定“外部外掛永遠最後覆蓋”。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;Hook&lt;/th&gt;
&lt;th scope="col"&gt;可修改或觀察的內容&lt;/th&gt;
&lt;th scope="col"&gt;注意事項&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session.context&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系統提示、訊息、本輪工具定義&lt;/td&gt;
&lt;td&gt;助手和模型標識是上下文身份；修改訊息僅代表本輪請求調整，不等於追加持久歷史&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tool.execute.before&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;工具輸入&lt;/td&gt;
&lt;td&gt;可以返回 &lt;code&gt;Tool.Error&lt;/code&gt; 阻止執行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tool.execute.after&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;成功結果或工具錯誤&lt;/td&gt;
&lt;td&gt;用於整理結果和補充資訊&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session.http.request/response&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型 HTTP 請求與響應&lt;/td&gt;
&lt;td&gt;可能接觸憑據和完整輸入，需控制記錄內容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aisdk.sdk/language&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SDK 與實際模型例項&lt;/td&gt;
&lt;td&gt;適合供應商適配&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shell.create.before&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;命令、目錄、時限、Shell 與環境變數&lt;/td&gt;
&lt;td&gt;字串規則本身不能提供系統級隔離&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;當前公開 Hook 型別中，只有 &lt;code&gt;tool.execute.before&lt;/code&gt; 聲明瞭可恢復的 &lt;code&gt;Tool.Error&lt;/code&gt; 失敗通道。其他 Hook 不應被當作任意丟擲業務錯誤的通用中介軟體。&lt;/p&gt;
&lt;p&gt;依據：Plugin Context（&lt;code&gt;packages/plugin/src/effect/plugin.ts&lt;/code&gt;）、狀態重建（&lt;code&gt;packages/core/src/state.ts&lt;/code&gt;）、Hook 註冊與執行（&lt;code&gt;packages/core/src/plugin/hooks.ts&lt;/code&gt;）、工具註冊與快照（&lt;code&gt;packages/core/src/tool.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;h2 id="工具示例与生命周期"&gt;工具示例與生命週期&lt;/h2&gt;
&lt;h3 id="示例一-使用-plugin-transform-定义-tool"&gt;示例一：使用 Plugin Transform 定義 Tool&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;工具包含模型可見的定義和宿主執行的函式。&lt;/strong&gt; 模型生成工具名與輸入後，OpenCode 使用本輪捕獲的工具能力處理呼叫。工具函式接收宿主提供的 &lt;code&gt;sessionID&lt;/code&gt;、&lt;code&gt;agent&lt;/code&gt;、&lt;code&gt;messageID&lt;/code&gt; 和呼叫 &lt;code&gt;id&lt;/code&gt;，長操作可以通過 &lt;code&gt;context.progress()&lt;/code&gt; 提供進度。&lt;/p&gt;
&lt;p&gt;下面是一個使用 Effect API 的獨立工具示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ts"&gt;import { Plugin } from "@opencode-ai/plugin/effect"
import { Effect, Schema } from "effect"

export default Plugin.define({
  id: "example.echo",
  effect: Effect.fn(function* (ctx) {
    yield* ctx.tool.transform((tools) =&amp;gt; {
      tools.add({
        name: "echo",
        description: "返回收到的文字",
        input: Schema.Struct({ text: Schema.String }),
        output: Schema.Struct({ text: Schema.String }),
        options: { codemode: false },
        execute: ({ text }) =&amp;gt;
          Effect.succeed({
            output: { text },
            content: text,
          }),
      })
    })
  }),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果欄位需要按用途區分：&lt;code&gt;output&lt;/code&gt; 是宣告輸出 Schema 後供程式使用的結構化值；&lt;code&gt;content&lt;/code&gt; 提供給模型並進入會話內容；&lt;code&gt;metadata&lt;/code&gt; 承載有界的附加狀態。未宣告輸出 Schema 卻返回 &lt;code&gt;output&lt;/code&gt;，在當前執行實現中屬於錯誤。&lt;/p&gt;
&lt;p&gt;原始碼中有兩個比介面名稱更具體的校驗細節：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tool.execute.before&lt;/code&gt; 先於工具執行函式中的 Schema 解碼執行。因此 Hook 讀取輸入時仍應把它當作未知結構；Hook 修改後的輸入才進入後續解碼。&lt;/li&gt;
&lt;li&gt;Effect Schema 和支援的 Standard Schema 會執行執行時輸入校驗；純 JSON Schema 分支直接傳遞輸入。純 JSON Schema 輸出分支也只檢查是否為 JSON 值，不逐項驗證宣告的約束。業務工具不能把“向模型提供了 JSON Schema”視為“服務端已驗證引數”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在當前工具通道里，執行前 Hook 自身失敗會直接中止後續流程；不能假定 &lt;code&gt;execute.after&lt;/code&gt; 總會為這類拒絕觸發。審計設計需要覆蓋拒絕路徑，而不是隻監聽成功執行之後。&lt;/p&gt;
&lt;p&gt;預期、可恢復的工具失敗可以對映為 &lt;code&gt;Tool.Error&lt;/code&gt;。取消執行、未知程式缺陷和成功結果需要保留不同語義，不應統一吞掉後返回一條普通成功文本。&lt;/p&gt;
&lt;p&gt;依據：工具型別（&lt;code&gt;packages/schema/src/tool.ts&lt;/code&gt;）、工具執行封裝（&lt;code&gt;packages/core/src/tool.ts&lt;/code&gt;）、實際引數校驗（&lt;code&gt;packages/core/src/tool/runtime.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;h3 id="示例二-使用-code-mode-组合工具调用"&gt;示例二：使用 Code Mode 組合工具呼叫&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Code Mode 決定一部分工具如何呈現與組合。&lt;/strong&gt; 當前分支會把 &lt;code&gt;codemode: false&lt;/code&gt; 的工具直接暴露給模型；其他工具可進入 Code Mode 目錄，通過 &lt;code&gt;execute&lt;/code&gt; 執行一小段程式碼來組合呼叫和處理結果。Code Mode 中的實際工具呼叫仍回到宿主的工具執行路徑。&lt;/p&gt;
&lt;p&gt;這適合連續查詢、過濾和聚合等場景。Code Mode 執行環境限制直接檔案訪問、匯入等操作，但這類限制不能等同於整個 OpenCode 服務或外部外掛都執行在系統沙箱中。上面的 &lt;code&gt;echo&lt;/code&gt; 示例設定了 &lt;code&gt;codemode: false&lt;/code&gt;，展示工具直接暴露給模型的方式。&lt;/p&gt;
&lt;p&gt;模型請求捕獲當時的工具註冊快照。外掛熱更新不會把已經準備好的請求視為使用全新的工具清單；後續請求會重新準備能力。外掛貢獻的工具執行函式本身也應避免依賴已被清理的無歸屬後臺資源。&lt;/p&gt;
&lt;p&gt;依據：工具分類與快照（&lt;code&gt;packages/core/src/tool.ts&lt;/code&gt;）、Code Mode（&lt;code&gt;packages/core/src/codemode/tool.ts&lt;/code&gt;）、本輪工具可用性檢查（&lt;code&gt;packages/core/src/session/model-request.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;h3 id="plugin-的-location"&gt;Plugin 的 Location&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;外掛例項按 Location 管理，持久業務狀態需要單獨儲存。&lt;/strong&gt; Location 表示由目錄和可選原生 Workspace 標識構成的執行上下文。一個 OpenCode 程序可以同時承載多個 Location，同一外掛可能在多個 Location 中分別啟用。&lt;/p&gt;
&lt;p&gt;每個外掛例項擁有 Scope，用於歸屬 Transform、Hook、工具註冊和正確繫結的資源。Scope 關閉時，這些註冊會清理。外掛自己的定時器、網路訂閱和檔案監聽也需要繫結清理邏輯；Promise 外掛可以從 &lt;code&gt;setup&lt;/code&gt; 返回 cleanup，Effect 外掛使用相應的 scoped 資源與 finalizer。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;首次加载 → 创建 Scope → 注册能力与钩子 → 服务会话
文件或配置变化 → 替换插件 → 清理旧 Scope → 激活新版本
新版本激活失败 → 尝试恢复旧版本 → 恢复失败则停用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地外掛入口檔案和配置來源存在變化監聽；顯式入口是目錄時，目錄內部檔案修改不具備同樣的自動熱更新保證。載入、更新和停止還受實際檔案監聽與執行狀態影響，因此“支援熱更新”不能替代讀取原生能力清單的驗證。&lt;/p&gt;
&lt;p&gt;外掛恢復只能恢復程式碼和註冊，無法回滾已經操作檔案、資料庫或遠端服務產生的副作用。定時任務、審批狀態、重試次數、冪等標識應由可靠儲存維護。記憶體裡的會話狀態至少按 &lt;code&gt;sessionID&lt;/code&gt; 隔離；模組級全域性變數還可能跨外掛例項共享，需要格外謹慎。&lt;/p&gt;
&lt;p&gt;依據：外掛 Scope 與恢復（&lt;code&gt;packages/core/src/plugin.ts&lt;/code&gt;）、外掛檔案監聽（&lt;code&gt;packages/core/src/config/plugin/source.ts&lt;/code&gt;）。&lt;/p&gt;
&lt;h2 id="plugin-最佳实践"&gt;Plugin 最佳實踐&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;外掛開發優先複用已有配置與公開介面&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只調整提示詞、用途或步驟上限時，使用 Agent 配置；&lt;/li&gt;
&lt;li&gt;提供方法和知識時，使用 Skill；&lt;/li&gt;
&lt;li&gt;增加實際操作時，註冊 Tool；&lt;/li&gt;
&lt;li&gt;需要在&lt;strong&gt;執行節點&lt;/strong&gt;調整行為時，使用對應 Hook；&lt;/li&gt;
&lt;li&gt;特別的，外掛需要持久化業務狀態或呼叫外部服務時，應顯式設計儲存、認證、事務與冪等處理，不依賴外掛記憶體狀態承擔這些保證。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;單一職責外掛可以只有一個 TS 擴充套件檔案。只有多個能力需要獨立啟停、版本或失敗策略時，才拆成多個外掛 ID。普通外部外掛應依賴公開外掛、客戶端和 schema 介面，避免匯入 Core 私有實現。分發時需明確相容的宿主版本、依賴版本、模組入口和構建方式。&lt;/p&gt;
&lt;p&gt;升級 Plugin 時，應至少檢查以下行為，而不是隻確認外掛模組能夠匯入：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent、工具和技能是否進入實際能力清單，解除安裝後貢獻是否消失。&lt;/li&gt;
&lt;li&gt;多外掛順序、配置覆蓋和熱更新失敗時的最終狀態是否符合預期。&lt;/li&gt;
&lt;li&gt;工具輸入、輸出、取消和執行拒絕是否保持正確語義。&lt;/li&gt;
&lt;li&gt;會話模型與助手選擇是否真正生效，子助手是否具有預期許可權和上下文。&lt;/li&gt;
&lt;li&gt;自定義工具及其呼叫的服務是否正確檢查會話歸屬、業務授權、狀態轉換和重複請求。&lt;/li&gt;
&lt;li&gt;服務重啟後，持久狀態能否恢復，即時事件缺失是否得到正確處理。&lt;/li&gt;
&lt;li&gt;工具結果與事件是否準確表達進度、完成、失敗和取消狀態。&lt;/li&gt;
&lt;/ul&gt;
</description>
    <category>Agent 開發</category><category>外掛開發</category></item>
    <item>
      <title>再讀整潔架構之道（六）邊界</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</link>
      <pubDate>Mon, 29 Jul 2024 06:13:15 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</guid>
      <description>&lt;h2 id="什么是边界"&gt;什麼是邊界&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;軟體架構設計本身就是一門劃分邊界的藝術。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;邊界的作用是將軟體劃分為各種元素，以便約束邊界兩側的依賴關係。在專案初期劃分的邊界可以方便我們將一些決策儘量延後進行，確保未來這些決策不會對系統額&lt;strong&gt;核心業務邏輯&lt;/strong&gt;造成干擾。劃分邊界同時可以降低或消除架構中不必要耦合，系統中存在的耦合——尤其是那些過早做出的、不成熟的決策所導致的耦合（如採用的框架，資料庫等元素，這些細節應該被推遲）是系統最消耗人力資源的部分。&lt;/p&gt;
&lt;p&gt;那麼如何劃分邊界呢？可以遵循如下的基本原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;邊界應該劃分在那些無關的事情中間：如 GUI 與業務邏輯，這意味著輸入輸出（GUI）對於業務邏輯來說並不重要，業務邏輯可以選擇多種不同的 GUI 實現；&lt;/li&gt;
&lt;li&gt;邊界線應該沿著系統的變更軸來畫。也就是說，位於邊界線兩側的元件應該以不同原因、不同速率變化著。指導變更軸心的原則包括模組/類級別的SRP原則和元件級別的CCP原則。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在瞭解劃分邊界的重要作用，並且瞭解如何劃分邊界後，可以進一步探討該如何劃分邊界了，即劃分邊界的基本流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先將系統分割成元件，其中一部分是系統的核心業務邏輯元件，另一部分是與核心業務無關但負責提供必要功能的外掛；&lt;/li&gt;
&lt;li&gt;通過對原始碼的修改，讓這些非核心元件依賴於系統的核心業務邏輯元件；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;劃分邊界是一種對依賴反轉原則和穩定抽象原則的具體應用，依賴剪頭應該由底層具體實現指向高層抽象的方向。&lt;/p&gt;
&lt;h2 id="边界剖析"&gt;邊界剖析&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;一個系統的架構是由一系列軟體元件以及它們之間的邊界共同定義的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;邊界具有多種形式，本章以跨邊界呼叫為例來剖析邊界。&lt;/p&gt;
&lt;p&gt;在執行時，跨邊界呼叫指的是邊界線一側的函式呼叫另一側的函式，並同時傳遞資料的行為。構造合理的跨邊界呼叫需要我們對原始碼中的依賴關係進行合理管控，若不對依賴管控，可能導致一個模組的原始碼發生變更時，其他模組的原始碼也可能會隨之發生變更或重新編譯，並需要重新部署，劃清邊界有助於減少這種情況的發生。&lt;/p&gt;
&lt;p&gt;跨邊界呼叫包含不同的類別：原始碼層次、部署層次、服務層次以及物理邊界。&lt;/p&gt;
&lt;h3 id="源码层次的跨边界调用"&gt;原始碼層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;原始碼層次的跨邊界調用出現在單體架構中，這類架構一般都需要利用某種動態形式的多型來管理其內部的依賴關係。&lt;/p&gt;
&lt;p&gt;最簡單的跨邊界呼叫形式，是由低層客戶端來呼叫高層服務函式，這種依賴關係在執行時和編譯時會保持指向一致，都是從低層元件指向高層元件。&lt;/p&gt;
&lt;p&gt;（原示意圖暫缺）&lt;/p&gt;
&lt;p&gt;但當高層元件中的客戶端需要呼叫低層元件中的服務時，我們就需要運用動態形式的多型來反轉依賴關係（圖中的Service介面是某種形式的SPI）。&lt;/p&gt;
&lt;p&gt;（原示意圖暫缺）&lt;/p&gt;
&lt;h3 id="部署层次的跨边界调用"&gt;部署層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;部署層次的跨邊界呼叫將其所有可部署的單元打包成一個便於操作的檔案格式，除這一點以外，這種按部署層次解耦的元件與單體結構幾乎是一樣的。&lt;/p&gt;
&lt;p&gt;部署層次的跨邊界呼叫涉及到物理邊界，因為不同部署的單元需要跨越物理邊界來完成通訊。常見的物理邊界包括動態連結庫、執行緒、本地程序等。&lt;/p&gt;
&lt;h3 id="服务层次的跨边界调用"&gt;服務層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;服務是系統架構中最強的邊界形式，一個服務就是一個程序。在劃分架構邊界時，一定要儘可能地控制通訊次數。在這個層次上通訊必須能夠適應高延時情況。除此之外，我們可以在服務層次上使用與本地程序相同的規則。也就是讓較低層次服務成為較高層次服務的“外掛”。&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（五）軟體架構</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</link>
      <pubDate>Sun, 28 Jul 2024 19:23:23 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</guid>
      <description>&lt;p&gt;是時候進入軟體架構了。在之前已經介紹了模組/類、元件的相關內容，這一部分會重點介紹軟體架構的基本定義。&lt;/p&gt;
&lt;h2 id="写在最前面"&gt;寫在最前面&lt;/h2&gt;
&lt;p&gt;🌟軟體架構師自身需要是程式設計師，並且必須一直堅持做一線程式設計師，如果不親身承受因系統設計而帶來的麻煩，就體會不到設計不佳所帶來的痛苦，接著就會逐漸迷失正確的設計方向。&lt;/p&gt;
&lt;h2 id="什么是软件架构"&gt;什麼是軟體架構？&lt;/h2&gt;
&lt;p&gt;軟體架構工作的實質就是討論規劃如何將系統切分成元件，並安排好元件之間的排列關係與通訊方式，即確定邊界。&lt;/p&gt;
&lt;p&gt;軟體架構設計的目的一般有兩個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;為了在工作中更好的對這些元件進行研發、部署、執行以及維護；&lt;/li&gt;
&lt;li&gt;如果想設計一個便於推進各項工作的系統，其策略就是要在設計中儘可能長時間地&lt;strong&gt;保留儘可能多的可選項&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一個軟體系統的架構質量和該系統是否能正常工作（行為）的關係並不大，畢竟世界上有很多架構設計糟糕但是工作正常的軟體系統。真正的麻煩往往會出現在這個軟體系統的開發、部署以及後續的補充開發中。這也間接反映的作者的觀點：軟體系統的架構價值在一定程度上大於其行為價值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;軟體架構設計的目標如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心目標：一個良好的架構設計應該圍繞著用例來展開，良好的架構設計應該只關注用例，並能將它們與其他的周邊因素隔離，隔離的方式是儘可能推遲選擇，保留更多的可選項；&lt;/li&gt;
&lt;li&gt;主要目標：支撐軟體系統的全生命週期，設計良好的架構可以讓系統便於理解、易於修改、方便維護，並且能輕鬆部署；&lt;/li&gt;
&lt;li&gt;終極目標：最大化程式設計師的生產力（解放生產力），同時最小化系統的&lt;strong&gt;總&lt;/strong&gt;運營成本；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;軟體架構的設計重點是將&lt;strong&gt;策略&lt;/strong&gt;彼此分離，然後將它們按照變更的方式進行重新分組，也就是劃分出邊界，在這一部分可以依據的原則包括SRP、CCP、CSP，SDP、SAP等等。&lt;/p&gt;
&lt;h2 id="软件架构的职责"&gt;軟體架構的職責&lt;/h2&gt;
&lt;p&gt;軟體架構具有開發、部署、執行以及維護多個方面的職責。&lt;/p&gt;
&lt;p&gt;在開發上：軟體架構要方便軟體系統的開發，因此不同的團隊應該使用不同的軟體架構設計。這在一定程度上也體現了康威定律。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;為什麼康維定律能夠體現團隊的架構？看影片瞭解更多：&lt;a href="https://www.bilibili.com/video/BV1bb421E7i6/?spm_id_from=333.337.search-card.all.click&amp;amp;vd_source=8c0f6bcaf6b2e92f574553f45e565994"&gt;康威定律：為什麼你的架構會反映團隊結構？_嗶哩嗶哩_bilibili&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在部署上：軟體架構要實現軟體的一鍵式輕鬆部署；&lt;/p&gt;
&lt;p&gt;在執行上，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正如前面提到的，架構對於軟體系統的高效執行遠小於其他幾個方面，作者認為這主要源於增加硬體資源可以彌補架構在執行方面的考慮不足，而架構設計通常需要更為昂貴的人力資源；&lt;/li&gt;
&lt;li&gt;除了架構設計對於高效執行的作用，架構還應該能反應系統在執行時的需求，也就是說，設計良好的系統架構應該可以使開發人員對系統的執行過程一目瞭然。架構應該起到揭示系統執行過程的作用，架構應該將系統中的用例、功能以及該系統的必備行為設定為對開發者可見的一級實體，簡化它們對於系統的理解，《尖叫的軟體架構》一章就是在重點說明這個問題；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在維護上，軟體系統維護的成本一般是最高的，維護的成本可以分為&lt;strong&gt;探秘&lt;/strong&gt;與&lt;strong&gt;風險&lt;/strong&gt;兩類：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;探秘：探秘(spelunking)的成本主要來自我們對於現有軟體系統的挖掘，目的是確定新增功能或被修復問題的最佳位置和最佳方式；&lt;/li&gt;
&lt;li&gt;風險：風險(risk)，則是指當我們進行上述修改時，總是有可能衍生出新的問題，這種可能性就是風險成本；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="保持可选项"&gt;保持可選項&lt;/h2&gt;
&lt;p&gt;回憶之前提到的軟體架構提到的兩種價值：“行為價值”和“架構價值”，提升架構價值的方式就是讓軟體更軟，而讓軟體更軟的方式就是讓軟體架構儘可能多的保持可選項。&lt;/p&gt;
&lt;p&gt;軟體系統的元素分為&lt;strong&gt;策略&lt;/strong&gt;與&lt;strong&gt;細節&lt;/strong&gt;兩類，策略體現的是軟體中所有的業務規則與操作過程，因此它是系統的真正價值所在，細節則是指那些讓操作該系統的人、其他系統以及程式設計師們與策略進行互動，但是又不會影響到策略本身的行為。如 IO 裝置、資料庫等等，細節選項應該是儘可能保留的。&lt;/p&gt;
&lt;p&gt;軟體架構師的目標就是要建立一種系統形態，該形態會以策略為&lt;strong&gt;最基本的元素&lt;/strong&gt;，並讓細節與策略脫離關係，以允許在具體決策過程中&lt;strong&gt;推遲或延遲與細節相關的內容&lt;/strong&gt;。因為越到專案的後期，我們就擁有越多的資訊來做出合理的決策。一個優秀的軟體架構師應該致力於最大化可選項的數量。&lt;/p&gt;
&lt;p&gt;作者使用裝置無關性的例子來說明推遲細節的重要性。現代作業系統的輸入輸出裝置種類多樣，而在計算機發展的最初時期，打孔紙帶是主流（幾乎是唯一）的一種輸入輸出方式，程式設計人員很自然的就將讀取/輸出紙帶的程式碼耦合到了系統程式碼當中，當磁帶出現後，開發人員不得不開發新的程式碼來適配磁帶輸入輸出，光碟出現後…為了應對這種重複開發，適應性差的問題，開發人員提出了裝置無關行的概念，即將裝置抽象成函式，OS會利用函式與輸入輸出裝置互動，而開發人員只需要提供這些函式的具體實現。此時輸入輸出就成為了OS的外掛，這也是開閉原則的雛形（擁抱👏新增、抗拒🥊修改！）。&lt;/p&gt;
&lt;h2 id="保持独立性"&gt;保持獨立性&lt;/h2&gt;
&lt;p&gt;良好的軟體架構應該有充足的獨立性。&lt;/p&gt;
&lt;p&gt;解耦可以讓我們保證軟體架構的獨立性。&lt;/p&gt;
&lt;p&gt;解耦分為水平解耦與垂直解耦。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;水平解耦（按層解耦）：系統可以被解耦成若干個水平分層——UI、資料庫等等；&lt;/li&gt;
&lt;li&gt;用例解耦（垂直解耦）：在水平分層解耦的同時，也按用例將其切分成多個垂直分片，如：將增加訂單的用例UI與刪除訂單的用例UI分開。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果我們按照變更原因的不同對系統進行解耦，就可以持續地向系統內新增新的用例，而不會影響舊有的用例。&lt;/p&gt;
&lt;p&gt;同樣，解耦的層次也可以是不同的。可以在原始碼、部署以及服務三個不同的層次來完成解耦。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原始碼層次：控制原始碼中模組之間的依賴關係，系統的元件通過函式呼叫來進行互動。這種模式叫做單體結構；&lt;/li&gt;
&lt;li&gt;部署層次：控制部署單元（如 Jar 檔案）之間的依賴關係，系統的元件可能使用跨執行緒（注意不是跨網路）的通訊、Socket 通訊或共享記憶體融通訊；&lt;/li&gt;
&lt;li&gt;服務層次：將元件間的依賴降低到資料結構級別，然後通過網路資料包進行通訊，如微服務通過 rpc、rest 互動。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;並沒有嚴格的標準說明那個解耦層次是更好的，因為隨著專案的逐漸成熟，最好的解耦模式可能會發生變化。一個設計良好的架構應該能允許一個系統從單體結構開始，以單一檔案的形式部署，然後逐漸成長為一組相互獨立的可部署單元，甚至是獨立的服務或者微服務。最後還能隨著情況的變化，允許系統逐漸回退到單體結構。一個設計良好的架構在上述過程中還應該能保護系統的大部分原始碼不受變更影響。對整個系統來說，&lt;strong&gt;解耦模式也應該是一個可選項&lt;/strong&gt;。我們在進行大型部署時可以採用一種模式，而在進行小型部署時則可以採用另一種模式。&lt;/p&gt;
&lt;p&gt;在瞭解解耦之後，讓我們看看解耦對系統獨立性到底有何影響。&lt;/p&gt;
&lt;h3 id="解耦对系统运行独立性的意义"&gt;解耦對系統執行獨立性的意義&lt;/h3&gt;
&lt;p&gt;如果不同面向之間的用例得到了良好的隔離，那麼需要高吞吐量的用例就和需要低吞吐量的用例互相自然分開了。如果 UI 和資料庫的部分能從業務邏輯分離出來，那麼它們就可以執行在不同的伺服器上。而且需要較大頻寬的應用也可以在多個伺服器上執行多個例項。&lt;/p&gt;
&lt;h3 id="解耦对系统开发独立性的意义"&gt;解耦對系統開發獨立性的意義&lt;/h3&gt;
&lt;p&gt;只要系統按照其水平分層和用例進行了恰當的解耦，整個系統的架構就可以支援多團隊開發，不管團隊組織形式是分功能開發、分元件開發、分層開發，還是按照別的什麼變數分工都可以&lt;/p&gt;
&lt;h3 id="解耦对系统部署独立性的意义"&gt;解耦對系統部署獨立性的意義&lt;/h3&gt;
&lt;p&gt;如果解耦工作做得好，我們甚至可以在系統執行過程中熱切換(hot-swap)其各個分層實現和具體用例。在這種情況下，我們增加新用例就只需要在系統中新增一些新的jar檔案，或啟動一些服務即可，其他部分將完全不受影響&lt;/p&gt;
&lt;h2 id="什么是代码重复"&gt;什麼是程式碼重複&lt;/h2&gt;
&lt;p&gt;程式碼中的重複可以分為兩種：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;真正的重複：程式碼中需要被消滅的部分；&lt;/li&gt;
&lt;li&gt;虛假的重複：看起來重複的程式碼，實際上是不同的演進路徑，也就是擁有不同的變更速率與變更邊緣。CRP闡述：對於使用頻次不同的程式碼需要拆分到不同的元件中，如果在閱讀程式碼時沒有考慮到這一點就很有可能誤認為虛假的重複為重複程式碼，這些“重複”有時候是必須的，還記得計算工資的例子嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第五篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>領域驅動設計初識</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</link>
      <pubDate>Fri, 26 Jul 2024 19:49:45 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</guid>
      <description>&lt;h1 id="领域驱动设计概述"&gt;領域驅動設計概述&lt;/h1&gt;
&lt;p&gt;領域驅動設計（DDD, Domain-Driven Design）是一種模型驅動設計的方法，它通過領域模型捕捉領域知識，使用領域模型構造&lt;strong&gt;更易維護&lt;/strong&gt;的軟體.&lt;/p&gt;
&lt;p&gt;DDD的設計過程分為戰略設計與戰術設計，其中戰略設計面向領域、子域以及限界上下文的設計，而戰術設計面向實體、值物件、領域事件等設計，關係如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/DDD1.png" alt="img"&gt;&lt;/p&gt;
&lt;h2 id="战略设计阶段相关概念"&gt;戰略設計階段相關概念&lt;/h2&gt;
&lt;h3 id="领域"&gt;領域&lt;/h3&gt;
&lt;p&gt;領域是系統要解決問題的領域，如商品資訊管理就可以是系統要解決問題的領域.&lt;/p&gt;
&lt;h3 id="子域"&gt;子域&lt;/h3&gt;
&lt;p&gt;根據使用語言的不同可以將領域劃分為不同的子域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心域：決定產品核心競爭力的子域，是最為重要、業務最核心、個性的部分；&lt;/li&gt;
&lt;li&gt;通用域：被多個子域使用的通用功能子域，比如用到的通用系統，例如認證、許可權等等，這類應用沒有企業特點限制，不需要做太多的定製化；&lt;/li&gt;
&lt;li&gt;支撐域：不包含核心功能與通用功能的子域，具有企業特性，但不具有通用性，例如資料程式碼類的資料字典等系統；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="限界上下文"&gt;限界上下文&lt;/h3&gt;
&lt;p&gt;限界上下文，即限定使用不同模型來解決不同問題所產生的不同區域.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限界上下文是一個子域或者多個子域的集合&lt;/strong&gt;，&lt;strong&gt;要保證一個限界上下文須支援一個完整的業務流程&lt;/strong&gt;，&lt;strong&gt;保證這個業務流程所涉及的領域都在一個限界上下文中&lt;/strong&gt;.限界上下文是微服務拆分的依據，即每個限界上下文對應一個微服務.&lt;/p&gt;
&lt;p&gt;那麼為什麼要進行限界上下文呢？這是因為&lt;strong&gt;上下文&lt;/strong&gt;的存在導致編碼十分困難，舉個例子說明：&lt;/p&gt;
&lt;p&gt;例如在招聘域我們可能會使用“平臺”來表明來自於哪個學校、機構；在跳水域我們使用“平臺”來表明選手從那個跳臺跳水；而在鐵路交通運輸領域，我們可能會使用“平臺”來描述鐵路站臺……在某一天的某一個機會，跳水選手、HR和乘務員聚到了一起，並且他們都不瞭解對方的身份，如果此時HR問跳水選手說你來自哪個平臺……&lt;/p&gt;
&lt;p&gt;看到了吧，同樣是使用“平臺”這個詞語，但三個人在&lt;strong&gt;沒有約定&lt;/strong&gt;的情況下可能對於詞語的理解不盡相同，解決這個問題的辦法就是把跳水域、招聘域和交通域隔離，分別定義域內的術語，在一個域內討論就可以最大可能的避免歧義，這就是約定.這也是為什麼最好把業務所涉及的領域定義在一個限界上下文中：減少歧義.&lt;/p&gt;
&lt;p&gt;在軟體系統的設計中同樣會遇到類似的問題，如果你負責設計一個足夠大的計算機軟體產品，產品涉及到跳水服務、招聘服務與乘車服務，系統沒有對服務做任何切分，你在跳水有關業務邏輯中使用platform資料結構描述跳臺、在招聘業務中使用platform代表平臺、在交通業務中使用platform代表站臺，當這三個業務不可避免的交匯到一起時，災難就發生了，面對滿螢幕不同語義但幾乎同名的變數，我們能怎麼辦？&lt;/p&gt;
&lt;p&gt;顯然，限定某一個領域術語的細節，我們就可以在領域中暢通無阻的使用這個術語了，這就是限界上下文的重要作用.&lt;/p&gt;
&lt;h2 id="战术设计阶段相关概念"&gt;戰術設計階段相關概念&lt;/h2&gt;
&lt;h3 id="值对象和实体"&gt;值物件和實體&lt;/h3&gt;
&lt;p&gt;首先了解一下值物件和實體.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;值物件是通過屬性值來識別的物件，即如果兩個值物件的內部值都是相同的，那麼我們就認為這兩個值物件是相同的.顯然，值物件的屬性不可變.&lt;/li&gt;
&lt;li&gt;實體是擁有唯一標識和狀態，且具有生命週期的業務物件.實體的屬性是可變的，如果兩個實體的屬性是完全相同的，我們也不認為他們是相同的實體，只有他們兩個的標識（如ID）是相同的才會被認為是相同的實體.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;舉例區分值物件和實體：&lt;/p&gt;
&lt;p&gt;假如現在我們有一個白色值物件Color white{R:255, G:255, B:255}和一個輪胎實體tire{Air:&lt;em&gt;,Size:&lt;/em&gt;}：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可變性：白色中的每一個屬性都是不可變的，因為一旦變化此物件就不再代表”白色“；而輪胎中的氣壓、尺寸等值可以改變，因為了即便這些引數發生了變化它仍然是輪胎；&lt;/li&gt;
&lt;li&gt;可比較性：正是由於值物件的值不可變性賦予了其相等的規則，兩個白色物件的值一定會是相同的，因此如果兩個值物件的內部值都是相同的，那麼我們就認為這兩個值物件是相同的，實體則由於其屬性的可變性而喪失了這個特點.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對於實體而言，其形態一般有四種：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;失血模型：模型僅僅包含資料的定義和getter/setter方法，業務邏輯和應用邏輯都放到服務層中.這種類在Java中叫POJO.&lt;/li&gt;
&lt;li&gt;貧血模型：貧血模型中包含了一些業務邏輯，但不包含依賴持久層的業務邏輯.這部分依賴於持久層的業務邏輯將會放到服務層中.&lt;/li&gt;
&lt;li&gt;充血模型：充血模型中包含了所有的業務邏輯，包括依賴於持久層的業務邏輯.&lt;/li&gt;
&lt;li&gt;脹血模型：脹血模型就是把和業務邏輯不相關的其他應用邏輯（如授權、事務等）全部都放到領域模型中.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;資源庫Repo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Repo是針對Entity設計的儲存操作，Repo中的操作應該儘可能低階、命名要簡短，並且不能定義太多.Java中的MyBatis Mapper可以看作是Repo的一種實現.&lt;/p&gt;
&lt;h3 id="聚合与聚合根"&gt;聚合與聚合根&lt;/h3&gt;
&lt;p&gt;聚合是一種更大範圍的封裝，把一組有相同生命週期、在業務上不可分隔的實體和值物件放在一起考慮，只有聚合根可以對外暴露引用，聚合也是一種內聚性的表現.聚合根之間也可相互呼叫，聚合根抽象出來一般名字為名詞.&lt;/p&gt;
&lt;p&gt;聚合根通過以下幾種手段實現封裝：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必須通過操作聚合根來實現操作整個聚合，外部操作不允許直接操作聚合中的元素.比如紫色（外觀）+輪胎+鋼架+…=汽車，我們開車時不能也不會去單獨操作輪胎、方向盤、外觀…，相反，我們通過操作汽車這個聚合根來間接操作其他實體/值物件.&lt;/li&gt;
&lt;li&gt;聚合定義了一組&lt;strong&gt;邊界&lt;/strong&gt;，邊界內所有的元件必須對業務邏輯有效；&lt;/li&gt;
&lt;li&gt;必須在一個原子性的事務中操作聚合，否則可能會出現異常，也就是說聚合是操作的單元，從倉庫取出聚合、操作完成後放回是一個原子性的操作；如何理解原子性？加入汽車聚合根有輪子實體、鋼/鋁/碳架實體，你不能拆掉汽車的一個輪子檢修完後不裝上.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="领域事件domain-events"&gt;領域事件Domain Events&lt;/h3&gt;
&lt;p&gt;當實體的屬性發生變化時就會產生領域事件，領域事件是領域專家認為重要的事情.領域事件是發生在領域中且值得注意的事件.而領域事件通常意味著領域物件狀態的改變.&lt;strong&gt;領域事件在系統中起到了傳遞訊息、觸發其他動作的作用，是解耦領域模型的重要手段之一&lt;/strong&gt;.我們往往利用&lt;strong&gt;訊息佇列&lt;/strong&gt;來傳遞領域事件，這樣所有訂閱此訊息的子域都會進行自己內部的響應操作.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;訊息匯流排也是一種實現方法👋.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如輪胎實體氣壓發生變化時，會釋放領域leaked漏氣事件，此漏氣事件傳遞了“輪胎漏氣”這個訊息，從而引發一系列其他的響應，如動能系統變化、方向盤手感變化等等，領域事件通常會使用過去式命名來說明事件已經發生，不可撤銷.&lt;/p&gt;
&lt;h3 id="领域服务domain-service"&gt;領域服務Domain Service&lt;/h3&gt;
&lt;p&gt;有些領域中的動作看上去&lt;strong&gt;並不屬於任何物件&lt;/strong&gt;.它們代表了領域中的一個重要的行為，不能忽略它們或者簡單地把它們合併到某個實體或者值物件中.當這樣的行為從領域中被識別出來時，推薦的實踐方式是將它宣告成一個服務，這個服務就是領域服務.&lt;/p&gt;
&lt;p&gt;領域服務不是微服務中的“服務”概念.服務和聚合根概念有些相近，他們都可以操作多個實體，但是二者理念和作用不同，&lt;strong&gt;聚合根是對實體的組合，而服務是用於同步多個實體的狀態，比如將某個item實體加入到list實體&lt;/strong&gt;（比如把mail投遞到inbox）.所以Servie一般抽象出來都是動詞.&lt;/p&gt;
&lt;h2 id="ddd领域建模设计领域模型"&gt;DDD領域建模（設計領域模型）&lt;/h2&gt;
&lt;p&gt;DDD領域建模一般步驟如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根據需求劃分出初步的子域和限界上下文，以及上下文之間的關係；&lt;/li&gt;
&lt;li&gt;進一步分析每個上下文內部，識別出哪些是實體，哪些是值物件，並確定實體需要使用哪種程式碼形態：失血、貧血、充血、脹血；&lt;/li&gt;
&lt;li&gt;對實體、值物件進行關聯和聚合，劃分出聚合的範疇和聚合根；&lt;/li&gt;
&lt;li&gt;為聚合根設計資源庫repo，並思考實體或值物件的建立方式；&lt;/li&gt;
&lt;li&gt;在工程中實踐領域模型，並在實踐中檢驗模型的合理性，倒推模型中不足的地方並重構.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這就是DDD採用兩階段設計原則——先進行戰略設計、隨後進行戰術設計。&lt;/p&gt;
&lt;p&gt;在實踐中，建議實體採用失血模型（實體方法只包括setter/getter）或者貧血模型（包含不涉及資料庫操作的簡單邏輯，如屬性合法性校驗）；&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;實際上這是DDD建模方式之一，是自頂向下的設計方法，還存在一種方式是使用自下而上的方法，即先確定領域模型——實體、值物件等，再確定子域、限界上下文…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（本節完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>領域驅動設計</category></item>
    <item>
      <title>再讀整潔架構之道（四）元件建構原則</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</link>
      <pubDate>Sat, 06 Jul 2024 17:27:18 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</guid>
      <description>&lt;h1 id="再读整洁架构之道四组件构建原则"&gt;再讀整潔架構之道（四）元件構建原則&lt;/h1&gt;
&lt;p&gt;第三篇主要敘述如何設計模組和類，在本篇將會更近一步，敘述如何進行元件的設計。&lt;/p&gt;
&lt;p&gt;元件是軟體的部署單元，是整個軟體系統在部署過程中可以獨立完成部署的最小實體，元件可以被單獨開發，元件化的外掛式架構已經成為我們習以為常的軟體構建形式了。&lt;/p&gt;
&lt;h2 id="组件聚合"&gt;元件聚合&lt;/h2&gt;
&lt;p&gt;元件聚合告訴我們哪些模組和類應該組合在一起形成元件。主要有三個原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複用/釋出等同原則；&lt;/li&gt;
&lt;li&gt;共同閉包原則；&lt;/li&gt;
&lt;li&gt;共同複用原則；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="复用发布等同原则rep"&gt;複用/釋出等同原則REP&lt;/h3&gt;
&lt;p&gt;REP指出軟體複用的最小粒度應等同於其釋出的最小粒度。&lt;/p&gt;
&lt;p&gt;REP是從程式碼複用角度考慮的，它規定了具備相同主題和功能的程式碼可以進行組合形成元件。REP建議軟體複用應該按照元件來進行的。&lt;/p&gt;
&lt;p&gt;通俗來講，某個元件打包釋出後可能會形成版本號或唯一標識，我們通過引入現成程式碼庫的方式複用程式碼，引入的單位就是元件打包的結果，引入的憑據就是版本號。&lt;/p&gt;
&lt;h3 id="共同闭包ccp"&gt;共同閉包CCP&lt;/h3&gt;
&lt;p&gt;CCP指出我們應該將那些會同時修改，並且為相同目的而修改的類放到同一個元件中，而將不會同時修改，並且不會為了相同目的而修改的那些類放到不同的元件中。&lt;/p&gt;
&lt;p&gt;CCP是從程式碼維護角度考慮的，對於由於同一原因/修改目的程式碼應該組合形成元件，這樣就可以有效地降低因軟體釋出、驗證及部署所帶來的工作壓力。&lt;/p&gt;
&lt;p&gt;前面說過，CCP是SRP的元件版，SRP和CCP都可以用以下的語言概括：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;將由於相同原因而修改，並且需要同時修改的“東西”放在一起。將由於不同原因而修改，並且不同時修改的東西分開。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在SRP中，東西是指函式和其他組成類或模組的成分；&lt;/li&gt;
&lt;li&gt;在CCP中，東西是指構成元件的類和模組。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="共同复用crp"&gt;共同複用CRP&lt;/h3&gt;
&lt;p&gt;CRP指出不要強迫一個元件的使用者依賴他們不需要的東西。&lt;/p&gt;
&lt;p&gt;CRP規定了對於使用場景不同、使用頻次不同的程式碼需要進行拆分到不同的元件中。CRP 是為了避免不必要的切分，是ISP介面隔離原則的普適版。&lt;/p&gt;
&lt;p&gt;CRP是介面隔離的普適版，ISP和CRP都可以用以下的語言概括：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要依賴不需要用到的“東西”。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在ISP中，東西是指包含不需要的方法的函式/類；&lt;/li&gt;
&lt;li&gt;在CCP中，東西是指包含不需要的函式的類/模組；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="思考"&gt;思考&lt;/h3&gt;
&lt;p&gt;那麼REP、CCP、CRP三者之間有什麼關係呢？&lt;/p&gt;
&lt;p&gt;實際上三者存在著&lt;strong&gt;競爭關係&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;REP和CCP是黏合性原則，它們指導我們應該把哪些類/模組放在一起形成元件，而CRP屬於排他性原則，它知道我們哪些類/模組不應該放在一起。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706143525187.png" alt="image-20240706143525187"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果架構師只關注REP和CCP，那麼會發現軟體依賴了的元件中包含了很多並不需要的部分，這樣會導致太多不必要的釋出；&lt;/li&gt;
&lt;li&gt;如果架構師只關注CRP和CCP，那麼會發現軟體的複用會變得非常困難；&lt;/li&gt;
&lt;li&gt;如果架構師只關注REP和CRP，那麼會發現如果部分類/模組需要變更，很多相關模組也不可避免的要跟隨變更；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;軟體架構師的任務就是要在這三個原則中間進行取捨，元件的構成安排應隨著專案重心的不同，以及研發性與複用性的不同而不斷演化，因此確定專案朝著哪個方向演進是要動態判斷的。&lt;/p&gt;
&lt;h2 id="组件耦合"&gt;元件耦合&lt;/h2&gt;
&lt;p&gt;元件耦合告訴我們如何安排元件之間的關係。它包含三個原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;無依賴環原則ADP；&lt;/li&gt;
&lt;li&gt;穩定依賴原則SDP；&lt;/li&gt;
&lt;li&gt;穩定抽象原則SAP；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="无依赖环原则adp"&gt;無依賴環原則ADP&lt;/h3&gt;
&lt;p&gt;ADP告訴我們元件依賴關係圖中不應該出現環。&lt;/p&gt;
&lt;p&gt;版本號控制機制解決了一覺醒來綜合徵的問題，而此機制需要遵循ADP，首先讓我們讀一下作者是怎麼介紹一覺醒來綜合徵的：&lt;/p&gt;
&lt;p&gt;當你花了一整天的時間，好不容易搞定了一段程式碼，第二天上班時卻發現這段程式碼莫名其妙地又不能工作了。這大機率是其他工作人員修改了你專案所依賴的元件。&lt;/p&gt;
&lt;p&gt;要想解決這個問題一般有兩種方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每週構建&lt;/li&gt;
&lt;li&gt;版本號控制機制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;每週構建&lt;/u&gt;&lt;/strong&gt;：每個人先在自己的程式碼倉工作，每週固定時間（比如週五）再進行專案構建並處理可能的衝突問題。&lt;/p&gt;
&lt;p&gt;這種方式的侷限性很明顯：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隨著專案越來越大，整合工作會越來越難以按時完成&lt;/li&gt;
&lt;li&gt;整個專案會變得越來越難以構建與測試，團隊反饋週期會越來越長，研發質量自然也會越來越差&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此版本號控制機制需要被引入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;版本號控制機制&lt;/u&gt;&lt;/strong&gt;：每當一個元件釋出新版本時，其他依賴這個元件的團隊都可以自主決定是否立即採用新版本。&lt;/p&gt;
&lt;p&gt;版本號控制機制&lt;strong&gt;不允許元件結構依賴關係圖中出現環&lt;/strong&gt;，也就是要遵守無依賴換原則ADP，否則一覺醒來綜合徵是不可避免的。為什麼呢？&lt;/p&gt;
&lt;p&gt;這是因為如果存在依賴環路，環路里的所有元件被組合成了一個更大的元件，這就要求他們都必須使用相同的版本，這樣其他的元件才能成功的完成依賴。同樣，存在依賴換的系統在測試時也會成為一個棘手的問題，雖然打樁mock技術已經被廣泛應用，但同時為環路中的元件重複打樁也已經十分的不優雅了。&lt;/p&gt;
&lt;p&gt;那麼如何消除迴圈依賴呢？方案有兩個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用依賴反轉建立介面；&lt;/li&gt;
&lt;li&gt;建立新元件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;應用DIP解決迴圈依賴很好理解，下面講述一個在工作中遇到的案例來說明如何使用建立新元件來打破迴圈依賴。&lt;/p&gt;
&lt;p&gt;現在有這樣的一個場景，團隊正在使用領域驅動設計的方法設計系統架構，現在實體Entity Dog需要依賴Dog Repo完成Save儲存操作。此時Entity模組依賴了Repo模組，很不幸的是在Entity模組中我們還定義了Dog PO（簡直糟透了），Repo的Save又需要使用PO來完成物件的轉換並存儲。此時Entity和Repo之間就發生了迴圈依賴，幸運的是，在Go中模組之間的相互依賴是不允許的，檢查起會提醒“迴圈匯入”錯誤，怎麼處理這個問題呢？&lt;/p&gt;
&lt;p&gt;答案是我們需要新建立一個PO模組，並把Entity中的PO移動到模組，Repo中依賴PO的函式也要劃分到PO中，這樣就消除了依賴環。使用依賴注入方法也是一個選擇，如果你使用SprintBoot，@AutoWired即可解決此問題，但其本質也是建立了一個新元件——依賴池，消除了依賴環。&lt;/p&gt;
&lt;p&gt;類似的例子還有很多很多，是否還記得我們使用anaconda下載Python庫時遇到的庫版本衝突問題……&lt;/p&gt;
&lt;p&gt;在這裡多插一句，筆者在設計元件結構的時候很自然的就想到要和系統功能對應，也就是說，元件應該是和系統功能一一對應的，這樣元件依賴圖就和系統功能模組劃分也一一對應起來。想當然認為在系統設計的一開始元件依賴結構圖也就產出了。&lt;/p&gt;
&lt;p&gt;但作者特別提到，自頂向下的設計是不可能的。讓我們看看書中是怎麼描述的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;
&lt;p&gt;元件結構圖必須隨著軟體系統的變化而變化和擴張，而不可能在系統構建的最初就被完美設計出。因為元件依賴結構圖並不和功能一一對應，它更像是應用程式在構建性與維護性方面的一張地圖。&lt;/p&gt;
&lt;p&gt;被設計並實現出來的模組越來越多，專案中就逐漸出現了要對元件依賴關係進行管理的需求，我們希望將專案變更所影響的範圍被限制得越小越好，因此需要應用單一職責原則(SRP)和共同閉包原則(CCP)來將經常同時被變更的類聚合在一起。&lt;/p&gt;
&lt;p&gt;元件結構圖中的一個重要目標是指導如何隔離頻繁的變更。我們不希望那些頻繁變更的元件影響到其他本來應該很穩定的元件。&lt;/p&gt;
&lt;p&gt;另外，隨著應用程式的增長，建立可重用元件的需要也會逐漸重要起來。這時CRP又會開始影響元件的組成。最後當迴圈依賴出現時，隨著無迴圈依賴原則(ADP)的應用，元件依賴關係會產生相應的抖動和擴張。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="稳定依赖原则sdp"&gt;穩定依賴原則SDP&lt;/h3&gt;
&lt;p&gt;SDP告訴我們依賴關係必須要指向更穩定的方向。通常來說，要做到軟體架構的越底層越穩定。&lt;/p&gt;
&lt;p&gt;任何一個我們預期會經常變更的元件都不應該被一個難於修改的元件所依賴，否則這個多變的元件也將會變得非常難以被修改。這就是軟體開發的困難之處，我們精心設計的一個容易被修改的元件很可能會由於別人的一條簡單依賴而變得非常難以被修改，通過遵循SDP，這樣的問題就能夠被避免。&lt;/p&gt;
&lt;p&gt;判斷元件是否穩定，可以使用穩定性指標。作者定義穩定性指標可以通過如下公式計算：
$$
I=\frac{Fan-out}{Fan-in+Fan-out}
$$
其中Fan-in為元件的入依賴數量，Fan-out為出依賴數量，這意味著一個元件的出依賴越少越穩定，因為如果出依賴為 0 意味著沒有因素可以讓元件改變。&lt;/p&gt;
&lt;p&gt;穩定依賴原則(SDP)的要求就是要讓每個元件的I指標都必須大於其所依賴元件的I指標（即被指向的元件更穩定）。&lt;/p&gt;
&lt;p&gt;SDP並不是要求所有元件都應該是穩定的，我們設計元件架構圖的目的就是要決定應該讓哪些元件穩定，讓哪些元件不穩定，所有元件都穩定的架構是不靈活的，不靈活的架構沒有足夠的架構價值。&lt;/p&gt;
&lt;h3 id="稳定抽象原则sap"&gt;穩定抽象原則SAP&lt;/h3&gt;
&lt;p&gt;SAP指出一個元件的抽象化程度應該與其穩定性保持一致。&lt;/p&gt;
&lt;p&gt;高層策略等應該存在穩定的元件中，但這樣做會導致高層策略很難修改，幸運的是開閉原則 OCP 告訴我們穩定的元件可以被設計成容易擴充套件的，抽象類具有穩定抽象能力，可以設計一個抽象類具有較好的穩定性並且易於擴充套件和修改。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抽象類是介面和類之間的緩衝地帶。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;穩定抽象原則(SAP)為元件的穩定性與它的抽象化程度建立了一種關聯。原則要求穩定的元件同時應該是抽象的，這樣它的穩定性就不會影響到擴充套件性。同時一個不穩定的元件應該包含具體的實現程式碼，這樣它的不穩定性就可以通過具體的程式碼被輕易修改。因此，如果一個元件想要成為穩定元件，那麼它就應該由介面和抽象類組成，以便將來做擴充套件。&lt;/p&gt;
&lt;p&gt;和穩定性類似，我們也可以使用抽象程度指標來衡量元件的抽象程度：
$$
A=\frac{N_c}{N_a}
$$
其中Nc是指元件中類的數量，而Na是元件中抽象類和介面的數量。A指標的取值範圍是從0到1，值為0代表元件中沒有任何抽象類，值為1就意味著元件中只有抽象類。&lt;/p&gt;
&lt;h3 id="思考-1"&gt;思考🤔&lt;/h3&gt;
&lt;p&gt;SDP和SAP有什麼關聯嗎？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一點，從SDP、SAP與DIP上說&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;實際上，SDP+SAP=元件DIP。因為SDP要求的是讓依賴關係指向更穩定的方向，而SAP則告訴我們穩定性本身就隱含了對抽象化的要求，即依賴關係應該指向更抽象的方向。&lt;/p&gt;
&lt;p&gt;怎麼理解這句話呢？&lt;/p&gt;
&lt;p&gt;可以想到，DIP在類上的作用是為了保證靈活性，SDP+SAP實際上就是為了保證元件在穩定的基礎上足夠靈活，並且 DIP 和 SDP+SAP 在軟體架構上的影響都是具體實現類指向抽象類/介面，因此從作用上看SDP+SAP=元件DIP。&lt;/p&gt;
&lt;p&gt;但是DIP和SDP+SAP還是有不同之處，對類來說，設計是沒有灰色地帶的，一個類要麼是抽象類，要麼就不是。SDP與SAP這對原則是應用在元件層面上的，我們要允許一個元件部分抽象，部分穩定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其次，從SAP和SDP的目的達成來說&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此外，SDP指出依賴應該指向穩定的位置，SAP要求我們在設計元件時，高層的策略應該設計在抽象的元件，以便我們在架構設計時將策略與細節分開，同時便於我們進行外掛式開發，SDP和SAP他們在目的上是一致的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看到了吧，這裡又提到了&lt;strong&gt;外掛式開發&lt;/strong&gt;這個詞，上次是在哪裡提到的？對，依賴反轉DIP，這也說明了SAP+SDP確實類似於元件DIP。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="主序列"&gt;主序列&lt;/h3&gt;
&lt;p&gt;把元件穩定性指標I與元件抽象程度指標A結合在一起，畫出一張圖，a=-i+1這條直線就叫做主序列線。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706171725797.png" alt="image-20240706171725797"&gt;&lt;/p&gt;
&lt;p&gt;整個區域可以分為三個區域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;痛苦區：靠近(0,0)的區域，在這個區域內的元件十分穩定但同時又十分的具體，因此不易改變，如資料庫表；工具庫也是處於痛苦區的元件，雖然其 I 指標為 1（工具庫依賴非常多的元件，因此其不穩定），但不能被修改，否則很多程式碼會出現問題；&lt;/li&gt;
&lt;li&gt;無用區：處於(1,1)附近的元件，該位置上的元件通常是無限抽象的，但是沒有被其他元件依賴，這樣的元件往往無法使用，對於這個區域中的軟體元件來說，其原始碼或者類中的設計問題通常是由於歷史原因造成的，比如忘記清除的舊程式碼；&lt;/li&gt;
&lt;li&gt;主序列線區：在整條主序列線上的區域，元件所能處於最優的位置是線的兩端(0, 1)與(1, 0)。一個優秀的軟體架構師應該爭取將自己設計的大部分元件儘可能地推向這兩個位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼衡量元件的綜合性能呢？答案是計算它與主序列線的距離，來量化一個系統設計與主序列的契合程度，這個值為D值，值為0意味著元件是直接位於主序列線上的，值為1則意味著元件在距離主序列最遠的位置。
$$
D=|A+I-1|
$$
這樣我們就可以用D指標大於0多少來指導元件的重構與重新設計。&lt;/p&gt;
&lt;p&gt;除此以外，D還可以有更多的作用，比如計算設計中所有元件的D指標的平均值和方差，用統計學的方法來量化分析一個系統設計：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;對於一個良好的系統設計來說，D指標的平均值和方差都應該接近於0；&lt;/li&gt;
&lt;li&gt;方差作為元件的“達標紅線”來使用，我們可以通過它找出系統設計中那些不合常規的元件；&lt;/li&gt;
&lt;li&gt;還可以按照時間追蹤 D 的方差值，來觀察隨著時間的變化，軟體系統架構的穩定和抽象變化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第四篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（三）SOLID 設計原則</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</link>
      <pubDate>Fri, 05 Jul 2024 14:11:30 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</guid>
      <description>&lt;h1 id="再读整洁架构之道三solid原则"&gt;再讀整潔架構之道（三）SOLID原則&lt;/h1&gt;
&lt;p&gt;構建軟體模組的主要目標有三個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使軟體可容忍被改動&lt;/li&gt;
&lt;li&gt;使軟體更容易被理解&lt;/li&gt;
&lt;li&gt;構建可在多個軟體系統中複用的元件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SOLID原則的主要作用就是告訴我們如何將資料和函式組織成為類，以及如何將這些類連結起來成為程式。SOLID原則指導我們如何設計模組，在架構設計層面，我們會有其他的設計原則。&lt;/p&gt;
&lt;p&gt;SOLID原則是指單一職責原則&lt;strong&gt;S&lt;/strong&gt;RP、開閉原則&lt;strong&gt;O&lt;/strong&gt;CP、裡式替換原則&lt;strong&gt;L&lt;/strong&gt;SP、介面隔離原則&lt;strong&gt;I&lt;/strong&gt;SP和介面反轉&lt;strong&gt;D&lt;/strong&gt;IP。&lt;/p&gt;
&lt;h2 id="单一职责原则srp"&gt;單一職責原則SRP&lt;/h2&gt;
&lt;p&gt;SRP定義每個軟體模組只對一個功能負責，&lt;strong&gt;只有一個理由可以讓模組改變&lt;/strong&gt;，任何一個軟體模組都應該只對某一類行為者負責。&lt;/p&gt;
&lt;p&gt;元件層面的SRP被稱作共同閉包原則CCP。&lt;/p&gt;
&lt;p&gt;倘若設計模組時沒有遵循SRP會發生什麼呢？一起看一個例子。&lt;/p&gt;
&lt;p&gt;如果財務部門、人事部門、研發部門共同依賴於一個工資、工時計算的程式，程式包含三個函式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CalculateSalary：計算工資；&lt;/li&gt;
&lt;li&gt;CalculateTime：計算工時；&lt;/li&gt;
&lt;li&gt;Save：儲存資訊；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三個部門平靜地使用著程式，突然有一天，財務部的薪資計算發生變化，因此財務部門的維護人員更改了CalculateSalary函式；然後，詭異的事情發生了，人事部門、研發部薪資也同時變化了…&lt;/p&gt;
&lt;h2 id="开闭原则ocp"&gt;開閉原則OCP&lt;/h2&gt;
&lt;p&gt;“擁抱新增、抗拒修改！”&lt;/p&gt;
&lt;p&gt;OCP指出：如果軟體系統想要更容易被改變，那麼其設計就必須允許新增程式碼來修改系統行為，而非只能靠修改原來的程式碼。設計良好的計算機軟體應該易於擴充套件，同時抗拒修改。&lt;/p&gt;
&lt;p&gt;OCP不僅適用於類和模組的設計，也適用於元件的設計。他起源於裝置無關性的概念。具體來說，我們使用依賴反轉將IO裝置設計成外掛，這樣的我們就能很輕易的新增IO裝置，而不去修改原有的IO裝置。&lt;/p&gt;
&lt;p&gt;OCP的實現方式是通過將系統劃分為一系列元件，並且將這些元件間的依賴關係按層次結構進行組織，使得高階元件不會因低階元件被修改而受到影響。&lt;/p&gt;
&lt;h2 id="里式替换lsp原则"&gt;裡式替換LSP原則&lt;/h2&gt;
&lt;p&gt;LSP指出：如果想用可替換的元件來構建軟體系統，那麼這些元件就必須遵守同一個約定，以便讓這些元件可以相互替換。&lt;/p&gt;
&lt;p&gt;LSP的本質是一個可替換性原則：如果對於每個型別是S的物件o1都存在一個型別為T的物件o2，能使操作T型別的程式P在用o1替換o2時行為保持不變，我們就可以將S稱為T的子型別。&lt;/p&gt;
&lt;p&gt;LSP可以且應該被應用於軟體架構層面，因為一旦違背了可替換性，該系統架構就不得不為此增添大量複雜的應對機制。&lt;/p&gt;
&lt;h2 id="接口隔离原则isp"&gt;介面隔離原則ISP&lt;/h2&gt;
&lt;p&gt;ISP指出：應該在設計中避免不必要的依賴，任何層次的軟體設計如果依賴了它並不需要的東西，就會帶來意料之外的麻煩。&lt;/p&gt;
&lt;h2 id="依赖反转dip"&gt;依賴反轉DIP&lt;/h2&gt;
&lt;p&gt;DIP指出該設計原則指出高層策略性的程式碼不應該依賴實現底層細節的程式碼，恰恰相反，那些實現底層細節的程式碼應該依賴高層策略性的程式碼。&lt;/p&gt;
&lt;p&gt;通俗理解依賴反轉：這需要使用控制流和原始碼依賴流來說明。控制流譬如A元件流向B元件，此時B需要知道A的實現，A也需要引入B的模組，程式碼依賴上也就不可避免的A元件流向了B元件。此時系統行為決定了控制流，而控制流則決定了原始碼依賴關係，軟體架構就別無選擇。但是通過引入介面，B元件此時只需要實現介面，介面引入B，A引入介面，此時A就無需引入B模組，此時的控制流和原始碼依賴是反向的，也就是“依賴反轉”。&lt;/p&gt;
&lt;p&gt;依賴反轉給了軟體架構設計的自由，如果想要設計一個靈活的系統，在原始碼層次的依賴關係中就應該多引用抽象型別（介面、抽象類等），而非具體實現。&lt;/p&gt;
&lt;p&gt;DIP可以歸納出幾條編碼守則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;應在程式碼中多使用抽象介面，儘量避免使用那些多變的具體實現類；&lt;/li&gt;
&lt;li&gt;不要在具體實現類上建立衍生類；&lt;/li&gt;
&lt;li&gt;不要覆蓋(override)包含具體實現的函式；&lt;/li&gt;
&lt;li&gt;應避免在程式碼中寫入與任何具體實現相關的名字，或者是其他容易變動的事物的名字。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第三篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（二）程式設計範式</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</link>
      <pubDate>Fri, 05 Jul 2024 07:25:33 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</guid>
      <description>&lt;h1 id="再读整洁架构之道二"&gt;再讀整潔架構之道（二）&lt;/h1&gt;
&lt;p&gt;程式設計範式指的是程式的編寫模式，它告訴我們應該在什麼時候採用什麼樣的程式碼結構。&lt;/p&gt;
&lt;p&gt;截止目前，一共出現了三種程式設計範式：結構化程式設計範式、物件導向程式設計範式和函數語言程式設計。&lt;/p&gt;
&lt;p&gt;作者認為，每種程式設計方式不是在給架構設計者的武器庫進行擴充，相反，架構師和程式設計師的武器已經夠多了，這三種程式設計範式是在對他們利用的武器進行&lt;strong&gt;限制&lt;/strong&gt;，這也是為什麼他們叫做“範式”。&lt;/p&gt;
&lt;h2 id="结构化编程范式"&gt;結構化程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;結構化程式設計對程式控制權的直接轉移進行了限制和規範，特別指的是限制了程式中goto的隨意使用。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="分解程序"&gt;分解程式&lt;/h3&gt;
&lt;p&gt;Dijkstra希望使用數學推導方法對程式進行推理證明：讓程式就成為了一種歐幾里得結構，這樣可以用一些已經證明的結構串聯起新的程式，從而進一步推導整個程式的正確性。&lt;/p&gt;
&lt;p&gt;他還發現，如果程式中包含大量的goto，那麼程式會變得難以分解，而goto語句的作用完全可以依靠分支和迴圈來完成。&lt;/p&gt;
&lt;p&gt;這樣，結構化程式設計範式表明，程式可以進行降解拆分，一個大型問題拆分為一系列高階函式的組合，而這些高階函式各自又可以繼續被拆分為一系列低階函式，如此無限遞迴。更重要的是，每個被拆分出來的函式也都可以用結構化程式設計範式來書寫。&lt;/p&gt;
&lt;p&gt;但是，這種形式化證明的程式設計方式沒有成為主流，科學證明法是目前使用較多的方法。&lt;/p&gt;
&lt;h3 id="科学证明法"&gt;科學證明法&lt;/h3&gt;
&lt;p&gt;科學理論和科學定律可以被證偽，但是沒有辦法被證明，類似的，程式只能測試證偽，不能證明，即“測試只能展示Bug的存在，並不能證明不存在Bug”。因此，利用科學證明法，結構化程式設計範式就可以促使我們先將一段程式遞迴降解為一系列可證明的小函式，然後再編寫相關的&lt;strong&gt;測試&lt;/strong&gt;來試圖證明這些函式是錯誤的。如果這些測試無法證偽這些函式，那麼我們就可以認為這些函式是足夠正確的，進而推導整個程式是正確的。&lt;/p&gt;
&lt;h2 id="面向对象编程范式"&gt;物件導向程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;物件導向程式設計對程式控制權的間接轉移進行了限制和規範。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具體來說，物件導向程式設計凡事使用&lt;strong&gt;多型特性&lt;/strong&gt;限制了函式指標的使用。這可以理解為函式指標只能進行有限制的指向，比如java中的多型一般出現在有繼承關係的兩個類的物件之間。&lt;/p&gt;
&lt;p&gt;作者也認為多型是OOP的最大特性，利用多型可以做到&lt;strong&gt;依賴反轉&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;依賴反轉即引入介面，完全控制系統中所有原始碼的依賴關係，而不需要收到系統控制流的制約。&lt;/p&gt;
&lt;p&gt;依賴反轉保證了我們可以對系統進行外掛式的開發，如將web UI和資料庫與業務邏輯之間的依賴進行反轉，可以保證主業務邏輯與UI和資料庫的解耦，這樣UI和資料庫就成了主業務邏輯的外掛。&lt;/p&gt;
&lt;p&gt;物件導向程式設計就是以多型為手段來對原始碼中的依賴關係進行控制的能力，這種能力讓軟體架構師可以構建出某種外掛式架構，讓高層策略性元件與底層實現性元件相分離，底層元件可以被編譯成外掛，實現獨立於高層元件的開發和部署。&lt;/p&gt;
&lt;h2 id="函数式编程范式"&gt;函數語言程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;函數語言程式設計對程式中的賦值進行了限制和規範。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;換句話說，函數語言程式設計語言中變數是不可變的。&lt;/p&gt;
&lt;p&gt;作者有以下的觀點，這一段話來自原文：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“所有的競爭問題、死鎖問題、併發更新問題都是由可變變數導致的。如果變數永遠不會被更改，那就不可能產生競爭或者併發更新問題。如果鎖狀態是不可變的，那就永遠不會產生死鎖問題"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果不考慮儲存器與處理器的速度限制，那麼不可變性是可行的，但實際上我們必須考慮這些因素，因此可變性只能在一定程度上可行，需要讓不可變性在一定程度上可行需要完成&lt;strong&gt;可變性的隔離&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="可变性的隔离"&gt;可變性的隔離&lt;/h3&gt;
&lt;p&gt;可變性隔離的一種常見方式是將應用程式，或者是應用程式的內部服務進行切分，&lt;strong&gt;劃分為可變的和不可變的兩種元件&lt;/strong&gt;。不可變元件用純函式的方式來執行任務，期間不更改任何狀態。這些不可變的元件將通過與一個或多個非函式式元件（即可變元件）通訊的方式來修改變數狀態。&lt;/p&gt;
&lt;p&gt;一個例子是GIT版本管理工具。它通過移動指標來記錄檔案的變化：增刪改，但實際上檔案並沒有被真正的修改和刪除，只存在增加和檢索查詢兩種情況，這不就是不可變性的一個例子嗎。&lt;/p&gt;
&lt;p&gt;同時，mysql中的事務管理、事務性記憶體的工作也都運用了這個思想。&lt;/p&gt;
&lt;h2 id="总结"&gt;總結&lt;/h2&gt;
&lt;p&gt;三種程式設計範式與軟體架構具有密切的關係：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多型是我們跨越邊界的手段；&lt;/li&gt;
&lt;li&gt;函數語言程式設計是我們規範和限制資料存放位置與訪問許可權的手段；&lt;/li&gt;
&lt;li&gt;結構化程式設計則是個模組的實現基礎；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三種程式設計範式與軟體架構的三大關注重點不謀而合：&lt;strong&gt;元件獨立性&lt;/strong&gt;（物件導向）、&lt;strong&gt;資料管理&lt;/strong&gt;（函式式）以及功能性（結構）。&lt;/p&gt;
&lt;h2 id="再次思考"&gt;再次思考&lt;/h2&gt;
&lt;p&gt;三種程式設計範式都對程式設計師提出了新的&lt;strong&gt;限制&lt;/strong&gt;。每個範式都約束了某種編寫程式碼的方式，沒有一個程式設計範式是在增加新能力。也就是說，程式設計範式帶來的是——什麼不應該做。&lt;/p&gt;
&lt;p&gt;（第二篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（一）</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</link>
      <pubDate>Wed, 03 Jul 2024 23:10:20 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</guid>
      <description>&lt;h2 id="前记"&gt;前記&lt;/h2&gt;
&lt;p&gt;為什麼是再讀？因為在這之前筆者已經閱讀過一遍架構之道這本書，但當時缺乏專案的鍛鍊和試錯，總覺得讀的不深，終於有幸在工作期間接觸到了一些專案和需求，更幸運的是專案組在開發時採用的架構正好是整潔架構，詳細的說，是事件驅動開發DDD+整潔架構，關於DDD的詳細內容將會在之後討論，筆者就重新閱讀了這本書📚，收穫頗豐，因此這一個系列，我準備用自己的拙見描述一下整潔架構，更準確的說，應該是是讀書筆記，以茲同仁。&lt;/p&gt;
&lt;p&gt;這本書的詳細地址為：&lt;a href="https://weread.qq.com/web/bookDetail/480322f072021a3248038c8"&gt;架構整潔之道-羅伯特 C. 馬丁-微信讀書 (qq.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本系列會以圖書的章節組織作為思路，跟隨作者的腳步逐漸深入，同時，由於是再讀，所以不可避免的也會穿插一些圖書中其他部分的內容，所以，如果你在閱讀時發現了一些難於理解或者沒聽說過的內容，那它大機率是在之後章節中的內容。&lt;/p&gt;
&lt;p&gt;讓我們開始吧。&lt;/p&gt;
&lt;h2 id="设计与架构的含义"&gt;設計與架構的含義&lt;/h2&gt;
&lt;p&gt;設計與架構，本質上並沒有區別，底層設計細節和頂層架構資訊共同定義了軟體系統。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;軟體架構的終極目標是用最小的人力成本來滿足構建和維護系統的需求&lt;/strong&gt;，因此成本可以評估一個軟體架構設計的優劣。&lt;/p&gt;
&lt;p&gt;沒有經過設計、匆匆構建的系統可能成為&lt;strong&gt;亂麻系統&lt;/strong&gt;，亂麻系統在構建過程中被長期忽視程式碼質量和設計結構最佳化。認真設計軟體架構可以在一定程度上避免系統成為亂麻系統，從而降低成本。&lt;/p&gt;
&lt;p&gt;軟體開發具有兩個核心特點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要想跑得快，要先跑得穩；&lt;/li&gt;
&lt;li&gt;過度自信只會使得重構設計陷入和原專案一樣的困局。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="两个价值维度"&gt;兩個價值維度&lt;/h2&gt;
&lt;p&gt;軟體系統包含兩種價值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行為價值：讓機器按照某種指定方式運轉，給系統的使用者創造或者提高利潤；&lt;/li&gt;
&lt;li&gt;架構價值：軟體要夠靈活，更改軟體的成本要做到與需求的範疇相關，與形狀無關。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼理解需求的範疇與形狀？
筆者自己理解範疇即需求大致的範圍和所屬&lt;strong&gt;領域&lt;/strong&gt;，而形狀指需求的細節內容。&lt;/p&gt;
&lt;p&gt;那麼哪個價值維度更重要呢？
作者認為架構價值比行為價值更重要，因為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果某程式可以正常工作，但是無法修改，那麼當需求變更的時候它就不再能夠正常工作了，我們也無法通過修改讓它能繼續正常工作。因此，這個程式的價值將成為0；&lt;/li&gt;
&lt;li&gt;如果某程式目前無法正常工作，但是我們可以很容易地修改它，那麼將它改好，並且隨著需求變化不停地修改它，都應該是很容易的事。因此，這個程式會持續產生價值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;業務部門與研發部門經常犯的共同錯誤是沒有把真正緊急並且重要的功能和緊急但是不重要的功能分開，結果就是重要的系統架構問題讓位給了不重要的系統行為功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平衡系統架構的重要性與功能的緊急程度這件事，是軟體研發人員自己的職責&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;筆者注：同時，即便同樣是研發人員，不同的小組也會存在類似問題，正如某些前端部門認為後臺只需要提供一系列介面很容易，而不知道良好設計的重要性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="为好的软件架构持续斗争"&gt;為好的軟體架構持續鬥爭✊&lt;/h3&gt;
&lt;p&gt;如果忽視軟體架構的價值，系統將會變得越來越難以維護，終會有一天，系統將會變得再也無法修改。如果系統變成了這個樣子，那麼說明軟體開發團隊沒有和需求方做足夠的抗爭，沒有完成自己應盡的職責。&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>實用分享：如何將 PyTorch 模型參數載入 MindSpore 模型？</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%AE%9E%E7%94%A8%E5%B9%B2%E8%B4%A7%E5%A6%82%E4%BD%95%E6%8A%8Apytorch%E6%A8%A1%E5%9E%8B%E5%8F%82%E6%95%B0%E5%8A%A0%E8%BD%BD%E5%88%B0mindspore%E6%A8%A1%E5%9E%8B/</link>
      <pubDate>Fri, 01 Sep 2023 22:53:58 +0530</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%AE%9E%E7%94%A8%E5%B9%B2%E8%B4%A7%E5%A6%82%E4%BD%95%E6%8A%8Apytorch%E6%A8%A1%E5%9E%8B%E5%8F%82%E6%95%B0%E5%8A%A0%E8%BD%BD%E5%88%B0mindspore%E6%A8%A1%E5%9E%8B/</guid>
      <description>&lt;h3 id="问题简述"&gt;問題簡述&lt;/h3&gt;
&lt;p&gt;在日常的模型開發、訓練過程中我們經常會遇到這樣的現象：在現有的開源專案或者論文復現中，多數模型使用Pytorch設計、開發和訓練推理，當我們需要使用MindSpore框架進行模型開發時，會遇到以下兩個問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型使用Pytorch編碼；&lt;/li&gt;
&lt;li&gt;Pytorch模型訓練後儲存的引數無法被MindSpore模型直接載入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對於第一個問題，我們可以根據昇思官方提供的文件：&lt;a href="https://www.mindspore.cn/docs/zh-CN/r2.1/migration_guide/typical_api_comparision.html#%E4%B8%8Epytorch%E5%85%B8%E5%9E%8B%E6%8E%A5%E5%8F%A3%E5%8C%BA%E5%88%AB"&gt;與Pytorch典型區別&lt;/a&gt;和&lt;a href="https://www.mindspore.cn/docs/zh-CN/r2.1/note/api_mapping/pytorch_api_mapping.html#pytorch%E4%B8%8Emindspore-api%E6%98%A0%E5%B0%84%E8%A1%A8"&gt;PyTorch與MindSpore API對映表&lt;/a&gt;來完成模型的遷移；&lt;/p&gt;
&lt;p&gt;對於模型引數的轉換，在最新的MindSpore版本中MindConverter不再支援，因此可以考慮針對模型引數，我們進行&lt;strong&gt;手動的轉換&lt;/strong&gt;，將Pytorch模型引數轉換為MindSpore能識別的格式後，再進行載入。&lt;/p&gt;
&lt;h3 id="解决方案"&gt;解決方案&lt;/h3&gt;
&lt;p&gt;模型的編碼轉換不再贅述。&lt;/p&gt;
&lt;p&gt;引數轉換主要思路如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用Pytorch載入Pytorch模型，並取得模型引數prams_torch；&lt;/li&gt;
&lt;li&gt;使用MindSpore載入MindSpore模型，並取得模型引數prams_ms；&lt;/li&gt;
&lt;li&gt;將Pytorch模型的引數名和MindSpore模型引數名一一對應（有的話）；&lt;/li&gt;
&lt;li&gt;建立torch_2_ms鍵名對映表，使用鍵名對映表將Pytorch模型引數值載入到MindSpore引數名對應的位置上；&lt;/li&gt;
&lt;li&gt;使用MindSpore載入引數。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="案例分析"&gt;案例分析&lt;/h3&gt;
&lt;p&gt;不同模型的模組不相同，引數型別也不盡相同，此處我們以一個網路舉例，說明轉換的基本思路，不同的模型其轉換思路是類似的。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://arxiv.org/abs/1905.11946"&gt;EfficientNet&lt;/a&gt;是谷歌於2019年發表的文章，詳細網路架構可檢視文章描述，此處我們以&lt;strong&gt;EfficientNet+FC&lt;/strong&gt;全連線層的模型為例，探討如何進行網路模型引數的轉換。&lt;/p&gt;
&lt;h4 id="使用pytorch加载pytorch模型并取得模型参数prams_torch"&gt;使用Pytorch載入Pytorch模型，並取得模型引數prams_torch&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; torch
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; test.efficientnet_pytorch.model &lt;span style="color:#f92672"&gt;import&lt;/span&gt; EfficientNet &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; EN_pytorch
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pytorch_model &lt;span style="color:#f92672"&gt;=&lt;/span&gt; EN_pytorch&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_name(cfg[&lt;span style="color:#e6db74"&gt;'model'&lt;/span&gt;], override_params&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;'num_classes'&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pytorch_model&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cuda()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pytorch_weights_dict &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pytorch_model&lt;span style="color:#f92672"&gt;.&lt;/span&gt;state_dict()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;param_torch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pytorch_weights_dict&lt;span style="color:#f92672"&gt;.&lt;/span&gt;keys()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;param_torch_lst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame(param_torch)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;param_torch_lst&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_csv(&lt;span style="color:#e6db74"&gt;'param_torch.csv'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;步驟結束後，我們就將pytorch的模型引數存到了param_torch.csv下，觀察資料：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;_conv_stem.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;_bn0.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;_bn0.bias&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;_bn0.running_mean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;_bn0.running_var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;_bn0.num_batches_tracked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;_blocks.0._depthwise_conv.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;_blocks.0._bn1.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;_blocks.0._bn1.bias&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;_blocks.0._bn1.running_mean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;_blocks.0._bn1.running_var&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="使用mindspore加载mindspore模型并取得模型参数prams_ms"&gt;使用MindSpore載入MindSpore模型，並取得模型引數prams_ms&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; mindspore &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; test.efficientnet_mindspore.model &lt;span style="color:#f92672"&gt;import&lt;/span&gt; EfficientNet &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; EN_ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mindspore_model &lt;span style="color:#f92672"&gt;=&lt;/span&gt; EN_ms&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_name(cfg[&lt;span style="color:#e6db74"&gt;'model'&lt;/span&gt;], override_params&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;'num_classes'&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;prams_ms &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mindspore_model&lt;span style="color:#f92672"&gt;.&lt;/span&gt;parameters_dict()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;keys()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;prams_ms_lst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame(prams_ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;prams_ms_lst&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_csv(&lt;span style="color:#e6db74"&gt;'prams_ms.csv'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;步驟結束後，我們就將MindSpore的模型引數存到了prams_ms.csv下，觀察資料：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;keys&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;_conv_stem.weight&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;_bn0.moving_mean&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;_bn0.moving_variance&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;_bn0.gamma&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;_bn0.beta&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0._depthwise_conv.weight&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0._bn1.moving_mean&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0._bn1.moving_variance&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0._bn1.gamma&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0._bn1.beta&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0._se_reduce.weight&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="将pytorch模型的参数名和mindspore模型参数名一一对应"&gt;將Pytorch模型的引數名和MindSpore模型引數名一一對應&lt;/h4&gt;
&lt;p&gt;自此我們就得到了MindSpore和Pytorch各自的引數鍵名錶（附在附件區域），隨後觀察二者引數命名上的差異，可以發現固定的規律，以下述幾個方面為例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Batch Normalization：
&lt;ul&gt;
&lt;li&gt;權重：weight|bias——gamma|beta；&lt;/li&gt;
&lt;li&gt;移動加權和方差：running_mean|running_var——moving_mean|moving_variance；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自定義blocks：pytorch帶前置的_blocks.；&lt;/li&gt;
&lt;li&gt;其他&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="键名映射表"&gt;鍵名對映表&lt;/h4&gt;
&lt;p&gt;這樣就可以根據規律寫出一個Python指令碼來完成鍵名的轉化，並生成鍵名對映表：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pytorch&lt;/th&gt;
&lt;th&gt;mindspore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;_conv_stem.weight&lt;/td&gt;
&lt;td&gt;_conv_stem.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_bn0.weight&lt;/td&gt;
&lt;td&gt;_bn0.gamma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_bn0.bias&lt;/td&gt;
&lt;td&gt;_bn0.beta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_bn0.running_mean&lt;/td&gt;
&lt;td&gt;_bn0.moving_mean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_bn0.running_var&lt;/td&gt;
&lt;td&gt;_bn0.moving_variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._depthwise_conv.weight&lt;/td&gt;
&lt;td&gt;0._depthwise_conv.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._bn1.weight&lt;/td&gt;
&lt;td&gt;0._bn1.gamma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._bn1.bias&lt;/td&gt;
&lt;td&gt;0._bn1.beta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._bn1.running_mean&lt;/td&gt;
&lt;td&gt;0._bn1.moving_mean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._bn1.running_var&lt;/td&gt;
&lt;td&gt;0._bn1.moving_variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_blocks.0._se_reduce.weight&lt;/td&gt;
&lt;td&gt;0._se_reduce.weight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;隨後在Pytorch的權重字典中，按照對應檔案的Pytorch_key取出權重值，隨後使用mindspore.Parameter進行封裝，新增到mindspore.key對應的權值中去：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; ms_param_lst&lt;span style="color:#f92672"&gt;.&lt;/span&gt;values:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    ms_key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; i
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    pt_key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; param_mapping[ms_key]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    pt_val &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pt_values_dict[pt_key]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; isinstance(pt_val, np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ndarray):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        pt_val &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pt_val&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cpu()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numpy()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    ms_val &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Parameter(pt_val, ms_key)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    print(ms_val)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    ms_values_dict[ms_key] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ms_val
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="使用mindspore加载参数"&gt;使用MindSpore載入引數&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;load_param_into_net(mindspore_model, ms_values_dict)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此時，引數應該就可以被MindSpore接受了。&lt;/p&gt;
&lt;h3 id="whats-more"&gt;What’s more&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在引數值的儲存過程中，要注意Pytorch和MindSpore引數精度的差異；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（完）&lt;/p&gt;
</description>
    <category>機器學習</category><category>模型遷移</category></item>
    <item>
      <title>梯度之上：Hessian 矩陣</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/h/</link>
      <pubDate>Fri, 01 Sep 2023 22:53:58 +0530</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/h/</guid>
      <description>&lt;p&gt;本文討論研究梯度下降法的一個有力的數學工具：海森矩陣。在討論海森矩陣之前，需要首先了解梯度和雅克比矩陣的基本概念。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⭐本文假設讀者已經熟悉梯度下降法和簡單的數值分析、線性代數知識
&lt;a href="https://tommycheese.github.io/blogs/%E6%A2%AF%E5%BA%A6%E4%B9%8B%E4%B8%8Ahessian-%E7%9F%A9%E9%98%B5/"&gt;原文連結&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="梯度雅克比矩阵"&gt;梯度、雅克比矩陣&lt;/h2&gt;
&lt;p&gt;梯度下降演算法需要當前函式點的導數資訊，當此函式點包含多個方向時，梯度是包含所有方向的（偏）導數向量。&lt;/p&gt;
&lt;p&gt;上述情況對應於&lt;strong&gt;輸出為一個&lt;/strong&gt;的情況，當函式的輸出也為一個向量時，我們需要把輸出向量的每一個元素對於多個輸入的梯度&lt;strong&gt;羅列在一起&lt;/strong&gt;，羅列形成的矩陣就是&lt;strong&gt;雅克比矩陣（Jacobian Matrix）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;舉例說明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若函式$f$接受三個輸入$x1、x2、x3$，產生一個輸出$y$，則其梯度為：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{equation}
Grad = [\frac{\partial y}{\partial x_1}, \frac{\partial y}{\partial x_2}, \frac{\partial y}{\partial x_3}]
\end{equation}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若函式$f2$接受三個輸入$x1、x2、x3$，產生三個輸出$y1、y2、y3$，則其雅克比矩陣為：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{equation}
Jacobian  = \begin{bmatrix}
\frac{\partial y_1}{\partial x_1} &amp;amp;  \frac{\partial y_1}{\partial x_2}&amp;amp;\frac{\partial y_1}{\partial x_3} \
\frac{\partial y_2}{\partial x_1} &amp;amp;  \frac{\partial y_2}{\partial x_2}&amp;amp;\frac{\partial y_2}{\partial x_3} \
\frac{\partial y_3}{\partial x_1} &amp;amp;  \frac{\partial y_3}{\partial x_2}&amp;amp;\frac{\partial y_3}{\partial x_3}
\end{bmatrix}
\end{equation}
$$&lt;/p&gt;
&lt;p&gt;利用二階導數，我們可以知道關於函式在特定方向 $d$ 上的凹凸資訊，利用凹凸資訊可以在一定程度上預判梯度下降法的表現效果。如果在特定方向 $d$ 上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;二階導數為正，則函式在方向$d$上一階導數增加，函式值下降更慢；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;二階導數為負，則函式在方向$d$上一階導數減少，函式值下降更快；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;二階導數為零，則函式在方向$d$上一階導數不變，函式值勻速下降；&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⭐注意在梯度下降法中是對損失函式進行下降，因此需要使用減函式來分析函式中&lt;strong&gt;某一小段&lt;/strong&gt;（經常使用二次函式的減半部近似：二階泰勒展開、牛頓法）中的導數變化情況；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="海森矩阵"&gt;海森矩陣&lt;/h2&gt;
&lt;p&gt;和雅克比矩陣類似，&lt;strong&gt;海森矩陣（Hessian 矩陣）&lt;strong&gt;可以包含函式中二階導的資訊：
$$
Hessian   = \begin{bmatrix}
\frac{\partial^2y}{\partial x_1\partial x_1} &amp;amp;  \frac{\partial^2y}{\partial x_1\partial x_2}&amp;amp;\frac{\partial^2y}{\partial x_1\partial x_3} \
\frac{\partial^2y}{\partial x_2\partial x_1} &amp;amp;  \frac{\partial^2y}{\partial x_2\partial x_2}&amp;amp;\frac{\partial^2y}{\partial x_2\partial x_3} \
\frac{\partial^2y}{\partial x_3\partial x_1} &amp;amp;  \frac{\partial^2y}{\partial x_3\partial x_2}&amp;amp;\frac{\partial^2y}{\partial x_3\partial x_3}
\end{bmatrix}
$$
同時由於二階導數計算順序的可交換性，即 $\frac{\partial^2y}{\partial x_1\partial x_2}=\frac{\partial^2y}{\partial x_2\partial x_1}$，因此&lt;/strong&gt;海森矩陣是一個對稱矩陣&lt;/strong&gt;，對於對稱矩陣我們可以使用&lt;strong&gt;特徵分解&lt;/strong&gt;來研究特徵值和二階導數的關係，便於我們快速獲得某個方向的二階導數。&lt;/p&gt;
&lt;p&gt;針對於特定方向d，已知此方向的二階導數可以寫成 $d^THd$ ，則：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;a href="https://blog.csdn.net/weixin_42397505/article/details/112066943"&gt;基於Hessian矩陣的二階方向導數與性質_Hi 喀什噶爾的胡楊的部落格-CSDN部落格_二階方向導數&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;若d是H對應特徵值λ的特徵向量：&lt;/p&gt;
&lt;p&gt;因為d為對應λ的特徵向量（以下簡稱特徵向量），則據定義有：
$$
Hd = \lambda d\
\Rightarrow  d^THd=d^T\lambda d = \lambda d^Td=\lambda    \ \ \ 对称矩阵d^T = d^-
$$&lt;/p&gt;
&lt;p&gt;因此特徵向量對應的特徵值λ即為此方向的二階導數；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若d為其他方向：設$e_i$為$H$對應特徵值$\lambda_i$的特徵向量，由上可知，
$$
\lambda_i=e_i^THe_i
$$
可知任何一個方向$d=\sum_i^mt_ie_i$為特徵向量的線性組合，其中m為特徵值個數，$t_i$為第$i$個特徵向量的加權數，則：
$$
d^THd=(\sum_i^mt_ie_i)^TH(\sum_i^mt_ie_i)=\sum_i^mt_ie_i^THt_ie_i=\sum_i^mt_i^2\lambda_i
$$
因此任意非特徵向量方向的二階導數是所有特徵值的加權和，特別的，此時的加權和是一個橢球體，在二維的特徵值情況下，二階導數是一個橢圓，橢圓方程為：
$$
y=\frac{\lambda_1}{\frac{1}{t_1^2}}+\frac{\lambda_2}{\frac{1}{t_2^2}}
$$
&lt;img src="https://img-blog.csdnimg.cn/img_convert/bb30779d25d486346799cb0fce7d34ad.png#pic_center" alt="在這裡插入圖片描述"&gt;&lt;/p&gt;
&lt;p&gt;由圖可知，最大二階導數由最大特徵值決定（長半軸），而最小二階導數由最小特徵值決定（短半軸）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="海森矩阵应用"&gt;海森矩陣應用&lt;/h2&gt;
&lt;p&gt;在弄清海森矩陣的基本定義後，就可以使用海森矩陣的一些性質來分析最佳化方法中的一些問題了。如確定區域性最大點、區域性最小點和鞍點、確定學習率、以及使用病態條件來確定梯度下降的表現等，同時我們還可以利用Hessian矩陣來實現&lt;strong&gt;牛頓法&lt;/strong&gt;這種最佳化演算法，。&lt;/p&gt;
&lt;p&gt;（本節完）&lt;/p&gt;
</description>
    <category>機器學習</category><category>數學基礎</category></item>
    <item>
      <title>探索</title>
      <link>https://tommycheese.github.io/zh-TW/gallery/</link>
      <pubDate>Sat, 25 Jun 2022 18:35:46 +0530</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/gallery/</guid>
      <description></description>
    </item>
  </channel>
</rss>
