💭

言語化できない人がコードの可読性について考えてみた話

2024/11/17に公開

はじめに

私は自分の考えを構造化して話すのにとても時間がかかる人です。
恥ずかしながら、考えが片付けてないまま話してしまうと、話したい話がたくさん混ぜて喋って何を言っているかわからなくなることがとても多いです。

現在私たちのチームでは品質管理のためSonarQubeというツールを導入しております!
ここから指摘されたコードを見てまるで私の話し方と同じで反省をたくさんし、もっと伝えやすい話をしたい、もっと読みやすいコードを書きたい気持ちになりました。そのためにどうすれば良いかを考えた内容を記録したいなと思いこの記事を書きます。
もちろん個人的な考えなので正解ではありません!

SonarQubeとは?

SonarQubeは、コードの品質を自動的にチェックしてくれるツールです。
主に以下のような項目を分析してくれます:

  • テストカバレッジ(コードがどれだけテストされているか)
  • コードの重複
  • バグの可能性がある部分
  • コードの複雑度

つまり、私たちのコードの「健康診断」をしてくれるようなものです。
今回は特に「コードの複雑さ」に注目して、どうすれば読みやすいコードが書けるのかについて考えてみました。

SonarQube Documentation

コード複雑度(循環的複雑度 vs 認知的複雑度)

私がSonarQubeを使って開発をしていると、よく目にする警告がありました:

CopyCognitive Complexity of functions should not be too high

「この関数の認知的複雑度が高すぎます!」というメッセージです。
最初は「これどのような基準だろう..」と気になりました。
しかし、この警告を理解しようと調べていくうちに、とても興味深い発見がありました。
実は、コードの複雑度を測る方法は2種類あります:

循環的複雑度:従来からある指標
認知的複雑度:より人間の理解しやすさに注目した新しい指標

この2つ、具体的にどう違うのか、もっと簡単に比べてみましょう!

循環的複雑度では分岐パスの数だけを測るので、本当にコードの複雑さを正しく表現できているのか疑問があります。
以下の二つの例を見てみましょう。

例1ネストが深いパターン

循環的複雑度: 4 (if x3 + 最後のreturn +1)
認知的複雑度: 6 (nestされる追加で+される)

function validateUserInput(age: number, income: number, creditScore: number) {
  if (age >= 18) { // +1
    if (income >= 50000) { //+2
      if (creditScore >= 700) { //+3
        return false;
      }
    }
  }
  return true; 
}

例2シンプルなパターン

循環的複雑度: 4

function validateUserInput(age: number, income: number, creditScore: number) {
  if (age < 18) return false;
  if (income < 50000) return false;
  if (creditScore < 700) return false;
  return true;
}

面白いことに、この二つのコードは循環的複雑度が同じ「4」になります。
しかし、上のコードはifの中にifがネストされており、人間がより多くの脳の処理能力を使って理解しなければならないコードです。
そのため、SonarQubeでは循環的複雑度より認知的複雑度を重視しようという考え方を採用しています。
これを見て気づいたのですが、認知的複雑度が高いコードは、まるで私の話し方にとても似ているということです!
もっとシンプルに説明できるトピックなのに、話したい内容や知っている内容が混ざってしまい、どんどんネストが深くなっていきます。
時には「すみません、何の話でしたっけ?」と聞き返されることもあります!とてもはずかしいです。
この経験から学んだのは、構造化された表現の重要性です。
自分の話し方にこのような傾向があるからこそ、きっとコードにも同じ傾向が出てしまうのではないか心配をしていました。
そう気づいてからは、「誰が読んでも理解しやすいコードを書こう!」という新しい目標ができました。

変数名で話したい内容を説明しよう

コードの複雑さを減らすことも大切ですが、それ以外にも読みやすいコードを書くためのポイントがあると思います。
その中で重要だと思うのが変数名でヒントをあげるということです。
人と会話する時、「今から〜について話します」と前置きすることで、聞き手は内容を理解しやすくなります。
同じように、コードでも「これから〜をします」ということを変数名で明確に伝えることで、コードの意図がより伝わりやすくなります。
コードで見てみましょう:

例1意図が見えにくいコード

const data = [
  { name: 'Beer', temp: 3, date: '2024-03-01 14:00' },
  { name: 'Beer', temp: 4, date: '2024-03-01 15:30' },
  { name: 'Beer', temp: 5, date: '2024-03-01 16:45' },
  { name: 'Beer', temp: 6, date: '2024-03-01 17:20' },
];

const result = data
  .filter((x) => x.temp <= 5)
  .sort((a, b) => new Date(a.date) - new Date(b.date));

例2意図が明確なコード

const coldEnoughBeers = data.filter((beer) => beer.temp <= 5);
const prioritizedBeersToServe = coldEnoughBeers.sort(
  (a, b) => new Date(a.date) - new Date(b.date)
);

このコードは私の居酒屋でのアルバイト経験から作った例です。
瓶ビールを提供する時は、先に冷蔵庫に入れたものから出す(First In First Out)のが鉄則でした。
例1のコードも機能的には問題ありませんし簡単なので、複雑度も低いです。
しかし、例2では変数名を見るだけで:

coldEnoughBeers → 適切な温度のビールを選別している
prioritizedBeersToServe → サービスの優先順位をつけている

というコードの意図が明確に伝わってくるのではないかなと思いました。

変数名は詳しいほど良いか

最後に上で説明したよう、変数名でHintをあげるとしたらなるべく具体的に書くべきかについて考えました。私は個人的に違うと思います。「人間が読みやすい」コードが私の目標なので、長い文章で説明するより、簡単な文章がもっと理解しやすい場合があります。
具体的な例を見てみましょう:

// 👎 過度に具体的な変数名
const CONVERSION_RATE_YEN_TO_DOLLAR_FOR_CURRENCY_EXCHANGE = 150;

function convertJapaneseYenToUnitedStatesDollarForPayment(yenAmount) {
    return yenAmount / CONVERSION_RATE_YEN_TO_DOLLAR_FOR_CURRENCY_EXCHANGE;
}

// 👍 適切な長さの変数名
const YEN_USD_RATE = 150;  

function convertYenToUSD(yen) {  
    return yen / YEN_USD_RATE;
}

い変数名を見て、あなたは本当に「分かりやすい」と感じましたか?
私は逆に「読みづらい」と感じました。
それは、まるで次のような会話の違いに似ています:
👎「日本円からアメリカドルへの通貨換算レートを使用して金額を計算する場合...」

👍「円ドルレートで計算すると...」
必要な情報は全て含まれていますが、長すぎる説明は却って理解の妨げになることがあります。
これからも「どうすれば誰にとっても分かりやすいコードになるか」を考えながら、変数名の付け方を工夫していきたいと思います。

最後に

SonarQubeを使って開発する中で気づいたことを書いてみましたが、これはあくまでも私の個人的な経験と考えに基づいています。
「こんな書き方をしたらコードが読みやすくなった!」
「違う視点からのアプローチを試してみては?」
など、みなさんの経験や考えもぜひ教えていただけたら嬉しいです。
長い記事を読んでいただき、ありがとうございました。
少しでも参考になる部分があればとても幸いです。😊

Discussion