メインコンテンツまでスキップ

コメント

コメントはなぜ必要なのか

良いコードはコメントがなくても理解できることが理想です。しかし、以下の場合にはコメントが有用です:

コメントが必要な場面
  • Why(なぜ)を説明する
  • 複雑なアルゴリズムの意図を説明
  • 注意事項や制約を明記
  • TODO や FIXME などのマーカー
  • 公開 API のドキュメント(JSDoc)

コメントのデメリット

問題点:

  • コメントはコンパイラでチェックされない
  • コードが変更されてもコメントが更新されないことがある
  • 古くなったコメントは誤解を招く

結論: コメントよりも自己説明的なコードを優先する


コードで意図を表す

❌ Bad: コメントで説明

❌ Bad: コメントで説明
// ユーザーが管理者または編集者の場合にtrueを返す
if (user.role === 'admin' || user.role === 'editor') {
// 編集を許可
allowEdit = true;
}

✅ Good: コードで意図を表現

✅ Good: コードで意図を表現
// 関数名で意図を表現
function canEdit(user: User): boolean {
return user.role === 'admin' || user.role === 'editor';
}

if (canEdit(user)) {
allowEdit = true;
}

❌ Bad: コメントに頼る

❌ Bad: コメントに頼る
// 価格から消費税を計算して合計金額を返す
function calc(p: number): number {
return p * 1.1;
}

✅ Good: 自己説明的な関数

✅ Good: 自己説明的な関数
function calculatePriceWithTax(price: number, taxRate: number = 0.1): number {
return price * (1 + taxRate);
}

当たり前のことは書かない

❌ Bad: 自明なコメント

❌ Bad: 自明なコメント
// ユーザー名を取得する
function getUserName(user: User): string {
return user.name;
}

// カウンターをインクリメント
counter++;

// iを1増やす
i++;

// ユーザークラス
class User {
// 名前
name: string;

// メールアドレス
email: string;
}

これらのコメントはコードを読めば分かるため不要です。

✅ Good: コメントなしで明確

✅ Good: コメントなしで明確
function getUserName(user: User): string {
return user.name;
}

counter++;
i++;

class User {
name: string;
email: string;
}

良いコメント

1. **Why(なぜ)**を説明するコメント

✅ Good: なぜこの実装をしたのかを説明
// ✅ Good: なぜこの実装をしたのかを説明

function getUsers(): User[] {
// パフォーマンス向上のため、ユーザー一覧をキャッシュする
// API呼び出しは1分間に1回のみ許可されている
if (cache.has('users') && !cache.isExpired('users', 60000)) {
return cache.get('users');
}

const users = fetchUsersFromApi();
cache.set('users', users);
return users;
}

// ✅ Good: 制約や注意事項
function processPayment(amount: number): void {
// 注意: この処理は冪等性がない
// 同じリクエストを複数回実行すると重複課金される
chargeCustomer(amount);
}

2. 複雑なアルゴリズムの説明

✅ Good: アルゴリズムの意図を説明
// ✅ Good: アルゴリズムの意図を説明

/**
* Luhnアルゴリズムを使用してクレジットカード番号を検証
* https://en.wikipedia.org/wiki/Luhn_algorithm
*/
function validateCreditCard(cardNumber: string): boolean {
const digits = cardNumber.replace(/\D/g, '').split('').map(Number);

// 右から2桁目を起点に、1つおきに2倍にする
for (let i = digits.length - 2; i >= 0; i -= 2) {
digits[i] *= 2;
// 2桁になった場合は各桁を足す(例: 12 → 1 + 2 = 3)
if (digits[i] > 9) {
digits[i] -= 9;
}
}

const sum = digits.reduce((acc, digit) => acc + digit, 0);
return sum % 10 === 0;
}

3. TODOやFIXMEのマーカー

// TODO: ページネーションを実装する
function getUsers(): User[] {
return allUsers;
}

// FIXME: この処理は O(n^2) なのでパフォーマンスが悪い
function findDuplicates(items: string[]): string[] {
const duplicates: string[] = [];
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
if (items[i] === items[j]) {
duplicates.push(items[i]);
}
}
}
return duplicates;
}

// HACK: 一時的な回避策(本来はAPIを修正すべき)
const data = apiResponse.data?.result?.[0] || {};

// NOTE: この定数は外部APIの仕様に依存している
const MAX_ITEMS_PER_PAGE = 50;

一般的なマーカー:

  • TODO: 将来実装すべきこと
  • FIXME: 修正が必要な既知の問題
  • HACK: 一時的な回避策
  • NOTE: 重要な注意事項
  • WARNING: 警告

4. 公開APIのドキュメント(JSDoc)

TypeScriptで公開するAPIには、JSDocコメントを使用します。

/**
* ユーザーをIDで検索します
*
* @param userId - ユーザーの一意なID
* @returns ユーザーオブジェクト。見つからない場合はundefined
*
* @example
* ```typescript
* const user = getUserById("12345");
* if (user) {
* console.log(user.name);
* }
* ```
*/
function getUserById(userId: string): User | undefined {
return users.find(user => user.id === userId);
}

/**
* ユーザーを作成します
*
* @param name - ユーザー名
* @param email - メールアドレス(一意である必要があります)
* @returns 作成されたユーザーオブジェクト
* @throws {ValidationError} メールアドレスが無効な場合
* @throws {DuplicateError} メールアドレスが既に使用されている場合
*/
function createUser(name: string, email: string): User {
if (!isValidEmail(email)) {
throw new ValidationError('Invalid email address');
}

if (emailExists(email)) {
throw new DuplicateError('Email already exists');
}

return new User(generateId(), name, email);
}

/**
* 複数の条件でユーザーを検索します
*
* @param options - 検索オプション
* @param options.role - ユーザーロール(オプション)
* @param options.isActive - アクティブ状態(オプション)
* @param options.limit - 最大取得件数(デフォルト: 100)
* @returns 条件に一致するユーザーの配列
*/
function searchUsers(options: {
role?: UserRole;
isActive?: boolean;
limit?: number;
}): User[] {
// ...
}

5. 非直感的な実装の説明

✅ Good: なぜこの順序なのかを説明
// ✅ Good: なぜこの順序なのかを説明

function initializeApp(): void {
loadConfig();
connectDatabase();
startServer();

// サーバー起動後にキャッシュを初期化する必要がある
// キャッシュの初期化にはHTTPリクエストが必要なため
initializeCache();
}

// ✅ Good: 特殊なケースの説明

function parseDate(dateString: string): Date {
// Safari は "YYYY-MM-DD" 形式をサポートしていないため
// "YYYY/MM/DD" に変換する
const safariCompatibleDate = dateString.replace(/-/g, '/');
return new Date(safariCompatibleDate);
}

悪いコメント

1. コードの繰り返し

❌ Bad: コードをそのまま繰り返している
// ❌ Bad: コードをそのまま繰り返している
// ユーザー名をuserNameに代入する
const userName = user.name;

// ❌ Bad
// iが10未満の間ループする
for (let i = 0; i < 10; i++) {
// ...
}

// ❌ Bad
// User クラス
class User {
// name プロパティ
name: string;
}

2. 誤解を招くコメント

❌ Bad: コメントとコードが一致していない
// ❌ Bad: コメントとコードが一致していない

// 全ユーザーを取得
function getActiveUsers(): User[] {
return users.filter(user => user.isActive); // アクティブなユーザーのみ
}

// ❌ Bad: 古くなったコメント
// 最大3回リトライする
const MAX_RETRIES = 5; // コメントと値が矛盾

3. コメントアウトされたコード

❌ Bad: コメントアウトされたコードを残す
// ❌ Bad: コメントアウトされたコードを残す

function calculateTotal(items: Item[]): number {
// const tax = 0.08;
// const subtotal = items.reduce((sum, item) => sum + item.price, 0);
// return subtotal * (1 + tax);

return items.reduce((sum, item) => sum + item.price, 0);
}

理由:

  • バージョン管理システム(Git)があるので、古いコードは履歴から復元できる
  • コメントアウトされたコードは混乱を招く

4. 過剰なコメント

❌ Bad: 過剰
// ❌ Bad: 過剰

// ユーザー情報を保持するクラス
class User {
// ユーザーID
private id: string;

// ユーザー名
private name: string;

// メールアドレス
private email: string;

// コンストラクタ
// @param id ユーザーID
// @param name ユーザー名
// @param email メールアドレス
constructor(id: string, name: string, email: string) {
// idをthis.idに代入
this.id = id;
// nameをthis.nameに代入
this.name = name;
// emailをthis.emailに代入
this.email = email;
}

// ユーザー名を取得するメソッド
// @returns ユーザー名
getName(): string {
// this.nameを返す
return this.name;
}
}

// ✅ Good: シンプルで明確
class User {
constructor(
private id: string,
private name: string,
private email: string
) {}

getName(): string {
return this.name;
}
}

TypeScriptにおけるコメントのベストプラクティス

1. 型情報をコメントに書かない

TypeScriptは型システムがあるため、型情報をコメントに書く必要はありません。

❌ Bad: 型をコメントで説明
// ❌ Bad: 型をコメントで説明
// @param userId ユーザーID(文字列)
// @returns ユーザーオブジェクト(Userまたはundefined)
function getUserById(userId, email) {
// ...
}

// ✅ Good: 型システムで表現
function getUserById(userId: string): User | undefined {
// ...
}

// ✅ より詳細なドキュメントが必要な場合
/**
* ユーザーをIDで検索します
*
* @param userId - ユーザーの一意な識別子
* @returns 見つかったユーザー。存在しない場合は undefined
*/
function getUserById(userId: string): User | undefined {
// ...
}

2. インターフェースには説明を追加

✅ Good: インターフェースの意図を説明
// ✅ Good: インターフェースの意図を説明

/**
* ユーザー情報を表すインターフェース
*/
interface User {
/** ユーザーの一意なID */
id: string;

/** 表示名 */
name: string;

/** メールアドレス(一意) */
email: string;

/** アカウント作成日時 */
createdAt: Date;

/**
* ユーザーロール
* @default 'user'
*/
role?: UserRole;
}

3. 複雑な型にはコメントを追加

✅ Good: 複雑な型の説明
// ✅ Good: 複雑な型の説明

/**
* API レスポンスの型
* 成功時は data を含み、失敗時は error を含む
*/
type ApiResponse<T> =
| { success: true; data: T; error?: never }
| { success: false; data?: never; error: string };

/**
* ユーザーの権限チェック関数の型
* @param user - チェック対象のユーザー
* @param resource - アクセスするリソース
* @returns true の場合、アクセスを許可
*/
type PermissionChecker = (user: User, resource: string) => boolean;

練習問題 6: コメントの改善

以下のコードのコメントを改善してください。

❌ Bad: 修正前
// ❌ 修正前

// ユーザークラス
class User {
// 名前
name: string;

// 年齢
age: number;

// コンストラクタ
// 名前と年齢を受け取る
constructor(name: string, age: number) {
// nameをthis.nameに代入
this.name = name;
// ageをthis.ageに代入
this.age = age;
}

// 大人かどうかを判定
// 20歳以上ならtrue
isAdult(): boolean {
// ageが20以上ならtrueを返す
return this.age >= 20;
}
}

// ユーザーを取得する関数
// idを受け取ってユーザーを返す
function getUser(id: string): User {
// ユーザー一覧からidで検索
// const user = users.find(u => u.id === id); // 古いコード

// キャッシュから取得
return cache.get(id);
}
📝 解答を見る
✅ Good: 修正後
// ✅ 修正後

/**
* ユーザー情報を表すクラス
*/
class User {
constructor(
public name: string,
public age: number
) {}

/**
* 成人(20歳以上)かどうかを判定
*/
isAdult(): boolean {
const ADULT_AGE = 20;
return this.age >= ADULT_AGE;
}
}

/**
* ユーザーをIDで取得
*
* パフォーマンス向上のため、データベースではなくキャッシュから取得する
* キャッシュにヒットしない場合の処理は呼び出し元で行う必要がある
*
* @param id - ユーザーID
* @returns キャッシュされたユーザーオブジェクト、存在しない場合は undefined
*/
function getUser(id: string): User | undefined {
return cache.get(id);
}

改善のポイント:

  1. 自明なコメントを削除
  2. コメントアウトされたコードを削除(履歴はGitで管理)
  3. 意図や制約を説明するコメントを追加
  4. JSDocで公開APIをドキュメント化

コメントのまとめ

コメントのガイドライン
  • コードで表現できることはコメントしない
  • What(何を)ではなく Why(なぜ)を書く
  • 複雑なアルゴリズムには説明を追加
  • 公開 API には JSDoc を使用
  • TODO / FIXME などのマーカーを活用
  • コメントアウトされたコードは削除
  • 型情報はコメントではなく型システムで表現

原則: コメントよりも自己説明的なコードを優先する