読みやすいコードは「読ませない」

2024/10/11に公開
6

経験の浅い人にちょくちょくするアドバイスとして、「コードリーディングのときにはあんまコードを読まないほうがいいよ」がある。コード全体を詳細に読むのではなく、名前やインターフェイスからコードの意図を把握することで効率的にコードリーディングできる。完全に下記の受け売り。

「実装は極力見ないようにして、インターフェイスと構造を理解するようにするんです。ダイヤグラムや、関係のグラフを書いたりして。実装はちゃんと出来ていると信じて、読んでいるメソッドやクラスのインターフェイスの役割やパラメータをしっかり理解するようにするんです。そっちの方が、実装を見るよりずっと楽ですよね。」

牛尾 剛「コードリーディングのコツは極力読まないこと

自分なんかは、エディタの畳み込み機能と変数名ホバーを使って、名前とインターフェイスしか見えない状態で読む。中身を読みたいなーと思ったところは畳み込みを解除して徐々に読んでいく。ちゃんと書かれてたらそれで大体理解できる。

逆に言うと、名前とインターフェイスを見てピンとこないものは中身の実装を読まなきゃいけない。言ってしまえば「読まされる」感じ。読まされるときは、「読みにくいなー」とか「ここ難しいとこなんやなー」とか「もうちょい別の書き方あるんじゃないのー」とか思いながら読んでる。

何が言いたいかというと、これはコードを書くときにも使える知識なのではないか、ということ。「中身を読まなくていい」が読みやすいコードを書けてるかどうかの指針の1つになるのではないだろうか。

つまり、読みやすいコードは読み手に極力「読ませない」コードなのではないか。

余計にコードを読ませないために気をつけるポイント

読み手に「読ませない」コードを書けるようになれば、読みやすいコードが書けることになる。そうだとして、「読ませない」コードを書くためのポイントを考えてみる。

名前

まず、名前の威力は大きい。適切な名前がつけられていれば、その名前と「=」で結ばれた先にあるn行のコードの内容が一撃で掌握できることになる。「読ませない」ために重要なポイントだ。

逆に言うと、名前を見ても中身が伝わらなかったり、「なんかズレてそうな命名だな」と思ったりしたら、そこは「臭う」。設計についてレビューすべきところは大体そういうところだ。

これはつまりこういうことなのではないかと思います。適切な名前をつけられると言うことは、その機能が正しく理解されて、設計されているということで、逆にふさわしい名前がつけられないということは、その機能が果たすべき役割を設計者自身も十分理解できていないということなのではないでしょうか。個人的には適切な名前をつけることができた機能については、その設計の8割が完成したと考えても言い過ぎでないことが多いように思います。

Matz「名前重要

命名の重要性については下記が詳しくておすすめ。

https://r-west.hatenablog.com/entry/20090510/1241962864

「正しい名前」とは、それが何なのかを、そのスコープにおいて的確に表現している名前のこと。「それは何?」を一言で言うには、その名前以外に言い様がないような名前。

インターフェイス

関数のインターフェイスは名前とセットで見ている。インターフェイスが名前と噛み合ってたら書き手を信頼して中身を追わない。例えばuploadFile関数のインターフェイスが (file: File) ⇒ { url: string } だったら、「あー、ファイルをアップロードしてそのURLが返ってくるんだろうなー」として一旦は中身を読むのをスキップする。

また、処理の流れもインターフェイス(というか、なんのデータが入ってなんのデータが出てくるか)でイメージすることが多い。

例えば下記のような処理があったとしたら、

  1. idから従業員の詳細情報を取得
  2. 従業員情報からプロフィール情報作成
  3. プロフィールからメッセージ生成

「大体こんな感じかなー」と考える

  1. (id: string) ⇒ Employee
  2. (employee: Employee) ⇒ Profile
  3. (profile: Profile) ⇒ string

この感じで眺めて、関数の区切り・組み立てが納得の行く感じだったら心の中で書き手と握手する。そうじゃなかったら、書き手とのさらなる対話を求めて中身を読みに行く(?)。

テストケース

「こういう場合にどう振る舞うの」が知りたいときに、テストが書いてあると中身を読まなくて済むことがある。

例えば、isXXX(yyy) みたいな関数で「こういう場合にtrue、こういう場合にfalse」のテストが書かれていてそれが通っていれば、信頼して中身を読まなくて済む。中身の判定ロジックが込み入っている場合には特に威力が大きい。

まあでも、そもそもisXXXの命名とインターフェイスがしっくりきていればもう中身は読まないかも。

コメント

どうしても認知負荷が高いコードを書くときに、自然言語で振る舞いを説明して読み手をアシストすることがある。処理の概要を把握するのに役立つ。

ただ、コメントだけを信頼して読み飛ばすということはあまりない。名前とインターフェイスがしっかりしていてこそ、「ちゃんと設計されているな」と安心して詳細をスキップすることができる。

コメントは、「コード外にある情報を探しに行く」のをスキップするために書けるといいと思う。文脈を伝える資料のURLとか、「MEMO: ここイマイチな気がする…」みたいな書き手の気持ちとか。名前とインターフェイスがしっかりしているうえで、それだけでは伝わりきらない情報を書く。

逆に、コードから自明なことは余計なので極力書かない。自然言語のコメントは読みやすいだけについつい「読ませちゃう」ところがあるので気をつける。

読ませないコードを書くための術

「読ませないコードを書くためのより具体的なノウハウを教えて!」と言われたら、とりあえず次の3つを紹介する。

処理の流れを自然言語で書く

いきなりコードを書き始めない。まずは自然言語で処理の流れを説明してみる。こうすると、「どう動くか」ではなくて「何が起こるか」という観点で整理できる。一行で説明できるくらいの意味のまとまりで処理を考えられる。

// Employee IDをもとに詳細情報をサーバー取得

// バリデーション

// EmployeeからProfileオブジェクトの作成

// Profileからメッセージを生成

// メッセージを出力

これでもう、処理の概形ができる。最近はAIの支援も受けられる。

// Employee IDをもとに詳細情報をサーバー取得
const fetchEmployeeResult = await fetchEmployeeById(...)

if(isErr(fetchEmployeeResult)) {
  // エラー処理
}

// バリデーション
const validateEmployeeResult = validateEmployee(fetchEmployeeResult.val)

if(isErr(validateEmployeeResult)) {
  // エラー処理
}

// EmployeeからProfileオブジェクトの作成
const profile = createEmployeeProfile(validateEmployeeResult.val)

// Profileからメッセージを生成
const message = generateMessageFromProfile(profile)

// メッセージを出力
console.log(message)

あとは個別の関数について中身を書いていくだけ(コメントは余計だと思ったら消す)。

TDD

TDDをやると「中身がどう動くか」ではなくて「振る舞いはどんなか」を中心に考えることになるし、いやでも名前・インターフェイス・テストケースに意識が向く。

処理の流れを自然言語で書いて全体観を得たあとに、個別の関数についてTDDで整理していくといいと思う。

TDDについては、自分はt_wadaさんの資料を色々追いかけて学んだ。自分の周りの人も大体そう。

https://t-wada.hatenablog.jp/entry/canon-tdd-by-kent-beck

関数型プログラミングのスタイル

関数型プログラミングのスタイルについては自分も序の口しか学べてないけど、それでも「式の組み立てでプログラムを構成していく」という世界観に触れられたのは良かったなと思う。名前やインターフェイス、意味のまとまりや処理の流れについて考えるきっかけをもらえた。

おわりに

プログラムを書き始めの頃にこれがわかってたら、コードを俯瞰的に読み書きできていくらか楽だったかもしれない。他にもポイントがあれば教えてください。

株式会社ゆめみ

Discussion

もりたもりた

めっちゃ刺さりました!
ありがとうございます!!!

だめぽだめぽ

関数型プログラミングに関して、挙げられている記事は詳しい人から見るとワードサラダに見えるトンデモな代物なので、リンクを削除されることをお勧めします。例えば「タイプコンストラクタ」をおかしな意味で使っているので、知らない人が読むと誤解を植え付けられる可能性があります。

件の記事についての私の感想を https://qiita.com/mod_poppo/items/a6317294ba1e39b1c3b3 に書いているので、あの記事がどういう代物なのか判断する参考にしてください(コメント欄も見ていただけると、件の記事の著者の人となりのヒントが得られるかもしれません)。

それ以外の部分は非常に共感できる内容でした。

hajimismhajimism

親切なご案内ありがとうございます。共有いただいた記事も大変参考になりました。だめぽさん以外からも同様のコメントが複数寄せられたため、読者の方に文脈が共有できるよう、該当箇所に注釈を加えました。私自身、内容についてきちんと吟味できるよう、同分野についての学習に取り組もうと思います。

だめぽだめぽ

私は「削除」と言いました。「脚注を追加する」だけでは見落とす読者もいそうですし、トンデモへの言及の正当化としては不十分だと思います。私の言葉が足りなかったようです。

株式会社ゆめみさんは多数の技術記事を発信なさっているようですが、社内に関数型プログラミングがわかる人材はおられないのでしょうか?もし本当に削除されないのであれば、「株式会社ゆめみ」への私の中での評価を下げざるを得ません。

だめぽだめぽ

ご対応ありがとうございます。

強い言葉を使ってしまったお詫びと言っては何ですが、関数型プログラミングの真っ当な教材(本)をいくつか紹介しておきます。手持ちからの紹介なので、使用する言語などに偏りがあるかもしれないことは断っておきます。

  • プログラミングHaskell 第2版: Haskellで関数型プログラミングに入門する本です。前半は純粋な関数型プログラミングなので、いわゆる「関数型プログラミング」で想像するものに近いでしょう。
  • すごいHaskellたのしく学ぼう!: これもHaskellの入門書です。
  • 純粋関数型データ構造: 関数型プログラミングでは基本的に破壊的更新を避けます。これをデータ構造にも当てはめて、破壊的更新を行わずにデータ構造を構成するにはどうするか、というような本です。サンプルコードはStandard MLとHaskellで提供されています。
  • プログラミング言語Standard ML入門: Standard MLの入門書です。Standard MLはHaskellほど純粋ではないので、破壊的更新等の副作用を使ったプログラミングも扱っています。