JA Development Guides Code Styles - aku11i/phantom GitHub Wiki

コードスタイル

English | 日本語

このガイドでは、Phantomのコーディング規約とスタイルガイドラインを文書化しています。これらに従うことで、コードベース全体の一貫性が保証されます。

一般原則

ソース: CLAUDE.md#L10

  1. コードコメントなし - 自己文書化されたコードを書く
  2. 既存のパターンに従う - 個人の好みより一貫性
  3. 英語のみ - すべてのコード、コミット、ドキュメントは英語で
  4. 型安全性 - TypeScriptを最大限活用

コードフォーマット

Biome設定

ソース: biome.json

PhantomはフォーマットとリンティングにBiomeを使用します:

{
  "formatter": {
    "enabled": true,
    "indentStyle": "tab",
    "indentWidth": 2,
    "lineWidth": 100,
    "lineEnding": "lf",
    "quoteStyle": "double",
    "trailingComma": "all",
    "semicolons": "always"
  }
}

主要なフォーマットルール

インデント

  • インデントにはタブを使用
  • タブ幅は2スペースとして表示
// ✓ 良い
function example() {
	if (condition) {
		doSomething();
	}
}

// ✗ 悪い - スペース
function example() {
  if (condition) {
    doSomething();
  }
}

クォート

  • 文字列にはダブルクォートを使用
// ✓ 良い
const message = "Hello, world!";
const html = `<div class="container">`;

// ✗ 悪い
const message = 'Hello, world!';

セミコロン

  • 常にセミコロンを使用
// ✓ 良い
const name = "phantom";
import { Result } from "./types.js";

// ✗ 悪い
const name = "phantom"
import { Result } from "./types.js"

末尾のカンマ

  • 複数行の構造では末尾のカンマを使用
// ✓ 良い
const config = {
	name: "phantom",
	version: "1.0.0",
	author: "aku11i",
};

// ✗ 悪い
const config = {
	name: "phantom",
	version: "1.0.0",
	author: "aku11i"
};

TypeScriptの規約

型アノテーション

関数パラメーターと戻り値には常に明示的な型を使用:

// ✓ 良い
function createWorktree(name: string, branch?: string): Promise<Result<WorktreeInfo>> {
	// 実装
}

// ✗ 悪い - 型がない
function createWorktree(name, branch) {
	// 実装
}

Interface vs Type

オブジェクトの形状にはinterface、ユニオンとプリミティブにはtypeを使用:

// ✓ 良い - オブジェクトにはinterface
interface WorktreeInfo {
	name: string;
	path: string;
	branch: string;
	isMain: boolean;
}

// ✓ 良い - ユニオンにはtype
type Result<T, E = Error> = 
	| { ok: true; value: T }
	| { ok: false; error: E };

// ✗ 悪い - シンプルなオブジェクトにtype
type WorktreeInfo = {
	name: string;
	// ...
};

Constアサーション

リテラル型にはas constを使用:

// ✓ 良い
const COMMANDS = ["create", "delete", "list", "exec", "shell"] as const;
type Command = typeof COMMANDS[number];

// ✗ 悪い
const COMMANDS = ["create", "delete", "list", "exec", "shell"];

モジュール構造

インポートの整理

ソース: src/cli/handlers/create.ts

インポートを一貫して順序付け:

// 1. Node.js組み込み
import { existsSync } from "node:fs";
import { join } from "node:path";

// 2. 外部依存関係(ランタイムにはなし)

// 3. 内部 - 絶対パス
import { Result } from "../../core/types/result.js";
import { createWorktree } from "../../core/worktree/create.js";

// 4. 内部 - 相対パス
import { output } from "../output.js";

ファイル拡張子

インポートには常に.js拡張子を含める:

// ✓ 良い - ESMは拡張子を必要とする
import { createWorktree } from "./create.js";

// ✗ 悪い - ランタイムで失敗する
import { createWorktree } from "./create";

エクスポートスタイル

名前付きエクスポートを使用、デフォルトエクスポートは避ける:

// ✓ 良い - 名前付きエクスポート
export function createWorktree() { }
export function deleteWorktree() { }

// ✗ 悪い - デフォルトエクスポート
export default function createWorktree() { }

命名規約

変数と関数

camelCaseを使用:

// ✓ 良い
const worktreeName = "feature";
function validateWorktreeName(name: string) { }

// ✗ 悪い
const worktree_name = "feature";
function ValidateWorktreeName(name: string) { }

定数

真の定数にはUPPER_SNAKE_CASEを使用:

// ✓ 良い
const MAX_NAME_LENGTH = 50;
const DEFAULT_BRANCH = "main";

// ✗ 悪い
const maxNameLength = 50;
const default_branch = "main";

型とインターフェース

PascalCaseを使用:

// ✓ 良い
interface WorktreeInfo { }
type CommandHandler = () => void;

// ✗ 悪い
interface worktreeInfo { }
type commandHandler = () => void;

ファイル名

ファイル名にはkebab-caseを使用:

✓ 良い:
- create-worktree.ts
- git-executor.ts
- list-worktrees.test.ts

✗ 悪い:
- createWorktree.ts
- GitExecutor.ts
- listWorktrees.test.ts

関数ガイドライン

関数の長さ

関数は短く、焦点を絞って保つ:

// ✓ 良い - 単一責任
function validateWorktreeName(name: string): Result<void> {
	if (!name) {
		return Result.error(new Error("Name is required"));
	}
	
	if (name.includes("/") || name.includes("..")) {
		return Result.error(new Error("Invalid characters in name"));
	}
	
	return Result.ok();
}

// ✗ 悪い - やりすぎ
function createAndValidateWorktree(name: string, showOutput: boolean) {
	// 検証ロジック
	// 作成ロジック
	// 出力ロジック
	// 責任が多すぎる
}

早期リターン

ネストを減らすために早期リターンを使用:

// ✓ 良い - 早期リターン
function processWorktree(name: string): Result<void> {
	if (!name) {
		return Result.error(new Error("Name required"));
	}
	
	if (invalidCharacters(name)) {
		return Result.error(new Error("Invalid name"));
	}
	
	// メインロジック
	return Result.ok();
}

// ✗ 悪い - ネストした条件
function processWorktree(name: string): Result<void> {
	if (name) {
		if (!invalidCharacters(name)) {
			// メインロジック
			return Result.ok();
		} else {
			return Result.error(new Error("Invalid name"));
		}
	} else {
		return Result.error(new Error("Name required"));
	}
}

純粋関数

副作用のない純粋関数を優先:

// ✓ 良い - 純粋関数
function calculatePath(basePath: string, name: string): string {
	return join(basePath, name);
}

// ✗ 悪い - 副作用
let globalPath: string;
function calculatePath(basePath: string, name: string): void {
	globalPath = join(basePath, name);  // 副作用
}

エラーハンドリング

Result型パターン

失敗する可能性のある操作には常にResult型を使用:

// ✓ 良い - 明示的なエラーハンドリング
function parseConfig(content: string): Result<Config> {
	try {
		const config = JSON.parse(content);
		return Result.ok(config);
	} catch (error) {
		return Result.error(new Error(`Invalid config: ${error.message}`));
	}
}

// ✗ 悪い - 例外を投げる
function parseConfig(content: string): Config {
	return JSON.parse(content);  // エラー時にスロー
}

エラーメッセージ

エラーメッセージは役に立つものにする:

// ✓ 良い - 説明的なエラー
return Result.error(
	new Error(`Worktree '${name}' already exists at ${path}`)
);

// ✗ 悪い - 曖昧なエラー
return Result.error(new Error("Error"));

Async/Await

常にAsync/Awaitを使用

プロミスよりasync/awaitを優先:

// ✓ 良い
async function loadConfig(): Promise<Result<Config>> {
	const result = await readFile("config.json");
	if (!result.ok) {
		return result;
	}
	
	return parseConfig(result.value);
}

// ✗ 悪い - プロミスチェーン
function loadConfig(): Promise<Result<Config>> {
	return readFile("config.json")
		.then(result => {
			if (!result.ok) {
				return result;
			}
			return parseConfig(result.value);
		});
}

Asyncでのエラーハンドリング

async関数でエラーを適切に処理:

// ✓ 良い
async function execute(): Promise<Result<void>> {
	try {
		await riskyOperation();
		return Result.ok();
	} catch (error) {
		return Result.error(error as Error);
	}
}

// ✗ 悪い - 未処理のリジェクション
async function execute(): Promise<void> {
	await riskyOperation();  // スローできる
}

コード編成

単一責任

各モジュールには明確な目的が1つあるべき:

// ✓ 良い - 焦点を絞ったモジュール
// validate.ts - 検証のみ
export function validateWorktreeName(name: string): Result<void> { }

// create.ts - 作成のみ
export function createWorktree(name: string): Result<WorktreeInfo> { }

// ✗ 悪い - 混在した責任
// worktree.ts - すべてを行う
export function validateAndCreateWorktree() { }
export function formatWorktreeOutput() { }
export function deleteWorktreeWithLogging() { }

依存関係の方向

依存関係は下向きに流れるべき:

✓ 良い:
CLI → Core → Infrastructure

✗ 悪い:
Core → CLI (上向き依存)

テストの規約

テスト構造

Arrange-Act-Assertパターンに従う:

it("should create worktree with branch", async () => {
	// Arrange
	const name = "feature";
	const branch = "develop";
	mockGit.execute.mockReturnValue(Result.ok(""));
	
	// Act
	const result = await createWorktree(name, branch);
	
	// Assert
	assert.strictEqual(result.ok, true);
	if (result.ok) {
		assert.strictEqual(result.value.branch, branch);
	}
});

テストの命名

説明的なテスト名を使用:

// ✓ 良い
describe("validateWorktreeName", () => {
	it("should accept alphanumeric names with hyphens");
	it("should reject names with path separators");
	it("should reject empty names");
});

// ✗ 悪い
describe("validate", () => {
	it("works");
	it("fails");
});

ドキュメント

コードコメントなし

コメントの代わりに自己文書化されたコードを書く:

// ✗ 悪い - コメントが必要
// 名前に無効な文字が含まれているかチェック
if (n.indexOf("/") >= 0 || n.indexOf("..") >= 0) {
	return false;
}

// ✓ 良い - 自己文書化
const containsPathSeparator = name.includes("/");
const containsParentReference = name.includes("..");

if (containsPathSeparator || containsParentReference) {
	return Result.error(new Error("Name cannot contain path separators"));
}

関数名

説明的な関数名を使用:

// ✓ 良い
function isValidWorktreeName(name: string): boolean { }
function getWorktreeByName(name: string): Result<WorktreeInfo> { }

// ✗ 悪い
function check(n: string): boolean { }
function get(n: string): Result<WorktreeInfo> { }

Gitコミットスタイル

コミットメッセージ

従来のコミットに従う:

✓ 良い:
feat: add support for custom worktree paths
fix: handle spaces in repository names
docs: update README with new commands
test: add tests for error scenarios
refactor: extract git operations to separate module

✗ 悪い:
Updated stuff
Fix
Changes
WIP

コミットスコープ

コミットを焦点を絞って保つ:

✓ 良い:
- コミットごとに1つの論理的な変更
- すべてのテストが合格
- 無関係な変更なし

✗ 悪い:
- 複数の無関係な変更
- 壊れたテスト
- デバッグコードが含まれている

リンティングルール

Biomeリンター設定

ソース: biome.json

主要なリンティングルール:

{
  "linter": {
    "rules": {
      "recommended": true,
      "style": {
        "noParameterAssign": "error",
        "noVar": "error",
        "useConst": "error",
        "useTemplate": "warn"
      },
      "complexity": {
        "noForEach": "off",
        "useOptionalChain": "warn"
      },
      "correctness": {
        "noUnusedVariables": "error",
        "noUnusedImports": "error"
      }
    }
  }
}

リンターの実行

# コードをチェック
pnpm lint

# 問題を自動修正
pnpm fix

# 特定のファイルをチェック
npx biome check src/core/worktree/create.ts

ベストプラクティスのまとめ

  1. 一貫性 - 既存のパターンに従う
  2. 明確性 - 読みやすく、自己文書化されたコードを書く
  3. シンプルさ - 巧妙なコードを避け、明白な解決策を優先
  4. 型安全性 - TypeScriptの機能を最大限使用
  5. エラーハンドリング - Result型を一貫して使用
  6. テスト - すべての新しいコードにテストを書く
  7. コメントなし - コードにコメントが必要な場合は、リファクタリング

覚えておいてください:コードは書かれるよりもはるかに頻繁に読まれます。読みやすさのために最適化してください。

⚠️ **GitHub.com Fallback** ⚠️