<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>baseballyama&apos;s Blog</title>
		<link>https://blog.baseballyama.com/ja/blog</link>
		<atom:link href="https://blog.baseballyama.com/ja/rss.xml" rel="self" type="application/rss+xml" />
		<description>Yuichiro Yamashita (baseballyama) — software engineer working on compilers, parsers and static analysis. Svelte core team, VP of Technology at Flyle.</description>
		<language>ja</language>
		<item>
			<title>rsvelte: AI 時代の Svelte ツールチェーンを Rust で作り直す</title>
			<link>https://blog.baseballyama.com/ja/posts/20260721-rsvelte</link>
			<guid>https://blog.baseballyama.com/ja/posts/20260721-rsvelte</guid>
			<pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>私が働いている [Flyle](https://flyle.io/jp) では、複数の AI エージェントを並列で走らせて開発することが当たり前になっている。この開発スタイルでは、エージェントが実装のたびに型検査や Lint を回すため、</description>
			<content:encoded><![CDATA[<p>私が働いている <a href="https://flyle.io/jp">Flyle</a> では、複数の AI エージェントを並列で走らせて開発することが当たり前になっている。この開発スタイルでは、エージェントが実装のたびに型検査や Lint を回すため、静的検査の実行回数が人間だけで開発していた頃とは比較にならないほど増える。検査が並列で走れば CPU とメモリを使い切り、1 回あたりの検査時間はさらに伸びる。エージェントは検査が終わるまで次の修正に進めないので、<strong>静的検査の所要時間が、そのまま 1 ループの所要時間の下限になる</strong>。</p>
<p>そして Svelte プロジェクトでは、この静的検査が特に遅い。この問題を解決するために、私は <a href="https://github.com/baseballyama/rsvelte">rsvelte</a> という、Svelte のツールチェーンを Rust で作り直すプロジェクトを開発している。この記事では、なぜ今それが必要なのか、rsvelte が何をするものなのかを整理する。</p>
<h2>tl;dr</h2>
<ul>
<li>AI エージェントはループ毎に静的検査を回すため、検査の所要時間が 1 ループの所要時間の下限になり、CPU/メモリ消費も並列数を制約する</li>
<li>Rust / ネイティブベースのツールは静的検査を桁で高速化しつつあるが、<code>.svelte</code> の Svelte 固有処理(パース・変換・Lint・フォーマット)には JavaScript 実装が残っている</li>
<li>rsvelte は Svelte ツールチェーンのドロップイン代替を目標とする Rust 実装群である。言語仕様への独自拡張は加えない。early stage で、到達度はパッケージごとに異なる</li>
<li>Flyle の本番フロントエンド(公式構成で 8,795 ファイルを検査)の型検査は、rsvelte-check への置き換えだけで 51.4s → 30.6s(1.7 倍、CPU 約 56% 減)、検査エンジンの tsgo 化を合わせて 9.0s(5.7 倍)</li>
<li>実行ルール集合を揃えた Svelte 固有 Lint は約 20 倍(診断 382 件は両実装で完全一致)。8 並列の高負荷条件では両構成に共通する TypeScript 処理がボトルネックになり型検査の差は縮む。価値は個々の検査を速く終わらせて重複区間を減らす点にあると考えている</li>
</ul>
<h2>1. なぜ今、静的検査の「速度」なのか</h2>
<h3>1.1 実装者が AI になり、品質保証はハーネスに移った</h3>
<p>AI エージェントが実装の主体になると、品質を支える仕組みの重心が変わる。人間中心の開発でも静的検査は重要だったが、エージェントが書く時代には、<strong>エージェントの出力を機械的に検証するハーネス</strong>が品質の主な担い手になる。</p>
<p>ハーネスにはいくつかのレイヤーがある。仕様や設計をドキュメントや型で表現しエージェントの前提を固定する設計ハーネス、実装のたびに出力を検証する開発ハーネス、振る舞いを検証するテストハーネスなどだ。それぞれに論じる価値があるが、この記事では開発ハーネスの中核である<strong>静的検査基盤</strong>(フォーマッター、型検査、Linter、未使用コード検出)に絞る。</p>
<h3>1.2 静的検査の実行モデルが変わった</h3>
<p>静的検査そのものは昔からある。変わったのは実行モデルだ。</p>
<p>人間中心の開発でも静的検査は重要だった。ただし、一人の開発者が短時間に何度もフル検査を起動し、さらに複数の作業ツリーで同じ検査を並列実行することは、現在のエージェント開発ほど一般的ではなかった。</p>
<p>エージェントは違う。Codex CLI のように Language Server を利用しないエージェントもあり、Claude Code のように利用できる場合でも、並列インスタンスが Language Server を共有するとは限らない。各インスタンスが独立した Language Server を起動すれば、その解析処理とメモリ消費は並列数に応じて重複する。さらに、LSP の診断範囲や設定が CI と一致するとは限らないため、多くの環境では最終的な品質ゲートとして CI と同じ CLI 検査も必要になる。1 つのタスクの中で何度も回すし、複数のエージェントを並列で走らせれば、その数だけ検査も並列に走る。つまり静的検査は、比較的低頻度・低並列で実行するものから、高頻度・高並列で実行するものに変わった。</p>
<p>正確に言えば、これは AI に固有の問題ではない。CI や pre-commit のように CLI からフル検査を回す仕組みは以前からあり、フル検査の遅さも昔からの既知の問題だった。変わったのは実行の頻度と並列度である。静的検査がボトルネックになるワークロードは以前から少しずつ増えていたが、AI エージェントによる並列開発は、その問題を最も極端な形で表面化させた。この記事が AI を軸に論じるのはそのためで、以降の議論は CLI でフル検査を大量に実行するワークロード全般に当てはまる。</p>
<h3>1.3 遅さのコストは 2 つの形で現れる</h3>
<p>このとき、検査の遅さはレイテンシとリソース消費という 2 つの側面でコストになる(両者は相互に影響する)。</p>
<p>1 つ目は<strong>レイテンシ</strong>である。検査結果を同期的に待って次の修正方針を決めるループでは、検査時間がそのままクリティカルパスになる。検査に 5 分かかるなら、1 ループは最短でも 5 分だ。私の経験でも、検査一式に数分かかるプロジェクトは珍しくない。本記事で実測する Flyle の本番フロントエンドでも、型検査単体で約 51 秒かかる(3.3)。1 タスクで 5 回検査を回せば、待ち時間はその 5 倍になる。</p>
<p>2 つ目は<strong>リソース</strong>である。現行の Svelte 検査構成は、TypeScript 検査エンジンや変換処理を含むプロセスツリー全体として大きな CPU・メモリを使用する。後述の実測(3.3)では、Flyle の本番フロントエンドの型検査で、現行の JS 構成はプロセスツリー全体で peak 4.2GB のメモリと約 105 CPU 秒を消費した。これが並列で走るとローカルマシンのリソースを使い切ってしまう。つまり検査のリソース消費が、同時に動かせるエージェント数の主要な制約の 1 つになる。</p>
<h3>1.4 「CI でやればいい」「マシンを強くすればいい」だけではダメな理由</h3>
<p>CI に検査を寄せる案は、フィードバックのタイミングを後ろにずらすだけだ。エージェントは検査結果を次の修正に利用するため、ループ内の早いフィードバックが重要になる。検査を回さずに実装を進めれば、誤った前提の上に次の実装が積み上がり、エラーを後からまとめて検出したときの修正コストは膨らむ。次の実装の判断に使うには、検査はループの中で走る必要がある。</p>
<p>マシンの増強も有効ではある。ただし検査プロセスの消費量が変わらなければ、並列数に応じて必要なコア数とメモリも増える。ツール側の効率改善は、同じハードウェアで扱える並列度そのものを高める。</p>
<h2>2. Rust ベースのツールチェーンと Svelte の現状</h2>
<h3>2.1 静的検査ツールを桁違いに高速化する動き</h3>
<p>この問題への回答として、静的検査ツールを Rust やネイティブコードで書き直す動きがすでに進んでいる。</p>
<ul>
<li><a href="https://oxc.rs/">oxc</a> プロジェクトの oxlint は、ESLint の 50〜100 倍(公称)の速度で Lint を実行する</li>
<li><a href="https://github.com/microsoft/typescript-go">tsgo</a>(TypeScript コンパイラの Go 移植)は、tsc 比で約 10 倍の高速化を公称している(<a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">A 10x Faster TypeScript</a>)</li>
<li><a href="https://biomejs.dev/">Biome</a> は Rust 製のフォーマッター・リンターとして同様の高速化を実現している</li>
</ul>
<p>なお、この高速化は「Rust で書けば速くなる」という単純な話ではない。言語の実行効率に加えて、並列処理を前提とした設計、再パースの削減、メモリ効率の良いデータ構造など、実装全体の設計で達成した速度である。</p>
<p>重要なのは、速度が桁で変わると振る舞いも変わることだ。10 分かかる検査は並列エージェントの間で重なり合い、リソースを奪い合う。数秒で終わる検査は、そもそも検査同士が同時に走っている時間帯が短いので、リソース競合自体が起きにくい。レイテンシの改善がリソース問題も同時に緩和する。</p>
<h3>2.2 しかし Svelte 固有の処理には JavaScript 実装が残っている</h3>
<p>ところが、Svelte 固有の処理はこの流れに十分に乗れていない。</p>
<ul>
<li><strong>Lint</strong>: oxlint は alpha 段階の仕組みで <code>.svelte</code> 内の script 部分を抽出して検査できるようになったが、テンプレートや style を含む Svelte 固有のセマンティクスを検査する基盤はまだない。Svelte 固有ルールは JavaScript 製の eslint-plugin-svelte + ESLint に依存したままである</li>
<li><strong>フォーマット</strong>: oxfmt の Svelte 対応は、Svelte の構造部分を JavaScript 製の prettier-plugin-svelte に依存し、埋め込み JS/TS のみを oxc_formatter に委譲する構成である(埋め込み CSS は Prettier 組み込みのフォーマッタで処理する)。そのため <code>.svelte</code> の構造と埋め込み CSS のパースと整形は依然として Prettier 側のコストを受ける</li>
<li><strong>型検査</strong>: svelte-check は svelte2tsx で <code>.svelte</code> を TypeScript に変換して検査エンジンに渡す構成である。検査エンジンは <code>--tsgo</code> で高速化できるようになったが、変換とオーケストレーションは JavaScript の速度で動く</li>
</ul>
<p>つまり、公式ツールでも TypeScript 検査部分は tsgo によって高速化できるようになった一方で、Svelte のパース、svelte2tsx 変換、テンプレートの Lint、フォーマットといった Svelte 固有の処理には、依然として JavaScript 実装が残っている。この構造は Svelte 固有のものではなく、Vue のような独自のテンプレート言語を持つフレームワークも同型の問題を抱えている。</p>
<h2>3. rsvelte</h2>
<p><a href="https://github.com/baseballyama/rsvelte">rsvelte</a> は、このギャップを埋めるためのプロジェクトである。</p>
<h3>3.1 目標: Svelte ツールチェーンのドロップイン代替</h3>
<p>rsvelte の長期目標は、Svelte の既存ツールチェーンの<strong>ドロップイン代替</strong>になることだ。設定ファイルもコマンドの使い方も変えずに、実装だけが Rust に置き換わる状態を目指している。そして、<strong>Svelte の言語仕様やコンパイラの意味論に独自拡張を加えない</strong>ことを設計原則にしている(CLI フラグやキャッシュのような、ツールとしての付加機能はある)。</p>
<p>理由は、最終的に切り替えコストと撤退コストを限りなく小さくするためだ。独自機能を持つ代替ツールは、採用した時点でその機能への依存が生まれ、元のツールに戻る選択肢が失われていく。互換性が保たれていれば、導入して効果を測り、問題があれば戻す、という試行が安全にできる。oxlint が ESLint ルールの忠実な移植によって信頼を得たのと同じ戦略である。そしてこの互換性は、後述する「Svelte 組織での公式メンテナンス」を目指す上での前提条件でもある。</p>
<p>互換性は宣言ではなく検証で担保する方針を取っている。コンパイラは、Svelte v5.56.4 公式テストスイートのうち rsvelte が対象とする 3,500 以上のフィクスチャで 100% 合格している(Svelte 4→5 マイグレーターや一部の個別フィクスチャは対象外)。加えて、実世界の約 30 リポジトリ・約 12,000 コンパイル単位のコードを公式コンパイラと rsvelte の両方でコンパイルして出力を比較し続けており、既知の構造的差分は client 8 件・server 0 件である(いずれも commit <code>76ac14b3</code> 時点。リンク先の README やダッシュボードは更新タイミングにより数値が異なる場合がある)。CI はこの既知差分リストを「増やすことを禁止する」ラチェットとして運用しており、差分ごとに原因を文書化している。</p>
<p>ただし、ドロップインへの到達度はパッケージごとに大きく異なる。フィクスチャ 100% は公開 API の完全互換を意味しない。CSS ハッシュ関数など関数値オプションの制約が残るほか、各パッケージの現状は 3.2 の表に示す通りで、正確に言えば現状は「完成したドロップイン代替」ではなく「ドロップイン代替を目標に、互換性を検証しながら近づけている複数の互換実装」である。</p>
<h3>3.2 コンポーネント</h3>
<p>rsvelte は静的検査基盤を構成する各ツールを個別のパッケージとして提供している。成熟度が大きく異なるため、内容と現状を併記する。</p>
<table>
<thead>
<tr>
<th>領域</th>
<th>パッケージ</th>
<th>内容</th>
<th>現状</th>
</tr>
</thead>
<tbody><tr>
<td>コンパイラ</td>
<td><code>@rsvelte/compiler</code></td>
<td>Svelte 5 コンパイラの Rust 移植</td>
<td>対象フィクスチャ 100% 合格。実コード出力の既知構造差 client 8 / server 0</td>
</tr>
<tr>
<td>ビルド</td>
<td><code>@rsvelte/vite-plugin-svelte</code></td>
<td>公式 vite-plugin-svelte の fork(API 同一)</td>
<td>experimental</td>
</tr>
<tr>
<td>型検査(変換)</td>
<td><code>@rsvelte/svelte2tsx</code></td>
<td>svelte2tsx の Rust 移植</td>
<td>出力の既知差分 0。ただし API は非同期(WASM 初期化のため)で上流と異なる</td>
</tr>
<tr>
<td>型検査(CLI)</td>
<td><code>@rsvelte/svelte-check</code></td>
<td>svelte-check の移植。検査エンジンとして tsc / tsgo を選択できる</td>
<td>early stage。一部 CLI フラグに差分。公式版の併用なしの CI ゲート利用はまだ非推奨</td>
</tr>
<tr>
<td>フォーマット</td>
<td><code>@rsvelte/fmt</code></td>
<td>prettier-plugin-svelte の移植。oxfmt と連携</td>
<td>実コードでの出力既知差分 40 件。設定は .oxfmtrc ベース(Prettier 設定は読まない)</td>
</tr>
<tr>
<td>Lint</td>
<td><code>@rsvelte/lint</code></td>
<td>eslint-plugin-svelte の 80 ルールを移植</td>
<td>現時点では ESLint の代替ではなく補完という位置づけ</td>
</tr>
<tr>
<td>エディタ</td>
<td><code>@rsvelte/language-server</code> / <code>rsvelte-vscode</code></td>
<td>Language Server と VS Code 拡張(別パッケージ)</td>
<td>フォーマットと Lint のみ。型検査・補完・定義ジャンプは対象外</td>
</tr>
</tbody></table>
<p>ツールを横並びで作っているのは目的ではなく帰結である。フォーマッター・型検査・Lint はいずれも Svelte パーサーに依存しているが、ネイティブツールから JavaScript 製の Svelte コンパイラを利用するには JS ランタイムやプロセス境界を越える必要があり、oxc の AST・semantic パイプラインへ直接統合することはできない。1 つの Rust パーサー(oxc ベース)を全ツールで共有することが、後述する oxc 統合の前提になる。</p>
<p>型検査については補足が要る。公式の svelte-check にも <code>--tsgo</code> オプションが既にあり、TS 検査エンジン自体は両者が共有できる。rsvelte の差分は svelte2tsx 変換とオーケストレーションの Rust 化であり、次のベンチマークでは TypeScript 実装を揃えた比較も行い、Svelte 側の実装差をできるだけ分離する。</p>
<h3>3.3 ベンチマーク</h3>
<p>この記事のために手元で実測した。数字を読む前に、測定の性質を 2 つに分けておく。<strong>エンジン処理</strong>(ファイルをメモリに読み込んだ状態での、変換・整形処理そのもののスループット)と、<strong>エンドツーエンド</strong>(CLI 起動からプロセス終了までの、ユーザーが体験する時間)である。前者の倍率は後者をそのまま意味しない。</p>
<p>主な条件: Apple M4 Pro(12 コア)/ 48GB、Node.js 24.13.1、rsvelte commit <code>76ac14b3</code>(ソースからビルド)、Svelte v5.56.4、svelte-check 4.7.3、typescript 5.9.3、tsgo 7.0.0-dev.20260707.2、eslint 10.7.0、eslint-plugin-svelte 3.21.0。いずれも同一マシン・同一コーパスで複数回実行の<strong>中央値</strong>を採る。計測スクリプトと生データは rsvelte リポジトリの <code>scripts/bench/</code> にある(エンジン処理の公開値とグラフは <a href="https://baseballyama.github.io/rsvelte/benchmark">benchmark ページ</a>)。</p>
<details>
<summary>計測手法の詳細</summary>
<ul>
<li>CPU 時間は <code>/usr/bin/time -l</code>(user + sys、待機した子プロセスを含む)。RSS は <code>ps</code> でプロセスツリーを 100ms 間隔で合算した概算値で、共有ページを重複計上し得る。チェッカー本体と TS エンジンの分離は PID の親子関係に基づく、プロセス構造ベースの概算である</li>
<li>flowbite の計測は、検査ツールがワークスペースに書き込むオーバーレイ成果物が次の実行に影響するため、実行ごとに APFS clonefile で新品のワークスペースを用意した、ツールキャッシュなし・非インクリメンタル実行(OS のページキャッシュ等は制御していない)</li>
<li>flowbite ワークロードは両実装とも約 900 件のエラーを報告する(js 885 / rs 926、ファイルとメッセージ単位の一致 669 件)。セットアップ(<code>pnpm install --ignore-scripts</code> + typescript / @typescript/native-preview の追加 + <code>svelte-kit sync</code>)がライブラリ開発用の完全な環境を再現していないためで、正常な CI 結果ではなく<strong>性能傾向を確認する参考値</strong>である</li>
<li>Flyle の計測は実行ごとに <code>svelte-kit sync</code> とオーバーレイ削除で状態をリセットし、1 回のウォームアップ後 5 回の中央値を採った</li>
<li>Lint の設定: ESLint 側は eslint-plugin-svelte(flat/recommended)+ TS パーサーを <code>src</code> の全 1,296 ファイルに適用し、svelte/* 以外のルールと未使用ディレクティブ報告は無効化。rsvelte-lint 側は ESLint の解決済み設定から svelte/* ルール 37 個を同一重大度でインポートした設定(<code>extends: ["none"]</code> ベース)で実行</li>
<li>37 ルールのうち <code>svelte/no-unused-props</code> だけは比較から除外した。ESLint 実装は TypeScript の型情報を必要とし、この環境では何も検出しない一方、rsvelte-lint は型情報なしで 11 件検出するため、両側の仕事量と出力を揃える目的で rsvelte 側で無効化した</li>
</ul>
</details><h4>3.3.1 型検査エンドツーエンド(Flyle 本番)</h4>
<p>まず、ユーザー(あるいはエージェント)が実際に体験するエンドツーエンドから見る。私が働いている Flyle の本番フロントエンド(SvelteKit アプリ。公式 svelte-check では 8,795 ファイルが検査対象になる)で計測した。効果は 2 段に分解できる。</p>
<pre><code>svelte-check + TypeScript LS   51.4s   ← 現行 CI 構成
      ↓ rsvelte 化(TypeScript は JS 版 5.9.3 のまま)
rsvelte-check + tsc            30.6s   (1.7 倍。CPU 104.9s → 45.9s、約 56% 減)
      ↓ 検査エンジンを tsgo 化
rsvelte-check + tsgo            9.0s   (合成で 5.7 倍。CPU 32.1s)
</code></pre>
<table>
<thead>
<tr>
<th>構成</th>
<th align="right">wall time(範囲)</th>
<th align="right">CPU 時間</th>
<th align="right">peak RSS 総計</th>
<th align="right">うちチェッカー本体</th>
</tr>
</thead>
<tbody><tr>
<td>svelte-check + TypeScript Language Service(現行 CI 構成)</td>
<td align="right">51.4s(46.9〜64.4)</td>
<td align="right">104.9s</td>
<td align="right">4.2GB</td>
<td align="right">分離不能(LS 一体型)</td>
</tr>
<tr>
<td>rsvelte-check + tsc</td>
<td align="right">30.6s(29.8〜36.2)</td>
<td align="right">45.9s</td>
<td align="right">3.8GB</td>
<td align="right">0.12GB</td>
</tr>
<tr>
<td>rsvelte-check + tsgo</td>
<td align="right">9.0s(8.6〜9.9)</td>
<td align="right">32.1s</td>
<td align="right">5.5GB</td>
<td align="right">0.12GB</td>
</tr>
</tbody></table>
<p>つまり <strong>rsvelte-check への置き換えだけで 1.7 倍</strong>(この置き換えには Svelte 側の Rust 化に加えて、Language Service から tsc CLI への実行経路の変更も含まれる)、<strong>tsgo 化を合わせて 5.7 倍</strong>である。検査結果は js / rs とも 0 errors、警告 44 / 41 件で、差の 3 件はワークスペース境界の 1 ファイルを公式版だけが検査対象に含めたことによる(rs 側は検査ファイル数を機械可読出力に出さないため、対象数の完全な突合はできていない)。メモリは、rsvelte-check 本体の peak がどちらのエンジン構成でも<strong>約 0.12GB</strong> で、rsvelte 構成ではメモリの大半を TS エンジンが占める(tsgo 構成の合計 5.5GB の大半は tsgo プロセスの消費。同じ JavaScript 版 TypeScript を使う構成同士なら合計も 4.2GB → 3.8GB に減る)。なお公式 svelte-check の <code>--tsgo</code> はこのリポジトリでは検査対象が 355 ファイルに縮み 1,581 件の誤エラーを報告したため、正しく動作しておらず比較から除外した(svelte-check 4.7.3 で確認)。</p>
<h4>3.3.2 tsgo を揃えた比較(flowbite-svelte)</h4>
<p>Flyle で測れない比較が 2 つある。tsgo 同士の比較(公式 <code>--tsgo</code> が誤動作するため)と、並列実行(独立したワークスペースの複製が N 個必要で、17GB の本番モノレポでは非現実的なため)である。この 2 つは、公式 <code>--tsgo</code> が動作する <a href="https://github.com/themesberg/flowbite-svelte">flowbite-svelte</a>(commit <code>85f20a0</code>、実 <code>.svelte</code> 1,296 ファイル)で行った。重要な制約を 1 つ明記しておく: このセットアップは flowbite の完全な開発環境を再現していないため、両実装ともモジュール解決起因の診断を約 900 件報告する。したがってこの数値は正しさの突合ではなく、<strong>性能傾向を確認する参考値</strong>である(詳細は上の「計測手法の詳細」)。tsgo を両方に使い、<strong>交互に実行する 10 ペア</strong>(ペアごとに実行順を入れ替え、毎回新品のワークスペースを使用)で測った結果は、js 中央値 3.96s(3.84〜4.16)、rs 中央値 2.12s(2.03〜2.36)、ペア内比率の中央値 <strong>1.9 倍</strong>(1.72〜1.96)だった。エンジン処理の倍率よりずっと小さいのは、エンドツーエンドでは両構成に共通する TypeScript 検査が wall time の大半を占めるためである。</p>
<h4>3.3.3 並列実行</h4>
<p>次に、この記事の主張に最も直接関わる測定として、<strong>同じマシンで N 個の検査プロセスを同時に走らせた</strong>(flowbite-svelte、tsgo 構成同士。svelte-check + Language Service 構成は並列時の実行間分散が大きく安定した値を得られなかったため除外)。表の値は「N 件すべてが完了するまでのバッチ完了時間 / 合計 CPU 時間 / 合計 peak RSS」である。</p>
<table>
<thead>
<tr>
<th>同時実行数</th>
<th>svelte-check + tsgo</th>
<th>rsvelte-check + tsgo</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>4.0s / 8.1s / 1.3GB</td>
<td>2.1s / 6.0s / 1.2GB</td>
</tr>
<tr>
<td>2</td>
<td>4.7s / 17.3s / 2.4GB</td>
<td>3.5s / 13.1s / 2.2GB</td>
</tr>
<tr>
<td>4</td>
<td>8.1s / 39.7s / 4.2GB</td>
<td>6.7s / 28.6s / 4.3GB</td>
</tr>
<tr>
<td>8</td>
<td>14.1s / 86.0s / 6.9GB</td>
<td>13.5s / 67.8s / 6.7GB</td>
</tr>
</tbody></table>
<p>N=1 で 1.9 倍あった差は並列度を上げるほど縮み、8 件を同時起動した条件ではほぼ消える(14.1s vs 13.5s)。この結果は、<strong>同時起動により検査が重なり続ける高負荷条件では、両構成に共通する tsgo 処理がボトルネックになる</strong>ことを示唆する(それでも合計 CPU 時間を 8 件で割った 1 検査あたりの CPU 消費は 10.8 → 8.5 CPU 秒で約 21% 少なく、合計 RSS は同水準。rayon スレッド数を 12/N に制限する構成も測ったが有意差はなかった)。一方、実運用では検査の開始時刻はずれるため、単独実行時間の短縮が重複区間そのものを減らすと考えている。ただしこれは今回の同時起動ベンチマークでは直接測っておらず、実際のエージェント実行ログでの検証は今後の課題である。</p>
<h4>3.3.4 Svelte 固有 Lint</h4>
<p>Lint も同じ交互実行の方法で測った(設定は「計測手法の詳細」参照)。これは ESLint 構成全体の置き換えではなく、<strong>flat/recommended の Svelte 固有ルールを同一重大度で揃え、それ以外のルールは両側とも無効化した比較</strong>である。1 つだけ例外がある。<code>svelte/no-unused-props</code> は ESLint 実装が型情報を必要としこの環境では何も検出しないが、rsvelte-lint は型情報なしで 11 件検出する。両側の仕事量と出力を揃えるため、このルールは rsvelte 側で無効化して比較から外した(詳細は「計測手法の詳細」)。その結果、診断は両実装とも <strong>382 件で完全に一致</strong>した。</p>
<table>
<thead>
<tr>
<th></th>
<th align="right">wall time</th>
<th align="right">CPU 時間</th>
<th align="right">peak RSS</th>
</tr>
</thead>
<tbody><tr>
<td>ESLint + eslint-plugin-svelte</td>
<td align="right">5.02s</td>
<td align="right">8.4s</td>
<td align="right">0.86GB</td>
</tr>
<tr>
<td>rsvelte-lint</td>
<td align="right">0.25s</td>
<td align="right">1.7s</td>
<td align="right">0.05GB</td>
</tr>
</tbody></table>
<p>交互実行 10 ペアの比率中央値は <strong>19.4 倍</strong>(範囲 18.0〜23.5)だった。型検査と違って、この比較にはエンドツーエンドの時間を支配する大きな共通 TypeScript フェーズがないため、2 桁の差がそのまま残る。メモリも 0.86GB → 0.05GB と大きく減る。</p>
<h4>3.3.5 エンジン単体の性能</h4>
<p>最後に、なぜここまで速くなるのかの裏付けとして、エンジン単体のスループットを示す(Svelte 公式テストスイート由来の実 <code>.svelte</code> 3,857 件、事前にメモリへ読み込み済み。3 回のウォームアップ後 10 回実行の中央値):</p>
<table>
<thead>
<tr>
<th>処理</th>
<th align="right">JS</th>
<th align="right">rsvelte(1 スレッド)</th>
<th align="right">rsvelte(マルチ)</th>
<th align="right">倍率(マルチ)</th>
</tr>
</thead>
<tbody><tr>
<td>パース</td>
<td align="right">149.3ms</td>
<td align="right">9.0ms</td>
<td align="right">1.9ms</td>
<td align="right">79.1×</td>
</tr>
<tr>
<td>コンパイル(client)</td>
<td align="right">625.9ms</td>
<td align="right">202.8ms</td>
<td align="right">32.9ms</td>
<td align="right">19.0×</td>
</tr>
<tr>
<td>コンパイル(SSR)</td>
<td align="right">510.4ms</td>
<td align="right">114.1ms</td>
<td align="right">16.9ms</td>
<td align="right">30.2×</td>
</tr>
<tr>
<td>svelte2tsx</td>
<td align="right">231.6ms</td>
<td align="right">91.2ms</td>
<td align="right">11.4ms</td>
<td align="right">20.4×</td>
</tr>
<tr>
<td>フォーマット</td>
<td align="right">2891.6ms</td>
<td align="right">117.0ms</td>
<td align="right">23.7ms</td>
<td align="right">122.2×</td>
</tr>
<tr>
<td>svelte-check(ツール自身の処理)</td>
<td align="right">875.2ms</td>
<td align="right">41.6ms</td>
<td align="right">15.6ms</td>
<td align="right">56.2×</td>
</tr>
</tbody></table>
<p>この表の注意点を 3 つ。第一に、svelte-check の行は、両実装とも TypeScript 検査と TSX オーバーレイ生成を無効化し、ファイル走査と Svelte 側のパース・解析・診断生成のみを比較している。第二に、この表は API 呼び出しの計測であり、CLI 起動・ファイル探索・設定解決は含まない。つまり「フォーマットエンジンのスループットが 12 コアで約 120 倍」であって、「format コマンドの体験が 120 倍速くなる」ではない。第三に、コーパスは意図的にコンパイルエラーになるフィクスチャも含んでおり(スクリプトは JS 側の例外を捕捉して無視する)、両実装の成功・失敗件数が完全に一致することまでは検証していない。</p>
<p>ここから 1.3 で挙げた 2 つのコストへの回答が得られる。<strong>レイテンシ</strong>: 本番リポジトリの型検査 1 回は rsvelte 化だけで 1.7 倍、tsgo 化を合わせて 5.7 倍(51.4s → 9.0s)縮み、Svelte 固有 Lint は約 20 倍縮む。<strong>リソース</strong>: 型検査の CPU 時間は rsvelte 化だけで約 56% 減(104.9s → 45.9s)、tsgo 化を合わせて 104.9s → 32.1s になる。rsvelte-check 本体のメモリは約 0.12GB(Lint は 0.05GB)と小さく、rsvelte 構成ではメモリの大半を TypeScript エンジンが占める。tsgo を選ぶとエンジン分のメモリは増える。</p>
<h2>4. 今日から試す</h2>
<p>rsvelte は現在 1.0 前の early stage であり、API は予告なく変わる可能性がある。既知の制約と各パッケージの成熟度は 3.2 の表に示した通りである。その前提で、各ツールは個別に導入できる。導入手順は <a href="https://github.com/baseballyama/rsvelte#readme">rsvelte の README</a> を参照してほしい。</p>
<p>ドロップイン代替を目標としているため、既存ツールと併走させて出力を比較する、という試し方ができる。まずはソースを書き換えない型検査を公式版と併走させるのがおすすめだ。フォーマッターには既知の出力差があるため、<code>--check</code> での比較や隔離ブランチから始めると安全である。</p>
<h2>5. 今後の方向性</h2>
<p><strong>oxc とのネイティブ統合。</strong> oxc 側には外部の言語プラグインを受け入れる実装計画(<a href="https://github.com/oxc-project/oxc/discussions/21936">oxc-project/oxc#21936</a>)があり、Svelte も対象に含まれている。ただし実装はまだ初期段階で、統合時期は未定である。これが実現すれば、oxlint や oxfmt が <code>.svelte</code> ファイルを(prettier へのフォールバックではなく)ネイティブに処理できるようになり、ユーザーは rsvelte を意識することなく oxc ツールチェーンをそのまま使えるようになる。</p>
<p><strong>Svelte 組織での公式メンテナンス。</strong> これはまだ何も決まっていない、私が長期的に目指している方向性である。ツールチェーンにはコミュニティの信頼と継続性が不可欠であり、個人リポジトリのままでは限界がある。互換性を最優先する設計原則は、将来 Svelte 組織でのメンテナンスを提案できる水準に近づくための土台だと考えている。</p>
<h2>まとめ</h2>
<ul>
<li>AI エージェントが実装の主体になり、静的検査は「ループ毎に走るもの」になった。検査の所要時間は 1 ループの所要時間の下限であり、検査のリソース消費は並列度を制約する</li>
<li>Rust / ネイティブベースのツールチェーンはこれを解決しつつあるが、<code>.svelte</code> の Svelte 固有処理には JavaScript 実装が残っている</li>
<li>rsvelte は Svelte ツールチェーンのドロップイン代替を目標とする Rust 実装群である。言語仕様への独自拡張は加えず、切り替えも撤退も低コストであることを最優先する。到達度はパッケージごとに異なる</li>
<li>Flyle 本番の型検査は rsvelte 化だけで 1.7 倍(51.4s → 30.6s、CPU 約 56% 減)、tsgo 化を合わせて 5.7 倍(9.0s)。ルールセットを揃えた Svelte 固有 Lint は約 20 倍。rsvelte-check 本体のメモリは約 0.12GB で、メモリの大半は TS エンジンが占める。公式ツールと併走して診断差分を確認しながら試せる</li>
</ul>
<p>rsvelte はまだ発展途上のプロジェクトである。試して問題を見つけたら、<a href="https://github.com/baseballyama/rsvelte">GitHub</a> で issue やフィードバックをもらえると嬉しい。</p>
<hr>
<p>私が VPoT を務める <a href="https://herp.careers/v1/flyle">株式会社フライル</a> では、現在ソフトウェアエンジニアを募集しています。
この記事の内容に共感いただけた方、AI 時代の開発基盤づくりに興味のある方、また事業内容に興味のある方は、ぜひカジュアル面談でお話ししましょう。</p>
]]></content:encoded>
		</item>
		<item>
			<title>抽象化・共通化にどう立ち向かうか</title>
			<link>https://blog.baseballyama.com/ja/posts/20260211-abstraction-and-generalization</link>
			<guid>https://blog.baseballyama.com/ja/posts/20260211-abstraction-and-generalization</guid>
			<pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>ソフトウェア設計に関わったことのある人であれば、「データ設計」が設計における重要な要素の一つであることに異論はないだろう。   なお本記事で言う「データ設計」は、DBのテーブル設計に限らない。ビジネス上の概念をどう整理し、境界を引くか（ド</description>
			<content:encoded><![CDATA[<p>ソフトウェア設計に関わったことのある人であれば、「データ設計」が設計における重要な要素の一つであることに異論はないだろう。<br>なお本記事で言う「データ設計」は、DBのテーブル設計に限らない。ビジネス上の概念をどう整理し、境界を引くか（ドメインモデリング）も含めた意味で用いる。</p>
<p>この記事で一番伝えたいことはシンプルだ。</p>
<p><strong>共通化はコードのテクニックではなく、データ設計（＝どの概念を同一視し、どこで分離するか）の判断である。</strong><br>そして、<strong>抽象化は本質的に難しく、時間がかかる</strong>。だからこそ「急いで一つにまとめる」以外の選択肢を持とう、という話をする。</p>
<h2>tl;dr</h2>
<ul>
<li>共通化はコードのテクニックではなく、データ設計（どの概念を同一視するか）の判断である  </li>
<li>コードの「見た目の重複」だけで共通化すると、将来の変更で破綻しやすい  </li>
<li>抽象化は本質的に難しく、前提の変化によって正解が変わることもある  </li>
<li>抽象化とは「何が不変で、何が可変か」を見極める行為である  </li>
<li>迷ったら重複を許容する（Avoid Hasty Abstraction / Three Strikes Rule）  </li>
<li>迷ったら、一旦立ち止まり「本当に同じ概念か？」を問い直す</li>
</ul>
<h2>1. コードの「見た目」に惑わされない</h2>
<p>まず最も重要であり、かつ陥りやすい罠は、「記述としてのコード」そのものに着目してしまうことである。</p>
<p>コードとは、ドメイン分析やデータ設計を通じて整理された「概念」を、ソフトウェアという言語で書き下した最終的な出力に過ぎない。したがって、コード上でどれほど似た記述を発見したとしても、それが直ちに共通化の対象になるとは限らない。</p>
<p>例えば、サメとイルカを想像してほしい。どちらも「水中で高速移動する」という生存戦略をとっているため、その振る舞いをコード化すれば、一見すると非常によく似たロジックが並ぶだろう。しかし、これらを「同じもの」として共通化してしまうと、設計はすぐに破綻する可能性が高い。なぜなら、両者の意味論（セマンティクス）が根本から異なるからである。</p>
<p>サメは魚類であり、イルカは哺乳類である。例えば「呼吸」という処理を実装する場合、サメにはエラ呼吸のロジックが必要だが、イルカには水面での肺呼吸という全く別のロジックが求められる。もし無理に共通化していれば、共通化の中心（ベースクラスや共通関数）の中は呼吸法を切り分けるための if 分岐であふれ、変更のたびに破綻しやすくなる。</p>
<p>ここで強調しておきたいのは次の点だ。</p>
<blockquote>
<p>「コードが重複している」という状態は、設計の不備ではなく、<br>「まだ共通化すべき概念が十分に成熟していない」というシグナルに過ぎない場合がある。</p>
</blockquote>
<p>つまり、「現状のコードの見た目」だけに囚われると、その背後にある「概念の由来」や「変化の理由」を踏まえた思考ができなくなる。抽象化・共通化に立ち向かうときは、コードという現象の背後にある、構造化された「意味」に着目すべきである。</p>
<h2>2. データ設計は「どこを共通化し、どこを分離するか」を決める</h2>
<p>本記事の冒頭で、「データ設計」とは現実世界の概念をシステム上で扱える形に再構成する作業であると書いた。これは一般に「データモデリング」と呼ばれる。</p>
<p>データモデリングとは、どのようなエンティティが存在し、それぞれがどのような属性を持つかを整理し、さらにエンティティ間の関係性を定義する作業である。言い換えると、<strong>似ているものを同一視する（共通化する）のか、別物として分離するのか</strong>を決める作業でもある。</p>
<p>同じ「サメ」と「イルカ」を扱っていても、作るシステム次第で妥当な設計は変わる。ここでは2つの例を用いて考えてみる。</p>
<h3>A: 研究所向け生物シミュレーションシステム</h3>
<p>（私は関わったことはないが）生物シミュレーションシステムにおいては、生物分類に基づいてデータ設計を行うことは有力な選択肢の一つになるだろう。その場合、データモデルは次のようになるかもしれない。</p>
<pre class="mermaid">classDiagram
    class 動物 {
        +名前: string
    }
    class 魚類 {
        +呼吸方法: string = "エラ呼吸"
    }
    class 哺乳類 {
        +呼吸方法: string = "肺呼吸"
    }
    class サメ {
        +移動方法: string = "泳ぐ"
    }
    class イルカ {
        +移動方法: string = "泳ぐ"
    }
    動物 <|-- 魚類
    動物 <|-- 哺乳類
    魚類 <|-- サメ
    哺乳類 <|-- イルカ</pre>
<h3>B: 水中生物レースゲーム</h3>
<p>一方、水中生物レースゲームであれば、「どの速度で泳ぐか」を扱えれば十分かもしれない。その場合、データモデルは次のようになるだろう。</p>
<pre class="mermaid">classDiagram
    class 水中生物 {
        +名前: string
        +速度: int
        +スタミナ: int
    }
    class サメ {
        +名前: string = "サメ"
        +速度: int = 80
        +スタミナ: int = 70
    }
    class イルカ {
        +名前: string = "イルカ"
        +速度: int = 75
        +スタミナ: int = 85
    }
    水中生物 <|-- サメ
    水中生物 <|-- イルカ</pre>
<p>このように、同じサメとイルカを扱っていても、どのようなシステムを作るかによってデータ設計の結果は変わる。そして、どこを共通化するかも変わる。</p>
<p>Aでは生物分類単位で共通部分を切り出すのが妥当になりやすい。一方Bでは「水中生物」という単位で共通化するのが自然である。</p>
<h2>3. 抽象化は難しい（そして壊れうる）ことを前提にする</h2>
<p>抽象化は、いつでも簡単に正解が得られる類のものではない。少し視野を広げると、我々が当たり前に使っている概念ですら成立までに長い時間がかかっている。</p>
<p>例えば、「数える」という行為は古くから存在したはずだが、「数」という概念を一般化し体系化するには長い時間が必要だった。また負の数のように、直感に反する概念が広く受け入れられるまでにも時間がかかったと言われている。(参考: <a href="https://ja.wikipedia.org/wiki/%E6%95%B0%E5%AD%A6%E3%81%AE%E5%B9%B4%E8%A1%A8">数学の年表</a>)</p>
<p>ここから得られる教訓はこうだ。</p>
<ul>
<li>抽象化は本質的に難しい</li>
<li>「真に共通化できるもの」が見えるまでには時間がかかることがある</li>
<li>その間にコードの重複が発生するのは、ある程度は自然である</li>
</ul>
<p>そしてソフトウェアでは、さらに難しくなる要因がある。それが「前提が変わる」ことだ。</p>
<h3>3.1 変化する前提が抽象化を壊す</h3>
<p>例えば、商品の販売価格を管理するシステムがあり、税率が一律である前提で設計すると次のような形になるかもしれない。</p>
<pre class="mermaid">classDiagram
    class 商品 {
        +名前: string
        +税抜価格: decimal
        +販売価格(): decimal
    }</pre>
<p>この時点では、<code>販売価格()</code> を「税抜価格 × 1.10」のように共通実装したくなる。</p>
<p>しかし軽減税率の導入のように、将来「商品種別ごとに税率が異なる」前提に変わると、単純な共通実装は破綻する。</p>
<pre class="mermaid">classDiagram
    class 商品 {
        +名前: string
        +税抜価格: decimal
        +税区分: 税区分
        +販売価格(): decimal
    }

    class 税区分 {
        +税率: decimal
    }</pre>
<p>ここで重要なのは、「食品は1.08、家電は1.10」のように <em>クラスに税率を焼き付ける</em> ことではなく、<strong>税率が“変わりうるルール”である</strong>と捉えて設計上の居場所を分離することだ（期間や国によって変動する可能性もある）。</p>
<p>さらに踏み込むなら、次の視点が抽象化の核になる。</p>
<ul>
<li>税率という「数値」は可変である</li>
<li>税額を計算するという「ルール（計算が存在すること）」は不変に近い</li>
</ul>
<p>抽象化とは、この「不変」と「可変」の境界線を見極める作業である。<br>そして将来の前提変更は、その境界線をずらし得る。これが抽象化を難しくする最大の理由だと私は考えている。</p>
<h2>4. どうやって抽象化に取り組むか</h2>
<p>とはいえ、抽象化を諦めれば、コードは重複だらけのまま増え続け、最終的にはメンテナンスが難しくなる。よって我々は現実的な範囲で抽象化に取り組む必要がある。</p>
<p>私が有効だと考えている方法は、以下の2つである。</p>
<ul>
<li>製品のコンセプトを明確にし、将来の発展の可能性を幅広く想定する</li>
<li>類似事例を徹底的に調査する</li>
</ul>
<h3>4.1 製品のコンセプトを明確にし、将来の発展の可能性を幅広く想定する</h3>
<p>抽象化を検討する際、最も重要なのは、製品のコンセプトである。</p>
<p>例えば、「全ての商品の税額を管理して全社の業務を効率化するシステム」がコンセプトなのであれば、</p>
<ul>
<li>全社で扱う商品に耐える汎用性</li>
<li>年単位の運用を前提にした変更耐性（税法改正など）</li>
</ul>
<p>が求められやすい。</p>
<p>税法がどう改正されるかを完全に予測することはできないが、「どんなパターンがあり得るか」は調査できる。各国・各地域の税制や過去の改正の傾向を見て、起こり得る変更の幅を把握した上で、「どの範囲までを要件として設計に含めるか」を決める必要がある。</p>
<p>一方で「とりあえず直近の自チームが扱う商品の税額を管理するシステム」がコンセプトであれば、</p>
<ul>
<li>対象商品の範囲を限定した設計</li>
<li>直近の運用上の前提（例: 税率10%固定）に寄せた割り切り</li>
</ul>
<p>も合理的になり得る。</p>
<p>このように、製品のコンセプトによって、データ設計は大きく変わり、その結果として抽象化の方向性も変わってくる。</p>
<h3>4.2 類似事例を徹底的に調査する</h3>
<p>抽象化は難しい。だからこそ、自分一人の経験や知見だけでベストに近い答えを導ける確率は高くない、と捉えるのが現実的だ。</p>
<p>そこで、類似のソフトウェアがどういうデータ設計を採用したのか、その理由を調査することをおすすめする。</p>
<ul>
<li>既存プロダクトの設計思想</li>
<li>公開されている設計資料や事例</li>
<li>OSSのモデル設計や議論（Issue/PR など）</li>
</ul>
<p>こうした情報から「複数人が悩んだ過程」を取り込みつつ、自分のコンテキストに合わせて最終判断できる。結果として、1人で考えるより妥当な答えに辿り着ける可能性が上がると私は考えている。</p>
<h2>5. 共通化できそうなコードを見つけたときにどうするべきか</h2>
<p>実装者の立場からすると、明らかに同じコードを見つけると共通化したくなる気持ちは理解できる。こういうときは、以下を検討するのがおすすめだ。</p>
<ul>
<li>データ設計の観点から、この2つのコードが本質的に同じ概念なのかを自問する（レビューや第三者視点、議論、LLMとの壁打ちなどでもよい）</li>
<li>一旦共通化する場合でも、それが間違っていたときにすぐ解体できるように設計する（呼び出し側の依存を薄く保つ、境界を作るなど）</li>
</ul>
<p>加えて、意思決定の「目安」を持っておくと迷いにくい。</p>
<ul>
<li><strong>迷ったら重複を許容する（Avoid Hasty Abstraction）</strong></li>
<li><strong>同じものが3箇所に現れたら、そこで初めて共通化を検討する（Three Strikes Rule）</strong></li>
</ul>
<p>共通化は「短く書ける」ことが価値ではない。<br>共通化によって生まれる結合のコストが、将来どこまで効いてくるかを考える必要がある。</p>
<p>この記事の主張は「共通コードを見つけたら絶対に共通化するな」ではない。<br>「共通化は“コード上の見た目”ではなく“概念の同一性”で判断しよう」というだけだ。</p>
<p>また、技術負債の大きな一因はデータ設計（ドメイン設計）の不備にあると私は考えている。だからこそ、&quot;共通コード&quot; を見つけたときにデータ設計に立ち戻る習慣があれば、負債の蓄積を緩和できる可能性がある。</p>
<h2>まとめ</h2>
<ul>
<li>共通化はコードのテクニックではなく、データ設計（概念の同一視と分離）の判断である</li>
<li>抽象化は難しく、前提変更によって将来「壊れる」ことがある</li>
<li>抽象化の核は「不変」と「可変」の境界線を見極めることにある</li>
<li>迷ったら重複を許容し、3箇所に現れたら共通化を検討するくらいがちょうどよい</li>
</ul>
<p>共通コードを見つけたからといってすぐに共通関数を実装するのではなく、データ設計に立ち戻って考える、という選択肢もある。この記事がそのきっかけになれば嬉しい。</p>
<hr>
<p>私が VPoT を務める <a href="https://herp.careers/v1/flyle">株式会社フライル</a> では、現在ソフトウェアエンジニアを募集しています。<br>この記事の内容に共感いただけた方、ここで述べた考え方を実務にどう取り入れているのか気になる方、また事業内容に興味のある方は、ぜひカジュアル面談でお話ししましょう。</p>
]]></content:encoded>
		</item>
		<item>
			<title>テックリードに求められる素養</title>
			<link>https://blog.baseballyama.com/ja/posts/20260121-tech-lead</link>
			<guid>https://blog.baseballyama.com/ja/posts/20260121-tech-lead</guid>
			<pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate>
			<author>baseballyama</author>
			<description>私が働いている [Flyle](https://flyle.io/jp) では、長い間テックリードとして技術方針の意思決定に関わってきた。  今後（あるいは近い将来）、役割を分担したり、次の担い手を育てたりする場面も増えるはずだ。そこで今</description>
			<content:encoded><![CDATA[<p>私が働いている <a href="https://flyle.io/jp">Flyle</a> では、長い間テックリードとして技術方針の意思決定に関わってきた。</p>
<p>今後（あるいは近い将来）、役割を分担したり、次の担い手を育てたりする場面も増えるはずだ。そこで今日は、私が考える「テックリードに必要な素養」を整理しておく。</p>
<h2>1. 事業戦略から逆算する</h2>
<p>営利企業における製品開発は、「作ること」自体が目的ではなく、事業成長を実現するための手段である。したがって技術選定も、技術的な好みや流行ではなく、事業戦略と整合した意思決定でなければならない。</p>
<p>そのためには、SWOT分析などを用いて自社の強み・弱み、機会・脅威を整理し、それを技術選定の判断基準に落とし込む力が求められる。</p>
<p>例えば Flyle では、新製品の競争優位性を「大量データを素早く分析できること」と定めた。ここで重要なのは、強みを抽象的なスローガンで終わらせず、設計や投資判断に使える形で定義することだ。</p>
<p>具体的には、大量データを「1テナントあたり X 万件」、素早い分析を「概ね X 秒以内に集計処理が完了すること」と定義した（具体的な数字は非公開）。これにより技術選定は、「なんとなく速い」ではなく、達成すべき性能要件を満たすかどうかで評価できる状態になった。</p>
<p>また当時の私たちは、スタートアップでありながら成長期にあり、導入初期に一定のコストがかかる施策でも、中長期で強みを強化できる基盤に投資できる財務・組織状況にあった。つまり「理想論としての性能」だけでなく、「今の会社の体力で実現可能か」という現実的な観点も含めて判断できるフェーズだった。</p>
<p>この前提で複数の OLAP を比較した結果、まず Snowflake / BigQuery はコスト面で見送った。私たちのユースケースでは分析クエリが複雑になりやすく、検証の結果、1クエリあたりの実行コストが想定以上に高額になり得ることが分かったためだ。プロダクトとして提供する以上、利用が伸びるほどクエリ実行回数も増える。結果として、当時の状況では高価になりすぎる可能性が高いと判断した。</p>
<p>次に Redshift は性能面で見送った。求める性能要件（1テナントあたり X 万件規模のデータを、概ね X 秒以内に集計）を満たすことが難しく、製品の強みを担保できないためである。</p>
<p>一連の比較の結果、少なくとも当時の前提では、性能要件を現実的に満たし得る選択肢として ClickHouse が最も有力だった。ただし私たちはエンタープライズ顧客が多く、高いセキュリティ要件を満たす必要がある。そのため ClickHouse Cloud は採用できず、セルフホストを前提とした。</p>
<p>セルフホスト運用では、可用性・冗長性を考慮すると最低でも複数台の コンピューティングリソースが必要になり、インフラコストは決して小さくない。加えて ClickHouse はチューニング項目が多く、運用負荷も上がる。つまりこの選定は、「高性能」というメリットと引き換えに、コストと運用難易度を明確に背負う意思決定である。</p>
<p>それでも ClickHouse を採用したのは、定義した性能要件を満たすことが、新製品の競争優位性そのものだったからだ。単に「流行っているから」「速いから」ではなく、「事業の競争優位性を担保するために、このコストは正当化されるか」を説明責任を持って語れること――それこそが、技術選定における第一の素養だと考えている。</p>
<h2>2. 圧倒的当事者意識</h2>
<p>テックリードの役割は、技術的な意思決定を下すことだけではない。その決断がもたらす「結果」に対して、最後まで責任を持つことにある。</p>
<p>例えば、事業戦略上、ある機能の提供日を顧客と約束したのであれば、テックリードはその約束を守るために、チームを完遂へ導かなければならない。また、データ欠損のような事業の根幹を揺るがす重大なバグが発生した際には、事態が収束するまで向き合い続ける必要がある。</p>
<p>これは、必ずしも自らが作業の最前線に立ち続けることを意味しない。むしろ、チームのリソース再配分、仕様の削減交渉、優先順位の見直し、あるいは泥臭い暫定対応を含め、あらゆる手段を講じてでも「問題を解決する」というゴールにチームを導くことが重要だ。</p>
<h2>3. 技術的対話能力</h2>
<p>テックリードは、技術的な羅針盤としてチームを導く役割を担う。ただし、それは「すべての技術領域において最も詳しい人」であることを意味しない。むしろ、現代の複雑化した技術スタックを前提にすると、一人で全領域をプロダクションレベルでカバーするのは現実的ではない。</p>
<p>そこで重要になるのが、自分より詳しいメンバーの知見を引き出し、最適な意思決定につなげる力だ。</p>
<p>例えば私の場合、アプリケーション領域（DB、BE、FE）には知見がある一方で、インフラ領域の構築経験は浅い。だからといって、インフラ設計が必要な場面で丸投げはしない。まずは自分でキャッチアップし、「仮説」と「設計案」を持った状態で、インフラに強いメンバーと議論する。問いを立て、議論の中で学びながら解像度を上げていくスタイルを取っている。</p>
<p>また、ある機能開発で「テンポラルデータモデル」を採用した事例もある。私は概念としては理解していたものの、実務での利用経験がなく、当初の選択肢には入れていなかった。しかし、経験豊富なメンバーから提案を受け、今回の要件にはこれが最も適していると判断できた。こうした意思決定は、個人の知識だけではたどり着きにくい。</p>
<p>テックリードに求められる対話能力は、単なる雑談ではない。未知の領域に対しても事前にインプットし、仮説を持って議論に臨むこと。そして、メンバーの強みを引き出しながら、チームとして納得感のある技術的判断へ落とし込むこと。そうした積み重ねが、テックリードとしての価値につながると考えている。</p>
<h2>4. 突き抜けた強み</h2>
<p>とはいえ、「テックリード」という肩書きは、技術的なリーダーであることを前提としている。だからこそ、技術的な強みがない状態でその役割を担うのは、周囲から見ても違和感が出やすい。</p>
<p>そのため、何か一つでいいので、「この領域なら任せられる」と思ってもらえる強みを作っておくことをおすすめしたい。強みは幅広さではなく、深さがあるほど効いてくる。</p>
<p>私の場合、UI フレームワークである Svelte のコアチームメンバーとして活動しており、コンパイラやパーサー、静的検査といった、いわゆる言語処理系の領域に比較的深い知見がある。加えて、新卒で入社した富士通では、300人規模のプロジェクトで長く開発標準化を担当していた。</p>
<p>この2つのスキルを掛け合わせることで、開発の足回りを整えることに強みがあると感じている。例えばコードレビューで繰り返し指摘が発生する観点については、静的検査ルールを追加（必要に応じて自作）し、同じ指摘が再発しない仕組みに落とし込むことで、レビュー工数を圧縮している。</p>
<p>また、フロントエンドにおけるロジック部のテストに課題を感じた際には、Svelte コンポーネントからロジック部分だけを抽出する独自コンパイラを実装し、ロジックに焦点を当てたテストを実装できる基盤を用意した。</p>
<p>こうした取り組みは、開発標準化の経験によって「チームの生産性を下げるボトルネック」を見つけやすくなっていたこと、そして言語処理系の知見があったからこそ、効果的な施策として形にできたのだと捉えている。</p>
<p>強みは、必ずしも華やかな実績である必要はない。特定の領域で継続的に価値を出し、それをチームに還元し続けることが、テックリードとしての説得力と信頼につながっていく。</p>
<h2>5. 権限設計（テックリードを任命する側へ）</h2>
<p>ここまで述べた素養は、テックリード本人の努力で伸ばせる部分が大きい。一方で、見落とされがちだが決定的に重要なのが「テックリードにどこまでの権限を与えるか」である。</p>
<p>テックリードは、技術的な方針を決める役割だ。しかし、責任だけを背負わせて意思決定権を渡さない状態では、チームは機能しない。</p>
<p>例えば「性能要件を満たすために基盤投資が必要」と判断しても、予算の裁量がなく、意思決定が毎回承認待ちになるなら、テックリードは方針を出せない。
「この日までに出す」と顧客に約束していても、スコープ調整や優先順位の変更を動かせないなら、当事者意識があっても手が打てない。</p>
<p>この状態は、本人の力量不足ではなく、組織設計の問題である。</p>
<p>だからこそ、テックリードを任命する側は、任命と同時に次の問いに答えなければならない。</p>
<ul>
<li>技術選定の最終決定権は誰が持つのか</li>
<li>品質・納期・コストが衝突したとき、誰が優先順位を決めるのか</li>
<li>スコープ削減やリリース延期の判断は誰ができるのか</li>
<li>技術的負債返済のための時間を、誰が確保できるのか</li>
</ul>
<p>ここが曖昧なままだと、テックリードは「判断する役割」ではなく「説明責任だけを負う役割」になってしまう。結果として意思決定が遅れ、摩擦が増え、疲弊し、最終的には組織の速度が落ちる。</p>
<p>逆に言えば、権限設計が明確であれば、テックリードは技術的な羅針盤として機能し、チームは安心して前に進める。</p>
<p>テックリードを任命することは、意思決定が進む構造を作ることでもある。</p>
<h2>まとめ</h2>
<p>この記事で書いたテックリードに求められる素養は、あくまで2026年1月時点で私が考えるものであり、今後もアップデートされていくはずだ。</p>
<p>また、そもそも「テックリード」に求められる職務範囲は厳格に定義されているものではないため、違和感を感じた人もいるかもしれない。</p>
<p>あくまで個人的な捉え方として読んでもらえたら嬉しい。</p>
]]></content:encoded>
		</item>
	</channel>
</rss>
