📚
TS特化Clineプログラミング(テキスト版)
tskaigi で発表した https://tskaigi.mizchi.workers.dev/ のコピペしやすい用にしたバージョンです。
ほぼ marp のソースコードそのままですが、プロンプトのコピペ用にそのまま公開します。
本資料の内容
- うまくいくプロンプト
- うまくいかないプロンプト、その理由
- 現状認識
注意: 前日リリースのClaude 4 の評価は間に合ってません!!!!
- Claude 4 Opus の高すぎる怖い
- 数時間触った感じ: 改善傾向だが、抱えてる問題も同じ傾向
主張: 言語特化プロンプトが必要(今は)
- Coding Agent は言語ごとのユースケースに最適化されていない
- ベストプラクティスをユーザーが取捨選定する必要
- TS 周辺は技術選定で発散しがち
-
プログラミング言語間の転移学習は不安定
- GitHub を丸暗記しても、コンテキストに応じて翻訳&参照できるかは別の話
- ツールの組み合わせで性能が変わる
先に結論: プロンプトを書くコツ
- 書きすぎない (再現性ある範囲で詠唱破棄)
- 執拗に出力例を例示
- 両立条件の矛盾を避ける
- 規模感に合わせて厳しく
効くプロンプト: テスト駆動開発 (最重要)
TDD を実施する。コードを生成するときは、それに対応するユニットテストを常に生成する。
コードを追加で修正したとき、`npm test` がパスすることを常に確認する。
```ts
function add(a: number, b: number) { return a + b }
test("1+2=3", () => {
expect(add(1, 2)).toBe(3);
});
```
- 自己修復能力
- 高品質なテストがあればエージェントを放置しても完成する
効くプロンプト: コメントによる自己記述
各ファイルの冒頭にはコメントで仕様を記述する。
出力例
```ts
/**
* 2点間のユークリッド距離を計算する
**/
type Point = { x: number; y: number; };
export function distance(a: Point, b: Point): number {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
```
- 再修正時のコード解釈で一貫性を保てる
効くプロンプト: In Source Testing
vitest で実装と同じファイルにユニットテストを書く。
出力例
```ts
export function distance(a: Point, b: Point): number {...}
if (import.meta.vitest) {
const {test, expect} = import.meta.vitest;
test("ユークリッド距離を計算する", () => {
const result = distance({x: 0, y: 0}, {x: 3, y: 4});
expect(distance(result)).toBe(5)
});
}
```
- コメント/実装/テストは三位一体
- 欠点: ファイルが肥大化しやすい(800~1000行あたりが限界)
効くプロンプト: types.ts にドメイン型を集約
src/types.ts にアプリケーション内のドメインモデルを集約する。
その型がどのように使われるかを jsdoc スタイルのコメントで記述
```ts
/**
* キャッシュのインターフェース抽象
*/
export type AsyncCache<T> = {
get(): Promise<T | void>;
has(): Promise<boolean>;
set(value: T): Promise<void>;
}
```
- 中規模(1000L~): 複数ファイル間の SSoT を型定義とする
- read_file 頻度が下がる。書き換え頻度が(実装と比較して)比較的少ない
効くプロンプト: TS + 関数型ドメインモデリング
TypeScript で関数型ドメインモデリングを行う。class を使わず関数による実装を優先する。
代数的データでドメインをモデリングする。
出力例
````ts
type FetchResult<T, E> = {
ok: true;
data: T
} | {
ok: false;
error: E
}
```
- (好みの問題はあるが) 状態の発散を抑えるのに不変データ構造が望ましい
- 関数型ドメインモデリング ドメイン駆動設計とF#でソフトウェアの複雑さに立ち向かおう | Scott Wlaschin, 猪股 健太郎
効くプロンプト: ファイル配置規則を明記
以下のモノレポの配置規則に従う。
script/
<task-name>.ts # タスク
packages/
<mod-name>/
examples/
*.ts # ユースケース例
src/
index.ts # エントリポイント
index.test.ts # ファイルに対応するユニットテスト
types.ts # 型定義
test/
*.test.ts # インテグレーションテスト
- 中規模以上: タスク毎のエージェントの推測コストを減らす
効くプロンプト: 詳細指示を docs/*.md に分割
(システムプロンプト)
docs/*.md はこのプロジェクトで必要なドキュメントが置かれている。
ユーザーの指示に基づいて参照する。
(個別プロンプト)
@/docs/react-test-pattern.md を読んで @/src/components/Button.tsx にユニットテストを追加
(docs/react-test-pattern.md)
このドキュメントでは react を @testing-library/react でテストを書く方法を指示する。
以下テストコード例...
- 大規模用(3000L~)
- 理由: 無関係な指示が常に存在すると、次第にすべてを無視するようになっていく
- 欠点: ドキュメントを用意/把握するのがだっっっるい
Copliot TIPS: 型定義ファイル を一時的に Pin
- vscode の
Open Editors
でPin
しておくと優先的に参照 - 特に
typescript.d.ts
のようなAPIが膨大なライブラリ
Copilot TIPS: コメントから書き始める
- インラインプロンプトだと思って書く
// ExecutionResult を prioty で並び替える関数<TAB>
- Open Editors の他のタブの内容を見ているので、型定義のPinと相性がいい
- 最終的に消しがち
効くプロンプト: カバレッジに基づくテストの自動生成
テストカバレッジ100%を目指す。
`*.ts` に対して、`*.test.ts` でユニットテストを書く
.test.ts がない実装に対して、他のテストを参考にテストコードを追加
1. `npx vitest --run --coverage` を実行して、現在のカバレッジを取得
2. 今の状態から最もカバレッジが上がるテストコードを考察してから追加
3. 再度カバレッジを計測して、数値が向上していることを確認
ユーザーが満足するまで、テスト生成を繰り返す
個別プロンプト: 機械的なマイグレーション
@/docs/You-Dont-Need-Lodash-Underscore.md を参考に lodash を削除する。
プロジェクト内の lodash のコードを grep で検索して、それらを置き換える。
置き換えた後に `npm test` を実施して、テストが通っていることを確認する
- LLM の手数で面倒なマイグレーションを自動化させる
- 事前にマイグレーションガイドを入れる
個別プロンプト: 似たAPIのライブラリに置き換える
ライブラリAをライブラリBに置き換えて
-
APIまたは概念が類似しているライブラリを書き換えさせる
- cypress => playwright
- jest => vitest
- recoil => jotai
- Vue => Svelte
-
90%自動化できる(最後は気合)
-
これもマイグレーションガイドを用意したい
効く: URL を読む能力 (MCP)
(システムプロンプト)
あなたはURLが与えられた時、以下のコマンドでそのURLの内容をmardownで取得できる
`npx -y @mizchi/readability --format=md <url>`
(個別プロンプト: 1)
https://blog.cloudflare.com/cloudflare-containers-coming-2025/ 読んで
(個別プロンプト: 2)
今読んだ内容を docs/ 以下に要約して保存しておいて
-
https://github.com/mizchi/readability は本文抽出+markdown変換 CLI
- リンク先まで読むように指示すると Deep Research 的な挙動
-
@mizchi/npm-summary - JSR
-
$ npm-summary zod@latest
で.d.ts
を元にユースケース生成
-
うまくいかないプロンプト、その分析
うまくかない: 型だけで設計
Architect モードでは、最初に型シグネチャとそのユースケースで仕様を整理する。
型とそれを使うテストコードをユーザーに提案し、同意後に実装を行う
出力例
```ts
declare function add(a: number, b: number): number;
test("add(1,2) => 3", () => {
expect(1, 2).toBe(3);
})
```
- ほぼ確実に無視される。型だけで抽象的に設計する能力はない
- 「テストと型をそのままに実装だけを修正して」も無視されがち
うまくかない: 非同期例外処理がヘタクソ
async function main() {
try {
await init();
try {
await run();
} catch (err) {
/// ...
}
} catch (err) {
console.error(err)
}
}
- 思考停止気味に try-catch で握り潰す (Gemini に顕著)
- 大規模で破綻する要因の一つ
うまくかない: 環境構築が下手
(User)
typescript + vite + vitest でセットアップして
(Assistant)
foo.ts が実行できません。
vite-node を入れます。 ts-node を入れます、tsx を入れます。 tsconfig.json を削除してみます。
拡張子を全部 .cjs にします、package.json の `type: module` を削除します。
- 「環境」そのものが一番強力なプロンプトで、ゼロショットは発散
- Cline: 手数が仇になって環境破壊
- バイブコーディング: ボイラープレート選択が重要
うまくいかない: モジュールインターフェースが発散
モジュール利用者の視点で使いやすくするために export を減らす
```ts
export {
generateDetailedComplexityReport,
generateMetricsReport,
generateModuleComplexityReport,
} from "./core/mod.ts";
```
- 現実: とにかく実装次第に全部 export
- モジュール間の契約が肥大化して破綻
- 暫定対応: tsr でTreeShake相当でデッドコード検出
npx -y tsr src/index.ts test/**/*.ts
うまくいかない: 「ある」のがよくない!
-
チェーホフの銃の法則 (LLMのプロンプトエンジニアリング)
-
もし、第1幕から壁に拳銃をかけておくのなら、第2幕にはそれが発砲されるべきである。そうでないなら、そこに置いてはいけない。
- 無関係なリソースを読み込んでも、それを使うことに固執する
-
- 大規模コード + Cline は
ls
やgrep
で見つかること自体がノイズ
うまくかない: デバッグログ食いすぎ
console.log("debug: start process")
for (const item of items) {
console.log("debug: process item", item);
// ...
}
console.log("debug: end process")
- 自身が生成したプリントデバッグでコンテキストウィンドウを消費
- 書き散らしたデバッグコードを放置する(Claude 3.x 系に顕著)
結論: やっぱりLLMはコーディングが下手
-
低品質コードで設計破綻して自滅
- リファクタリング指針がない
- 不要コードを判定する能力が低い
- モジュール視点でAPI設計ができない
-
ユーザー側でリファクタリングしても元の低品質なコードに書き戻す
- モデルは自分で書いたコードが一番いいと思ってそう
- かなり最悪の体験
現状認識
TypeScript + LLM: 良い点
- GitHub の公開コードが多く学習量が段違いに多い
-
安全性より表現力を選んだピーキーな型システム
- 自然言語と対応をとった型のモデリングがしやすい
- タグ付きユニオン型で疑似的な代数的データ型も実現できる
-
豊富な静的解析手段とユーザー層の厚さ
- tsc / eslint / babel / ts-morph / oxc / biome ...
- 困ったら誰かが解決してくれるという安心感
TypeScript 言語仕様側の課題
- 貧弱な例外機構で雑な try-catch を誘発
- デフォルトミュータブル & readonly が使いづらい
- モジュールアクセス制御が足りてない
// 様々な処理が不明瞭なコード
import { unsafeFunc } from "../../external/module/internal";
export async function main(input: any): Promise<void> {
let v = input;
try {
v = await unsafeFunc(input, () => new Error("something fallback"));
} catch (err) {
if (err instanceof Error) {}
}
}
(エフェクトシステムがほしい...)
自分の現時点のプラクティス
-
PoC/プロトタイプのコードの生成
- 1ファイル完結の800行以内を目安
-
自分でリファクタしつつ組み込む
- 特にインターフェース設計は自分でやる
-
失敗パターンをプロンプトに反映
- 盆栽
-
成功するパターンのドキュメント化
- 再度参照する用に
docs/*.md
を構築
- 再度参照する用に
Deno: おそらく今最高のバイブコーディング環境
import fs from "node:fs";
import { ok } from 'npm:neverthrow@8.2.0';
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
exect(1).toBe(1);
});
- Deno Permission で実行権限を制御
- 例:
$ deno run --deny-net --deny-env --allow-read=./tmp run.ts
- 今後の学習データ汚染を考慮すると必須
- 例:
- ライブラリの事前インストール不要 + node.js 互換層
Deno PoC: プロンプト
- Deno の node 互換モードで単体ファイルに完結して実装する
- ファイルの先頭にはコメントで仕様を記述する
- 実装と同じファイルに `Deno.test()` でテストを書く
- `if(import.meta.main) {}` で直接したときのサンプルを記述する。あなたはここでデバッグして良い。
- 関数指向でドメインモデリングする。class より関数による実装を優先
出力例
```ts
/**
* <このモジュールの仕様>
**/
import fs from "node:fs";
import { ok } from 'npm:neverthrow@8.2.0';
export function add(a: number, b: number): number {
return a + b;
}
if (import.meta.main) {
console.log(add(3, 4));
}
import { expect } from "@std/expect";
Deno.test("足し算の結果を返すこと", () => {
expect(add(1, 2)).toBe(3);
})
```
対策: Lint でプロンプトをルールベースに変換
- 例: 自作カスタムルール do-try
- https://github.com/mizchi/deno-lint-plugin-do-try
// doからはじまる関数は try-catch を必須
try {
const v = await doGetData();
} catch (_err) {}
// 例外中立: doからはじまる関数のみ try catch 免除
async function doMain() {
await doXXX();
await doYYY();
}
Lint ルールをバイブコーディングする
$ deno init
以下のURLを参考に deno lint ルールを実装したい
`https://docs.deno.com/runtime/reference/lint_plugins/`
以下の条件を満たすルールを実装する
```ts
// エラーになるケース
const v = await doGetData();
// 許容するケース
try {
const v = await doGetData();
} catch (_err) {}
```
まとめ: Agentic Coding で何が変わったのか?
プログラミング自体の変質
-
より重要になったスキル
- テスト駆動開発 で開発高速化
-
新しく発生している領域
- LLM の得意領域/発達段階を予測する技術
- プロンプトエンジニアリング
-
変わっていないこと
- プログラミングの最終的な難易度/複雑性
- 自然言語 to コード で間違いなく学びやすくなっている
まとめ | 結論
-
今はコストと性能がトレードオフ
- 一旦は得意なことを任せる
- AIが苦手なことは(自動化コストを念頭に)人間がやる。VLM のコストを注視
-
エンジニアとして生き残るにはコード生成にスキルを寄せる
- 自動化成功の見返りは 10x どころではない破壊的なもの
- 自動テスト+プロンプトエンジニアリングが高コスパ
おわり
Discussion