<?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/ru/</link>
    <description>Личный блог Tommy Cheese о программной инженерии, искусственном интеллекте и практическом обучении.</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>ru</language>
    <lastBuildDate>Sun, 13 Sep 2026 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/ru/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Расширения OpenCode v2: устройство и интеграция</title>
      <link>https://tommycheese.github.io/ru/blogs/opencode-v2-extensions/</link>
      <pubDate>Sun, 13 Sep 2026 00:00:00 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/opencode-v2-extensions/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Дата исследования: 2026-09-13. Статья описывает общие возможности плагинов OpenCode v2: типы, загрузку, регистрацию возможностей, хуки выполнения, границы разрешений и жизненный цикл. Материал предназначен для разработки плагинов и встраиваемой интеграции.&lt;/p&gt;
&lt;p&gt;Исследование основано на ветке  &lt;code&gt;v2&lt;/code&gt;  изученного репозитория OpenCode, коммит  &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/ru/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;Встраивание OpenCode в собственное приложение JS/TS и управление им&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-клиент и  &lt;code&gt;ctx.plugin.list()&lt;/code&gt;  из Plugin Context также не являются этой встраиваемой точкой регистрации.&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;Повторная передача того же ID в один SDK-реестр заменяет определение и создаёт новую ревизию. Разные хосты друг друга не перезаписывают. Возврат из регистрации означает лишь запись определения и публикацию обновления, а не завершение активации во всех 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;Повторная регистрация одного ID в одном SDK-реестре&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 текущего вызова, не сохраняя ссылку на него и не создавая внешних бизнес-эффектов при пересборке. Внешние данные следует сначала загрузить и сохранить, а затем вызвать у соответствующей области  &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; Плагин регистрирует callback в точке расширения. Когда OpenCode достигает соответствующего узла, он автоматически вызывает callback и передаёт контекст узла. В рамках контракта интерфейса плагин может читать данные, менять параметры или обрабатывать результаты, участвуя в запросах модели, выполнении инструментов и других процессах.&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;  и другие интерфейсы объявляет нужные узлы. Хост вызывает callback только при достижении узла во время выполнения. После одной регистрации callback может срабатывать многократно при повторных прохождениях узла. Хост ждёт завершения callback и продолжает согласно контракту; например,  &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 видит изменения ранних. Если несколько плагинов меняют одно поле, проверяйте окончательный порядок загрузки. Встроенные плагины разделены на предшествующий и последующий наборы; нельзя считать, что внешние всегда перезаписывают последними.&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;ID ассистента и модели задают идентичность контекста; изменение сообщений влияет только на текущий запрос и не означает добавления в постоянную историю&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 нельзя считать универсальным middleware для произвольного выбрасывания бизнес-ошибок.&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: определение Tool через Plugin Transform&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;  содержит ограниченное дополнительное состояние. Возврат  &lt;code&gt;output&lt;/code&gt; без объявления выходной Schema считается ошибкой в текущей реализации.&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;Location плагина&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Экземпляры плагинов управляются по Location; постоянное бизнес-состояние нужно хранить отдельно.&lt;/strong&gt; Location — контекст выполнения из каталога и необязательного нативного ID Workspace. Один процесс OpenCode может обслуживать несколько 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>Разработка агентов</category><category>Разработка плагинов</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (6): границы</title>
      <link>https://tommycheese.github.io/ru/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/ru/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/ru/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/ru/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/ru/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/ru/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 означает вышку, в найме — организационную платформу, в транспорте — перрон. Когда три направления неизбежно пересекутся, начнётся беда: что делать с экраном, заполненным почти одинаковыми именами переменных с разным смыслом?&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. Операции должны быть по возможности низкоуровневыми, иметь короткие имена и не быть слишком многочисленными. MyBatis Mapper в Java можно рассматривать как реализацию 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;Обычная последовательность моделирования:&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/ru/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/ru/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;Перечитывая «Чистую архитектуру» (4): принципы построения компонентов&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. Оба принципа можно выразить так:&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 выражается так:&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;Команда проектирует систему с помощью DDD. Сущность Entity Dog зависит от Dog Repo для операции сохранения Save, поэтому модуль Entity зависит от Repo. К несчастью, Dog PO тоже определён в Entity — неудачное решение. Функции Save в Repo нужен PO для преобразования и сохранения объекта. Возникает цикл Entity ↔ Repo. К счастью, Go запрещает взаимные зависимости модулей, и проверка сообщает о циклическом импорте. Как поступить?&lt;/p&gt;
&lt;p&gt;Нужно создать модуль PO, перенести в него PO из Entity и отнести туда функции Repo, зависящие от PO. Это устраняет цикл. Другой вариант — внедрение зависимостей. При использовании SprintBoot проблему может решить @AutoWired, но по существу он также создаёт новый компонент — пул зависимостей, — устраняя цикл.&lt;/p&gt;
&lt;p&gt;Подобных примеров много. Помните конфликты версий библиотек при установке пакетов Python через anaconda?&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 — исходящих. Чем меньше у компонента исходящих зависимостей, тем он устойчивее: при нулевом значении отсутствуют внешние факторы, заставляющие его меняться.&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;Но различия остаются. Для класса нет серой зоны: он либо абстрактный, либо нет. SDP и SAP применяются к компонентам, которые могут быть частично абстрактными и частично устойчивыми.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Во-вторых, общность целей SAP и SDP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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 над нулём помогает направлять рефакторинг и перепроектирование компонентов.&lt;/p&gt;
&lt;p&gt;У D есть и другие применения. Например, среднее значение и дисперсия D всех компонентов позволяют статистически оценивать устройство системы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;У хорошо спроектированной системы среднее значение и дисперсия D должны быть близки к нулю.&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/ru/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/ru/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;Перечитывая «Чистую архитектуру» (3): принципы 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 прежде всего объясняют, как объединять данные и функции в классы, а затем связывать классы в программу. Они направляют проектирование модулей; на уровне архитектуры действуют другие принципы.&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 как плагины, чтобы легко добавлять новые, не изменяя прежние.&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 — взаимозаменяемость: если для каждого объекта o1 типа S существует объект o2 типа T, причём поведение программы P, работающей с T, не меняется при замене 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 лишь реализует его; связь с 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>Перечитывая «Чистую архитектуру» (2): парадигмы программирования</title>
      <link>https://tommycheese.github.io/ru/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/ru/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;Перечитывая «Чистую архитектуру» (2)&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/ru/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/ru/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;Чистая архитектура — Роберт К. Мартин — WeChat Read (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;Если программа работает, но её нельзя изменить, при изменении требований она перестанет работать должным образом. Исправить её для новых условий также не получится. В результате её ценность станет нулевой.&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/ru/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/ru/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;Таблица соответствия API PyTorch и MindSpore&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Для преобразования параметров MindConverter в рассматриваемой актуальной версии MindSpore больше не поддерживается. Поэтому можно выполнить &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 и получение параметров 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 и получение параметров 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>За пределами градиента: матрица Гессе</title>
      <link>https://tommycheese.github.io/ru/blogs/h/</link>
      <pubDate>Fri, 01 Sep 2023 22:53:58 +0530</pubDate>
      <guid>https://tommycheese.github.io/ru/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;Вторые производные по направлению и свойства матрицы Гессе — статья в блоге 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;Разобравшись с определением, можно применять свойства матрицы Гессе для анализа задач оптимизации: определять локальные максимумы, минимумы и седловые точки, выбирать скорость обучения и оценивать поведение градиентного спуска при плохой обусловленности. Матрица Гессе также позволяет реализовать &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/ru/gallery/</link>
      <pubDate>Sat, 25 Jun 2022 18:35:46 +0530</pubDate>
      <guid>https://tommycheese.github.io/ru/gallery/</guid>
      <description></description>
    </item>
  </channel>
</rss>
