SJ blog
architecture
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

クリーンコードの書き方:読みやすいコードの原則

命名・関数の単一責任・早期リターン・コメントの使い方など、実務ですぐ活かせるクリーンコードの書き方を具体的なコード例で解説します。

一言結論

クリーンコードの本質は「将来の自分を含む読み手への配慮」であり、意図が伝わる命名と単一責任の徹底だけで、コードの読み書きコストは大幅に下がる。

なぜクリーンコードが重要か

コードは書く時間より読む時間のほうが圧倒的に長い。自分が書いたコードでも、1ヶ月後には「このコード何をしているんだ?」となる。

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler

原則1: 意図が伝わる命名

// ❌ 何を表しているか不明
const d = new Date();
const x = users.filter(u => u.a > 18);

// ✅ 意図が一目でわかる
const currentDate = new Date();
const adultUsers = users.filter(user => user.age > 18);

命名のルール:

  • ブール値は is/has/can/should で始める(isActive, hasPermission
  • 関数は動詞から始める(getUser, sendEmail, validateInput
  • 定数はSCREAMING_SNAKE_CASE(MAX_RETRY_COUNT
  • 略語を避ける(usruser, btnbutton

原則2: 関数は一つのことをする

// ❌ 一つの関数がユーザー取得・バリデーション・メール送信・ログを全部やる
async function processUser(userId: string) {
  const user = await db.users.findById(userId);
  if (!user.email.includes("@")) throw new Error("Invalid email");
  await sendEmail(user.email, "Welcome!");
  console.log(`User ${userId} processed`);
}

// ✅ それぞれの責務を分離
async function getUser(userId: string): Promise<User> {
  const user = await db.users.findById(userId);
  if (!user) throw new UserNotFoundError(userId);
  return user;
}

function validateEmail(email: string): void {
  if (!email.includes("@")) throw new InvalidEmailError(email);
}

async function sendWelcomeEmail(user: User): Promise<void> {
  await emailService.send({ to: user.email, template: "welcome" });
}

// 呼び出し側で組み合わせる
async function onboardUser(userId: string) {
  const user = await getUser(userId);
  validateEmail(user.email);
  await sendWelcomeEmail(user);
}

原則3: 早期リターンでネストを浅く

// ❌ ネストが深く、正常系を把握しにくい
function processOrder(order: Order | null) {
  if (order !== null) {
    if (order.status === "pending") {
      if (order.items.length > 0) {
        // 正常系の処理がずっと下
        chargePayment(order);
      } else {
        throw new Error("Empty order");
      }
    } else {
      throw new Error("Invalid status");
    }
  } else {
    throw new Error("Order not found");
  }
}

// ✅ ガード節で先に異常系を排除する
function processOrder(order: Order | null) {
  if (!order) throw new OrderNotFoundError();
  if (order.status !== "pending") throw new InvalidOrderStatusError(order.status);
  if (order.items.length === 0) throw new EmptyOrderError();

  // ここに来たら必ず正常系
  chargePayment(order);
}

原則4: コメントはなぜ(Why)を書く

// ❌ コードと同じことをコメントしているだけ
// ユーザーの年齢が18より大きいか確認する
if (user.age > 18) { ... }

// ❌ 古いコードをコメントアウトして残す(削除してgitに任せる)
// const oldMethod = () => { ... }

// ✅ コードから読み取れない「理由」をコメントする
// RFC 5321 ではSMTPのリトライ間隔を指数バックオフにすることが推奨されている
const retryDelay = Math.pow(2, retryCount) * 1000;

// ✅ ハックや回避策には必ず背景を書く
// Safari 15.3 では Date.parse() が ISO 8601 を誤解析する (Safari Bug #234567)
// そのため手動でパースしている
const date = parseDateManually(dateString);

原則5: マジックナンバーを排除する

// ❌ 300・86400・7の意味が不明
if (responseTime > 300) {
  await sleep(86400 * 7);
}

// ✅ 定数に名前をつける
const SLOW_RESPONSE_THRESHOLD_MS = 300;
const ONE_WEEK_IN_SECONDS = 86400 * 7;

if (responseTime > SLOW_RESPONSE_THRESHOLD_MS) {
  await sleep(ONE_WEEK_IN_SECONDS * 1000);
}

原則6: エラーは無視しない

// ❌ エラーを握り潰す
try {
  await sendEmail(user.email);
} catch {
  // 何もしない
}

// ❌ console.error だけで終わる(本番ではログが埋もれる)
try {
  await sendEmail(user.email);
} catch (err) {
  console.error(err);
}

// ✅ エラーを適切に処理する
try {
  await sendEmail(user.email);
} catch (err) {
  logger.error("Failed to send welcome email", { userId: user.id, error: err });
  // リトライキューに入れるか、呼び出し元に伝播する
  throw new EmailDeliveryError("Welcome email failed", { cause: err });
}

原則7: DRY よりも読みやすさ

DRY(Don’t Repeat Yourself)は大切だが、無理な抽象化のほうが害になることがある。

// ❌ 無理に共通化して逆に読みにくい
function createEntity(type: "user" | "post" | "comment", data: Record<string, unknown>) {
  const id = generateId(type);
  const timestamp = new Date();
  // typeによって全然違う処理が混在...
}

// ✅ 少し繰り返しがあっても、それぞれ独立して読める
async function createUser(data: CreateUserInput): Promise<User> { ... }
async function createPost(data: CreatePostInput): Promise<Post> { ... }

“Duplication is far cheaper than the wrong abstraction.” — Sandi Metz

まとめ

原則要点
命名意図が伝わる名前。略語・単一文字変数を避ける
関数一つのことだけする。10行以内を目安に
早期リターンガード節で異常系を先に排除、正常系をシンプルに
コメントWhat(何をするか)はコードに。Why(なぜ)をコメントに
定数マジックナンバーに名前をつける
エラー必ず処理する。握り潰さない

参考: Clean Code(Robert C. Martin) / Refactoring(Martin Fowler)