mcpField
C
control-flow

control-flow

github.com/epicenterhq開発4.8k75 回インストール2 週間前に更新
リポジトリ

control-flow について

control-flowは、複雑な制御フローをリファクタリングする開発者向けスキルです。早期リターン、相互排他的な条件、明確な命名でネストを減らし、失敗処理と正常系を分けます。

主な機能

  1. 01早期リターンできるケースを先に処理
  2. 02状態を相互排他的な条件で表現
  3. 03正常系に主要な処理を集約
  4. 04自然な言葉のブール変数名を使用
  5. 05エラー境界と後続処理を分離

活用シーン

  1. 01重複を含むネスト条件の簡素化
  2. 02失敗箇所が複数あるハンドラーの整理
  3. 03危険な呼び出しが1つのtry-catchを再構成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/epicenterhq/epicenter --skill control-flow

インストール後、Claude Code で/control-flowを入力して読み込まれたか確認します。

詳細

人が読みやすい制御フロー

複雑な制御フローをリファクタリングするときは、人間の自然な推論パターンにならいましょう。

関連スキル:分岐の折りたたみと呼び出し元の根拠については、リファクタリング手法を参照してください。

コアパターン

  1. まず人間が考える質問をする:「今あるものを使えるか?」→ 成功パスで早期リターンする
  2. 状況を見極める:「現在の状態はどうなっていて、何をする必要があるか?」→ 明確で相互に排他的な条件を使う
  3. アクションを起こす:「必要なものを取得する」→ ロジックを最後に集約する
  4. 自然言語の変数名を使う:isUsingNavigator、isUsingLocalTranscription、needsOldFileCleanupのように、考えをそのまま読める名前にする
  5. 不自然な構造を避ける:人間が問題を実際に考える方法に合わない、ネストした条件分岐は使わない

これを変換します:ロジックが重複したネスト条件 これに変換します:人間の意思決定を反映した直線的なフロー

例:自然言語の変数名を使った早期リターン

// From apps/whispering/src/routes/(app)/_layout-utils/check-ffmpeg.ts

export async function checkFfmpegRecordingMethodCompatibility() {
	if (!window.__TAURI_INTERNALS__) return;

	// Only check if FFmpeg recording method is selected
	if (settings.value['recording.method'] !== 'ffmpeg') return;

	const { data: ffmpegInstalled } =
		await rpc.ffmpeg.checkFfmpegInstalled.ensure();
	if (ffmpegInstalled) return; // FFmpeg is installed, all good

	// FFmpeg recording method selected but not installed
	toast.warning('FFmpeg Required for FFmpeg Recording Method', {
		// ... toast content
	});
}

例:自然言語のブール値

// From apps/whispering/src/routes/(app)/_layout-utils/check-ffmpeg.ts

const isUsingNavigator = settings.value['recording.method'] === 'navigator';
const isUsingLocalTranscription =
	settings.value['transcription.selectedTranscriptionService'] ===
		'whispercpp' ||
	settings.value['transcription.selectedTranscriptionService'] === 'parakeet';

return isUsingNavigator && isUsingLocalTranscription && !isFFmpegInstalled;

例:コメント付きのクリーンアップ判定

// From packages/epicenter/src/indexes/markdown/markdown-index.ts

/**
 * This is checking if there's an old filename AND if it's different
 * from the new one. It's essentially checking: "has the filename
 * changed?" and "do we need to clean up the old file?"
 */
const needsOldFileCleanup = oldFilename && oldFilename !== filename;
if (needsOldFileCleanup) {
	const oldFilePath = path.join(tableConfig.directory, oldFilename);
	await deleteMarkdownFile({ filePath: oldFilePath });
	tracking[table.name]!.deleteByFilename({ filename: oldFilename });
}

例:try-catchをガード+成功パスに直線化する

try-catchブロックは、try本体とcatch本体という2つの分岐からなるネスト構造を作ります。try内で実際に例外をスローする可能性がある呼び出しが1つだけの場合は、try-catchをガード付きの呼び出し+早期リターンに置き換えると、コードを上から下へ読み進められます。

変換前(ネストしており、例外とリターンが混在):

async ({ body, status }) => {
	const adapter = createAdapter(body.provider);

	try {
		const stream = chat({ adapter, messages: body.messages });
		return toServerSentEventsResponse(stream);
	} catch (error) {
		if (error instanceof Error && error.name === 'AbortError') {
			throw status(499, 'Client closed request');
		}
		const message = error instanceof Error ? error.message : 'Unknown error';
		throw status('Bad Gateway', `Provider error: ${message}`);
	}
};

変換後(直線的で、リターンが一貫):

async ({ body, status }) => {
	const adapter = createAdapter(body.provider);

	const { data: stream, error: chatError } = trySync({
		try: () => chat({ adapter, messages: body.messages }),
		catch: (e) => Err(e instanceof Error ? e : new Error(String(e))),
	});

	if (chatError) {
		if (chatError.name === 'AbortError') {
			return status(499, 'Client closed request');
		}
		return status('Bad Gateway', `Provider error: ${chatError.message}`);
	}

	return toServerSentEventsResponse(stream);
};

この変換も、同じ人間の推論パターンに従います。

  1. リスクのある処理を試す:失敗する可能性がある箇所だけを囲む
  2. 失敗したかどうかを確認する:適切なエラーで早期リターンする
  3. 成功パスを続ける:関数の残りの部分では成功を前提とする

これによりネストがなくなり、returnとthrowの使い分けに一貫性が生まれ、エラー処理の境界と、その後に続く安全なコードを分離できます。

例:ハンドラー内での逐次ガード

ハンドラーに失敗する可能性のある箇所が複数ある場合は、それぞれのガードで同じパターンに従います。処理を実行し、結果を確認して、早期リターンするか続行します。

async ({ body, status }) => {
	// Guard 1: validate input
	if (!isSupportedProvider(body.provider)) {
		return status('Bad Request', `Unsupported provider: ${body.provider}`);
	}

	// Guard 2: resolve dependency
	const apiKey = resolveApiKey(body.provider, headers['x-api-key']);
	if (!apiKey) {
		return status('Unauthorized', 'Missing API key');
	}

	// Guard 3: risky operation
	const { data: stream, error } = trySync({
		try: () => chat({ adapter: createAdapter(body.provider, apiKey) }),
		catch: (e) => Err(e instanceof Error ? e : new Error(String(e))),
	});
	if (error) return status('Bad Gateway', error.message);

	// Happy path: all guards passed
	return toServerSentEventsResponse(stream);
};

すべてのガードは同じ形です。確認し、失敗していれば早期リターンします。成功パスは最後に集約されます。上から下へ読むと、成功ケースが現れる前に、関数が失敗するあらゆる方法が分かります。

コメント