<?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/ja/</link>
    <description>ソフトウェア工学、人工知能、学びと実践について綴る Tommy Cheese の個人ブログ。</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>ja</language>
    <lastBuildDate>Sun, 13 Sep 2026 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/ja/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>OpenCode v2 拡張機構の調査と導入ガイド</title>
      <link>https://tommycheese.github.io/ja/blogs/opencode-v2-extensions/</link>
      <pubDate>Sun, 13 Sep 2026 00:00:00 +0800</pubDate>
      <guid>https://tommycheese.github.io/ja/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; 現在のOpenCode v2の実装では、Extensionは主にPluginの仕組みに相当します。プラグインは、アシスタント、ツール、スキル、モデル、コマンドなどを提供し、指定された実行箇所で振る舞いを調整できます。セッションの実行、モデルリクエスト、ツール結果、実行記録は、引き続きOpenCodeのコアが処理します。&lt;/p&gt;
&lt;figure class="plugin-flow"&gt;&lt;img src="https://tommycheese.github.io/blogimages/ja/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; いずれもアシスタント、ツール、実行フックを登録できます。主な違いは、誰が定義を提供するか、どのようにホストへ読み込むか、ホストがコアの内部サービスを追加で注入するかです。ここで扱うのはサーバー側のプラグインです。ターミナルUIの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は、ホストのレジストリから現在の定義を読み取ります。したがって、「ホストが一度だけ登録した」ことは、プラグインの初期化が一度だけ実行されることを意味しません。クロージャー内の可変オブジェクトも、同じホストの複数の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の中核実装その1：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の中核実装その2：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;例1：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;例2：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>『クリーンアーキテクチャ』再読（6）：境界</title>
      <link>https://tommycheese.github.io/ja/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/ja/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>『クリーンアーキテクチャ』再読（5）：ソフトウェアアーキテクチャ</title>
      <link>https://tommycheese.github.io/ja/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/ja/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は関数を通じて入出力デバイスとやり取りし、開発者はその具体的な実装だけを提供します。こうして入出力は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;適切に分離できていれば、システムの実行中に各レイヤーの実装やユースケースをホットスワップできます。この場合、新しいユースケースを追加するには、新しい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/ja/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/ja/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;採用のドメインでは「プラットフォーム」という言葉で、出身の学校や組織を表すことがあります。飛び込み競技では、選手が飛び込む台を指します。鉄道輸送のドメインでは、駅のホームを表すかもしれません。ある日、飛び込み選手、人事担当者、乗務員が偶然集まり、お互いの職業を知らないまま、人事担当者が選手に「どのプラットフォームの出身ですか」と尋ねたら……。&lt;/p&gt;
&lt;p&gt;同じ「プラットフォーム」という言葉でも、三人の間に&lt;strong&gt;取り決めがなければ&lt;/strong&gt;、理解が異なる可能性があります。この問題を解決するには、飛び込み、採用、交通の各ドメインを分離し、それぞれで用語を定義します。一つのドメイン内で話せば、曖昧さをできる限り避けられます。これが取り決めです。業務に関係するドメインを一つのコンテキストに定義するのがよい理由も、曖昧さを減らすためです。&lt;/p&gt;
&lt;p&gt;ソフトウェアシステムの設計でも、同じような問題が起こります。飛び込み、採用、乗車の各サービスを含む大きな製品を設計しているとします。サービスを一切分割せず、飛び込みの業務では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へ配信する操作などが例です。そのため、Serviceを抽象化した名前は通常、動詞になります。&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>『クリーンアーキテクチャ』再読（4）：コンポーネントの原則</title>
      <link>https://tommycheese.github.io/ja/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/ja/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;『Clean Architecture』再読（四）コンポーネント構築の原則&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はコード再利用の観点から考える原則です。同じテーマと機能を持つコードをまとめてコンポーネントにし、ソフトウェアをコンポーネント単位で再利用することを勧めます。&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は、利用場面や利用頻度が異なるコードを、別々のコンポーネントへ分けることを求めます。不要な分割を避けるための原則でもあり、インターフェース分離の原則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は、Saveによる保存のためにDog Repoに依存します。つまり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も、コンポーネントを安定させつつ十分な柔軟性を保つためのものです。どちらもアーキテクチャ上では、具象実装から抽象クラス／インターフェースへ依存を向けます。そのため、役割としては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>『クリーンアーキテクチャ』再読（3）：SOLID 設計原則</title>
      <link>https://tommycheese.github.io/ja/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/ja/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;『Clean Architecture』再読（三）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でo2をo1に置き換えても振る舞いが変わらないなら、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はそのインターフェースを実装し、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;具象実装を持つ関数をオーバーライドしない。&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>『クリーンアーキテクチャ』再読（2）：プログラミングパラダイム</title>
      <link>https://tommycheese.github.io/ja/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/ja/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;『Clean Architecture』再読（二）&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;科学理論や科学法則は反証できますが、証明することはできません。同様に、プログラムもテストによって反証できるだけで、正しさを証明することはできません。つまり「テストはバグの存在を示せるだけで、バグがないことは証明できない」のです。科学的方法を使うと、構造化プログラミングは、まずプログラムを証明可能な小さな関数へ再帰的に分解し、それから関連する&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>『クリーンアーキテクチャ』再読（1）</title>
      <link>https://tommycheese.github.io/ja/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/ja/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;なぜ「再読」なのでしょうか。以前にも『Clean Architecture』を一度読みましたが、その頃は実際のプロジェクトで経験を積み、試行錯誤する機会が少なく、理解が浅いと感じていました。その後、仕事でいくつかのプロジェクトや要件に取り組む機会に恵まれました。さらに幸運なことに、チームが採用していたのはクリーンアーキテクチャで、詳しく言えばイベント駆動開発のDDDとクリーンアーキテクチャを組み合わせたものでした。DDDの詳細は後で取り上げます。そこで本書📚を読み直したところ、多くの学びがありました。このシリーズでは、私なりの理解でクリーンアーキテクチャについて説明します。より正確には読書ノートとして、同じ分野に携わる方々と共有したいと思います。&lt;/p&gt;
&lt;p&gt;本書の詳細はこちらです：&lt;a href="https://weread.qq.com/web/bookDetail/480322f072021a3248038c8"&gt;『Clean Architecture』—ロバート・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;筆者注：開発者同士でも、チームが異なれば似た問題が起こります。たとえば、バックエンドは一連のAPIを提供するだけなので簡単だと考え、適切な設計の重要性を理解していないフロントエンド部門もあります。&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/ja/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/ja/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;一つ目の問題は、MindSporeの公式ドキュメントである&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;は、Googleが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;補足&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>勾配の先へ：ヘッセ行列</title>
      <link>https://tommycheese.github.io/ja/blogs/h/</link>
      <pubDate>Fri, 01 Sep 2023 22:53:58 +0530</pubDate>
      <guid>https://tommycheese.github.io/ja/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;二階微分が0なら、方向$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行列に基づく二階方向微分とその性質—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/ja/gallery/</link>
      <pubDate>Sat, 25 Jun 2022 18:35:46 +0530</pubDate>
      <guid>https://tommycheese.github.io/ja/gallery/</guid>
      <description></description>
    </item>
  </channel>
</rss>
