第2回:コードスメルを見つける技術

こんにちは!モジュール分割マスター講座の第2回へようこそ。今日は「コードスメル」について学びます。これはコードの中で「何かおかしい」と感じる部分を見つける技術です。

コードスメルとは何か?

コードスメルとは、プログラムコードの中で「臭い」がする部分のことです。実際に匂いがするわけではありませんが、「このコードは何か問題があるかも」と感じさせる兆候のことを言います。

例えば、お部屋の中で変な臭いがしたら「何か腐ったものがあるのかな?」と探しますよね。コードも同じで、「何かおかしい」と感じたら改善が必要なサインです。

なぜコードスメルを見つけることが大切なの?

コードスメルを放っておくと、だんだんプログラムが:

  • 理解しにくくなる
  • 変更が難しくなる
  • バグが発生しやすくなる

早い段階でコードスメルを見つけて直すことで、これらの問題を防げます。

主なコードスメルとその見つけ方

1. 長すぎるメソッド(ロングメソッド)

特徴

  • 画面をスクロールしないと全部見えないほど長い
  • 多くの場合、20行以上のコードがある
  • 様々な処理が1つの関数に詰め込まれている

function processOrder(order) {
  // 在庫チェック(20行)
  // ...
  
  // 価格計算(30行)
  // ...
  
  // 支払い処理(25行)
  // ...
  
  // メール送信(15行)
  // ...
  
  // データベース更新(20行)
  // ...
}
Code language: JavaScript (javascript)

この関数では、注文処理に関する5つの別々の仕事をしています。それぞれを独立した関数に分けるべきです。

2. 大きすぎるクラス(ゴッドクラス)

特徴

  • たくさんのフィールド(変数)とメソッド(関数)がある
  • クラスの名前が「Manager」「Controller」「Processor」など抽象的
  • 様々な責任を一つのクラスが持っている

class OrderManager {
  // 顧客情報の処理メソッド(10個)
  // 商品情報の処理メソッド(15個)
  // 支払い処理のメソッド(8個)
  // 配送処理のメソッド(12個)
  // レポート作成のメソッド(7個)
  // ...合計100以上のメソッド!
}
Code language: JavaScript (javascript)

このクラスは「注文」だけでなく、顧客、商品、支払い、配送、レポートなど多くの責任を持っています。それぞれを別のクラスに分けるべきです。

3. 重複コード

特徴

  • 同じようなコードが複数の場所に出てくる
  • コピー&ペーストした形跡がある
  • 少しだけ違う似たようなコードがある

// ユーザー検証の関数
function validateUser(user) {
  if (user.name === '') {
    return '名前は必須です';
  }
  if (user.email === '') {
    return 'メールアドレスは必須です';
  }
  if (!user.email.includes('@')) {
    return 'メールアドレスの形式が正しくありません';
  }
  return null;
}

// 商品検証の関数
function validateProduct(product) {
  if (product.name === '') {
    return '商品名は必須です';
  }
  if (product.price === '') {
    return '価格は必須です';
  }
  if (isNaN(product.price)) {
    return '価格は数値でなければなりません';
  }
  return null;
}
Code language: JavaScript (javascript)

この2つの関数は構造がとても似ています。共通のバリデーション関数を作ると良いでしょう。

4. 複雑な条件分岐

特徴

  • if文やswitch文が入れ子になっている
  • 条件式が長く、理解しにくい
  • 同じ変数に対する条件分岐が複数ある

function calculateDiscount(customer, order, date) {
  if (customer.type === 'VIP') {
    if (order.total > 10000) {
      if (date.getDay() === 0 || date.getDay() === 6) {
        return order.total * 0.2;
      } else {
        return order.total * 0.15;
      }
    } else {
      if (date.getDay() === 0 || date.getDay() === 6) {
        return order.total * 0.1;
      } else {
        return order.total * 0.05;
      }
    }
  } else if (customer.type === 'Regular') {
    // さらに続く条件分岐...
  }
}
Code language: JavaScript (javascript)

この関数は読みにくく、新しい割引ルールを追加するのも難しくなっています。戦略パターンなどを使って整理すべきです。

コードスメルを見つけるための実践的な方法

1. コードを声に出して説明してみる

コードの目的を誰かに説明するつもりで声に出してみましょう。

「このコードは、まず顧客情報を取得して、それから商品の在庫をチェックして、それから…」

説明が長くなったり「そして」「それから」がたくさん出てきたら、そのコードは複数の責任を持っているサインです。

2. コメントに注目する

コメントが多いコードは要注意です。特に「なぜそうしているか」ではなく「何をしているか」を説明するコメントがあれば、コードが複雑すぎるサインかもしれません。

// ここからユーザー検証の処理
// emailが正しいか確認
// パスワードが8文字以上か確認
// ...
Code language: JSON / JSON with Comments (json)

このようなコメントは、関数抽出のヒントになります。

3. メトリクスを活用する

コードの複雑さを数値で測定する方法もあります:

  • 循環的複雑度:条件分岐の数を測定
  • 行数:メソッドやクラスの大きさ
  • 依存関係の数:クラスが他のクラスをいくつ使っているか

多くのIDEやコード解析ツールでこれらの数値を確認できます。

実習:サンプルコードからコードスメルを特定する

それでは実際にコードスメルを見つける練習をしましょう。以下のコードにはいくつかのコードスメルが含まれています。どんなコードスメルがあるか考えてみてください。

function processUserData(userData) {
  // ユーザー名の検証
  if (userData.name === '') {
    console.log('ユーザー名が入力されていません');
    return false;
  }
  
  // メールアドレスの検証
  if (userData.email === '') {
    console.log('メールアドレスが入力されていません');
    return false;
  }
  
  if (!userData.email.includes('@')) {
    console.log('メールアドレスの形式が正しくありません');
    return false;
  }
  
  // ユーザーデータの保存
  let userDatabase = [];
  userDatabase.push(userData);
  
  // ウェルカムメールの送信
  console.log('メール送信: ' + userData.email);
  console.log('件名: ようこそ ' + userData.name + ' さん');
  console.log('本文: アカウント登録ありがとうございます');
  
  // ログ出力
  const now = new Date();
  console.log('登録日時: ' + now.toISOString());
  console.log('ユーザー名: ' + userData.name);
  console.log('メールアドレス: ' + userData.email);
  
  return true;
}
Code language: JavaScript (javascript)

発見できるコードスメル

  1. 長すぎるメソッド: この関数は複数の責任を持っています(検証、保存、メール送信、ログ出力)
  2. コメントによる分割: コメントが関数分割のヒントになっています
  3. データベース操作の埋め込み: データベース操作(userDatabase.push)が直接関数内にあります
  4. メール送信ロジックの埋め込み: メール送信のロジックが直接書かれています

改善案

このコードは以下のように分割できます:

  1. validateUserData(userData) – 検証のみを行う関数
  2. saveUserData(userData) – データ保存のみを行う関数
  3. sendWelcomeEmail(userData) – メール送信のみを行う関数
  4. logUserRegistration(userData) – ログ出力のみを行う関数

そして、これらを呼び出す親関数:

function processUserData(userData) {
  if (!validateUserData(userData)) {
    return false;
  }
  
  saveUserData(userData);
  sendWelcomeEmail(userData);
  logUserRegistration(userData);
  
  return true;
}
Code language: JavaScript (javascript)

こうすることで、各関数の責任が明確になり、コードが読みやすく、変更しやすくなります。

まとめ

今日はコードスメルについて学びました:

  • コードスメルとは、コードの問題を示す兆候である
  • 主なコードスメルには、長すぎるメソッド、大きすぎるクラス、重複コード、複雑な条件分岐などがある
  • コードスメルを見つけるには、コードを声に出して説明したり、コメントに注目したり、メトリクスを活用したりする方法がある

コードスメルを見つけることができれば、次のステップである「リファクタリング」(コードの改善)がしやすくなります。これについては次回以降の講義で詳しく学びます。

質問はありますか?

ここまでの内容について質問や疑問があれば、ぜひ聞いてください。次回は「関数レベルのリファクタリング」について学びます。

質問と回答の時間

皆さん、質問をありがとうございます。それでは3人の意欲的な学生さんからの質問に答えていきましょう。

学生A(佐藤さん)の質問

佐藤さん: 「コードスメルを見つけるのは主観的な判断になると思うのですが、チームで開発する場合はどうやって基準を合わせればいいのでしょうか?」

回答: とても良い質問ですね、佐藤さん。コードスメルの判断には確かに主観的な要素があります。チームで基準を合わせるには次のような方法が効果的です:

  1. コーディング規約を作る: チーム内で「関数は〇〇行まで」「クラスは〇〇個のメソッドまで」などの具体的な数値基準を決めておきます。
  2. 定期的なコードレビュー: お互いのコードを定期的に見合うことで、感覚を共有できます。「これはなぜスメルだと思うの?」と話し合う機会を作りましょう。
  3. 静的解析ツールの活用: ESLintやSonarQubeなどのツールを導入すると、機械的にコードスメルを検出できます。ツールの判断なら客観的ですよね。
  4. リファクタリングカタログを共有: チーム内で「このパターンが見つかったらこう直す」という例を集めた資料を作ると良いでしょう。

大切なのは、「完璧なコード」を目指すのではなく、「継続的に改善するプロセス」をチームで共有することです。最初は意見が分かれても、話し合いを重ねるうちに基準は自然と統一されていきますよ。

学生B(田中さん)の質問

田中さん: 「プロジェクトの途中からコードスメルを意識し始めると、直すべきところがたくさん見つかって大変です。どうやって優先順位をつければいいでしょうか?」

回答: 田中さんのご質問は、実践的でとても重要です。確かに既存プロジェクトでは改善点が山積みになりがちです。優先順位づけには次のアプローチがお勧めです:

  1. 頻繁に変更が発生する部分から直す: よく機能追加や修正が発生するコードを最優先しましょう。使われていないコードよりも、毎週変更が入るコードの方が重要です。
  2. バグが発生しやすい部分を重視: エラーログやバグ報告が多い部分は、構造的な問題を抱えている可能性が高いです。
  3. 複雑度の高い部分から取り組む: 循環的複雑度などのメトリクスが特に高い部分は、理解が難しく修正ミスも発生しやすいので優先的に改善しましょう。
  4. 小さな改善から始める: 一度に全てを直そうとせず、「今日は長すぎるメソッドを3つ分割する」など、小さな目標を立てて継続的に改善していきましょう。

私がよく使うのは「ボーイスカウトの原則」です。「来たときよりも美しく去る」というものです。コードを触るたびに少しずつ改善していけば、時間をかけずに全体の質を上げられますよ。

学生C(鈴木さん)の質問

鈴木さん: 「コードスメルを見つけるのは分かりましたが、先輩や上司が書いたコードに対してスメルを指摘するのは失礼になりませんか?どうやって提案すればいいでしょうか?」

回答: 鈴木さん、人間関係に配慮した素晴らしい質問ですね。確かにコードレビューは時に難しい人間関係の問題を含みます。次のようなアプローチが役立つでしょう:

  1. 問題ではなく改善点として伝える: 「このコードは悪い」ではなく「こうするともっと読みやすくなりそうです」というポジティブな言い方をしましょう。
  2. 根拠を示す: 「この関数は80行あって理解しにくいです」など、具体的な事実を基に話すと感情的にならずに済みます。
  3. 質問形式で提案する: 「この部分を分割するメリットについてどう思いますか?」と相手の意見を尊重する形で提案してみましょう。
  4. 自分のコードから始める: まず自分のコードに対して改善提案を歓迎する姿勢を見せることで、チーム全体の雰囲気づくりに貢献できます。
  5. チーム全体のルールとして提案: 個人を名指しせず「今後のプロジェクトでは関数の長さを○○行以内にしましょう」と提案すると受け入れられやすいです。

大切なのは、コードと人間を分けて考えることです。「あなたのコードが悪い」ではなく「このコードをより良くする方法がある」というスタンスで接すれば、建設的な会話ができますよ。

まとめ

皆さん、素晴らしい質問をありがとうございました。コードスメルの発見と改善は技術的なスキルだけでなく、チームワークやコミュニケーションも重要な要素です。日々の開発の中で少しずつ意識して実践していくことで、コード品質とチームの協力関係を同時に高めていけるでしょう。

次回の講義では「関数レベルのリファクタリング」について学びます。今日の質問を踏まえて、実践的な内容をお届けしますので楽しみにしていてください。