Rのつく財団入り口

ITエンジニア関連の様々な話題を書いているはずのブログです。

【感想】『読みやすいコードのガイドライン―持続可能なソフトウェア開発のために』:Kotlinでモダンなコード指南

リーダブルコード的な本が新たに登場っす! (しかもKotlinなの)

 読書記録と感想です。著者の石川宗寿さんはLINEでご活躍中のシニアエンジニア。元は2019年に公開されたプレゼンテーション「Code Readability」を元に書籍化されたとのことで、モダンな大規模開発やコードレビューやリファクタリングで得られた現場の経験を通して、可読性の高いコードを大テーマに知見が詰まった一冊となっています。

読みやすいコードのガイドライン

第1章 可読性の高いコードを書くために

  • 可読性の向上は、生産性の改善に直結する。
    • 持続可能な開発ではコードを書く時間よりも読む時間の方が長い。コードベースの未来のために重要。
    • その場しのぎのHackyなコードを書いた方が開発速度は高くなってしまう。全体の生産性に与えた影響を考慮し、Readableなコードを適用していく。
  • 可読性の高いコードとは...
    • 単純、意図が明確、独立性が高い、構造化されている、が4指標。
    • 一般的なテクニック採用時には採用の目的と条件を明確にする。機能追加で複雑さが増すだけなら追加しない方が良い場合もある。またツールの利用も有用。
  • 代表的な原則として本書では5つ。
    • ボーイスカウトルール:キャンプ場を去る時は以前よりクリーンにして立ち去る。コードも同じ。
    • YAGNI: You ain't gonna need it. 今必要でない機能は実装しない。
    • KISS: Keep it simple stupid。(ちなみにカンマなしが原典らしい) 愚鈍なぐらいシンプルなのが好ましい。自分が書いて気持ち良いコードよりも、他人に読みやすいコードを。
    • 単一責任の原則: クラスが変更される理由はたった一つであるべき。クラスの責務が大きくなったら分割を。
    • 早計な最適化は諸悪の根源: パフォーマンス対応などで最初からコードを複雑化させてはいけない。97%の最適化は無駄と言われる。ただ残り3%の重要なケースも重要。

 第1章は可読性のキホンのところ。よく言われるプログラミングの原則や名言系では、上記の5つが特に上げられています。このあたりはソフトウェアエンジニアリング系の書籍や資料を見てきた方にはお馴染みではないでしょうか。GOODなコード、BADなコードの例もなかなか本格的なものになっており、お役立ちです。
 豆知識としてはKISSってKeep it simple, stupid のカンマありが原文だと思っていたのですがカンマなしだそうですねー。

gihyo.jp

第2章 命名

命名で大事なのは意味と実体が一致している「正確さ」、そして命名から内容が理解できる「説明的」であることだと定義し、諸々の命名について論じていく章。

  • 文法的には、クラスや変数には名詞か名詞句関数には命令文を使う。
    • 名詞では単語の順番が重要、最後に一番大事なことを。例外的に前置詞も使う。
    • 命令文の先頭は動詞、その後に名詞句が来ることも。その他の文法も使い、プログラミング言語ごとに原則が違ったりする。
    • 見た目の順番の統一さよりも、コードが理解されやすいかで決める。
  • 名前はいつ・どこで・どのようにではなく、「何であるか・何をするか」で決める。
    • 関数の引数名も「何をするか」で決める。下手に命名するとその後の拡張時にバグの原因になったりする。
    • 関数の名前もイベントなどでいつ呼ばれるかではなく、「何をするか」で決める。何をするかが未決なこともある抽象メソッドは除外。
  • 単語の選択について。
    • flag,check, old のような曖昧な単語は避ける。それぞれより良い名称がある。
    • 紛らわしい省略語は避ける。逆にTCPやURLなど使った方がよい省略語もある。
    • 単位や実体を表す言葉を追加してわかりやすく。secondsとかpixelsとか。
    • 否定的な単語より肯定的な単語を使う。両方使うのはさらにNG。

 これは分かる...と思いながら読みました。本章の不適切な名前でbool型のisCalledFrom...というのがあるのですが、これはローカル変数なら別にいいんじゃない?と思ってしまいます。しかしこれを関数の引数に取るとどうまずいのか、本書ではしっかり例を挙げて解説しており、読み終わると確かに...となります。
 似たような名詞が並ぶイベントクラスのサブクラス群の命名例でも、つい先頭の単語群を揃えて後半を変えるように命名してしまうのは、これ過去どこかで自分もやってしまったような...と反省しました。
 さすがLINEさんというか、Kotlin製のAndroidネイティブアプリのような、純粋ロジックより若干UI寄りの領域のコード例が多いのが今時っぽくて面白いです。

第3章 コメント

 読みやすいコードには必須、コメントについて深く掘り下げた章。

  • 関数コメントなどのドキュメンテーション、コード中に書く非形式的なコメントの2種類がある。コード理解の加速、実装ミスの防止、またコメントを書くことでリファクタリングのヒントが得られることもある。
  • ドキュメンテーションコメントについて。
    • IDEの自動生成したものを放置、コードの要約でなく同じ情報量しか持たせない、コード内容を自然言語に直しただけ、概要の書き忘れ、詳細を述べ過ぎてしまう、そしてその関数等を誰が呼び出すかに言及してしまう、というアンチパターンに注意。
    • 最初に要約、次に詳細を書く。Javadocでは最初のピリオドまでが要約と判定。KotlinのKDocでは空行が区切になる。文法は標準ライブラリやAPI仕様を参考に。
    • 詳細は基本的な使い方、戻り値の補足、制約や例外的な動作などを書く。
  • コード中の非形式なコメントについて。
    • 関数に分けるほどでもない処理のまとまりについて、要約を書いて区切ったり。
    • 読んでいて直感的に理解できないコードがある場合に、理由を補足コメントすることで理解を助けられる。

 たかがコメントされどコメント、本章ではかなり深く掘り下げています。要約の文法の指針、要約と詳細の良い例/悪い例もここまで詳細に描いた本というのはなかなかないんじゃないでしょうか。
 アンチパターンではEclipseとかが吐き出したままの空のJavaDoc、あ~これよく見てきたやつ! と思いました。
 そしてコードを使う側に言及してしまうというアンチパターン、僕もやってしまった記憶があるのでこれは気を付けないとなと思いました...。「XXからのみ呼ばれる内部関数です」とかつい書いちゃうのは理解の助けになるのか、それともこれもアンチパターンの範疇なのか...?
 そしてコメントを書いていくと関数のリファクタリングのきっかけにもなったりするという話が新たな発見でした。全体から詳細への構造把握になり、よりよい命名を思いついたりしていくわけですね。

第4章 状態

  • 可変なキュー、不変なリストの2通りの実装で示した二分木の例を用いて、可変な変数の方が可読性は高まることもある例。
  • 変数間の直交性。2つの変数で片方の値がもう片方に影響されない場合を「直交」、影響されるのを「非直交」と定義。
    • たとえばスマホゲーム画面でのコインの枚数とそれに伴うテキスト。この2つは互いに結びついているので非直交である。
    • 関数への置き換え:テキストを求める関数を定義、中でコイン枚数を元にその都度テキストを生成する、インスタンス生成時に計算したりファクトリ関数で実現する。コンピューテッドプロパティが使える言語も。
    • 直和型での置き換え:幾つかの型を持ちどれかの値を持つ直和型で表現できる。
  • 状態遷移の設計を工夫してコードを頑丈にしていける。
    • 不変な値を使う。なお「不変」と「読み取り専用」は違うので注意。
    • クラウドでもよく出てくる冪等性。オープン→クローズに状態遷移する関数は、何度呼んでも大丈夫なように。内部状態も隠蔽できるが、関数名などに注意する。
    • 状態が1周回って元に戻る場合がある「巡回」と「非巡回」。可変なオブジェクト設計では「非巡回」が望ましい

 4章はぐっとディープになり、Kotlinの言語仕様の特徴を活かしたコードなんかも出てきてコード例も複雑になります。今はJavaでも直和型が使えるのか...!と意外な発見があったりしました。シチュエーション例が所持コインというのがスマホ時代の今っぽくてイメージが湧きやすいですね。この言語だとこの機能でできる、などのコラム話も充実です。
 内容では最後の巡回と非巡回の話が難しく感じました。ネットの書評でもどこかで見たような気がするのですが、この4章だけ他の章に比べると扱う範囲や深さが違い、この章だけ趣が違う感じがします。

第5章 関数

 関数についても中身を詳細に見なくても動作を予測したい。予測したいのは戻り値、副作用、エラー周り。そして予測しやすいかを確かめるには、関数と名前と動作の一致、名前が具体的か、コメントで要約を簡単に書けるかの3点で判断できる...ということで、可読性の高い関数を掘り下げていく章。

  • 有名な単一責任の原則(Single Responsibility Principle: SRP)はクラスだけでなく関数にも適用できるので、関数の責任も1つにすべきである。
    • コメントで要約が書きにくい関数は、2つ以上の責任を持っている恐れがある。
    • コマンド・クエリ分離の原則(command-query separation: CQS)。状態を変更させるための関数がコマンドで戻り値はなし、状態を知るための関数がクエリで戻り値が情報。この2つは分離するべき。
    • ただCQSを過剰に適用すると分かりにくくなる場合もある。コマンドに分類されるが、戻り値で保存成功を返したりする方が良い場合もある。
  • 関数の流れを明確にすることで概要を理解しやすくなる。本書では定義指向プログラミング、早期リターン、操作対象による分割の3つの手法を紹介。
    • 定義指向プログラミング(Definition based programming)。
    • true/falseを返すプライベートな関数やローカル変数で先に判定して不要なネストを減らす。データの制御構造をそのまま関数に切り出さないように。
    • 関数の引数に関数を呼び出しを書く引数ネストより、メソッドチェインの方が分かりやすい。メソッドチェインでも重要な場所を分割して分かりやすくしたほうがよい。
    • コードに直接表現されたリテラル(直値)、マジックナンバーは変数名をつける。無名関数なども、中身が大きければローカル変数やプロパティ、名前のある関数に置き換えた方がよい。
    • 関数の主な目的を達成できるハッピーパスと達成できない「アンハッピーパスの分岐がある場合は、アンハッピーパスなら「早期リターン」で見やすくなる。ただしswitch文やwhen文の中に早期リターンが埋もれていると分かりにくい場合も。不正パターンは構造体で持たないようにして、アンハッピーパス自体を持たないようにするテクニックもある。
    • 操作対象による分割。例えばアカウントの種類で画面の表示内容を変える処理。
       アカウントの種類で「条件によって」内部関数で分岐していくと、全体の関数を見ただけでは処理内容が分からない、その後の拡張時に完全性が保証できないという問題がある。
      「操作対象(表示対象)によって分割する」方が後々はよい。背景色変更、アイコン変更などの操作対象ごとの内部関数の中でアカウントごとに分岐する。条件を示すクラスに設定する値を持たせて、分岐自体を消すテクニックもある。
    • これらは分岐(列挙子)が網羅されている保証ができる言語が前提だが、そうでない言語でもユニットテストを活用して保証するテクニックがある。

 SRP、CQS、早期リターン、マジックナンバーなどの話はこの手の書籍を学んできた方にはお馴染みではないでしょうか。「ハッピーパス」「アンハッピーパス」という呼び方があるんですね。
Kotlinのサンプルコードもこの辺に来るとぐっと本格的になり、未経験者には若干難しいかなという感じになってきます。badな例のコードですが、こんな複雑な書き方もできるんだ...! と感心したりしました。
 自分的には最近Rust言語を学んでmatch式で分岐漏れをすべてエラーで検知してくれるのがとても便利だと思ったのですが、Kotlinでもwhen式で同じようなことができるんですね。(それどころかJavaでも最新まで進むとできるようになってるのか...)
 内容で言うと「操作対象による分割」のアカウント別に表示内容を変えるコード例が、イメージも沸きやすく面白いです。これもAndroidネイティブアプリを想定したものですね。僕もこの仕様が目の前にあったら、条件によって分岐してアカウント毎の内部関数を追加していくbadな方法を安直にやってしまいそう...と反省しました。(というか、過去どれかの仕事のコードでこういうのやってしまってる気がする...笑)

第6章 依存関係

 この手のテーマの本ではよく語られる依存関連、本書のこの章はかなり長く、なかなか深いところまで踏み込んでいます。

  • 別クラスのインスタンスを持っていたり関数の引数にとっていたり内部のメンバ変数まで触る、継承している、などを「XがYに依存している」と称する。
  • 依存の強さは「結合度」で表される。書籍によって多少の定義の違いがあるが、内容結合>共通結合>外部結合>スタンプ結合>データ結合>メッセージ結合 の順。
    • 内容結合:依存先のコードの詳細まで依存してしまう。現在のプログラミング言語ではあまり発生しない。計算結果が依存先のクラスの内部状態として中に生成されるとか、処理の呼び出し順に制限がある、内部状態が外から更新できてしまうなど。
    • 共通結合と外部結合:誰もが読み書きできる、グローバル変数や可変なSingletonパターン、共有できるファイルやメモリで値を受け渡している。きちんと引数や戻り値で渡す。
    • 制御結合:引数でフラグを渡して、それで関数の動作が大幅に変わってしまう。分岐間で関連が薄かったら別関数にする、操作対象で関数を分割、不要な分岐をなくしたり分岐以外の方法で置き換えるなどの改善策がある。
    • スタンプ結合とデータ結合:引数か戻り値で値を渡し、かつ制御結合でない弱い結合。データ構造を含んでいるとスタンプ結合、基本データ型だけだとデータ結合になる。データ構造で渡すスタンプ結合が適切な場合もある。
    • メッセージ結合:引数でも戻り値でも値を渡さず、単に関数を呼び出すだけ。一番依存度が低い。しかしむりやり引数をなくした結果相手の関数の詳細まで見なければならなくなるパターンもあり、注意が必要。
  • 依存の方向:クラスと矢印の図で整理。巡回しないほうが望ましい。
    • 呼び出し元Callerクラスが呼び出し先Caleeクラスの関数を呼んだら、一方向の依存が発生するがこれは普通。逆方向の依存も発生してしまうとまずい。値で渡したり小さなクラスで抽出したりして対応。
    • 継承の概念がある言語では具体→抽象子クラス→親クラスに依存が発生する。この逆も発生するのは設計がどこかおかしい。
    • 複雑・可変なオブジェクト→単純・不変なオブジェクトへの一方向の依存にする。逆は避ける。例外は、デザインパターンのMediatorパターンの場合。
    • 依存の重複。あるクラス→別のクラス→そのまた別の依存先クラス... と繋がる先が長いと可読性が下がる。途中はやめて直接的な依存にする。有名なデメテルの法則」。ただし無理に適用しても駄目な場合もある。
    • また複数のクラスからの依存先の集合が複数あって重複している場合。後々問題になりやすいので、中間レイヤにクラスをひとつ作ってそこで吸収するとよい。
  • 依存の明示性。一見依存していないように見えるが、インターフェースを介して依存していたり隠れた依存もある。暗黙的な依存関係があると可読性の妨げになる。
    • 2つのクラスがお互いにまったく関係ないのに、ある処理を行うのに共通のインターフェースを使ってしまって読みにくくなる「過度の抽象化」。依存性の注入(DI)でも起こりやすい。
    • 「暗黙的な変域」。関数の引数が自由な文字列と見せかけて色を表す文字列限定で、実は特定の数種類の文字列しか受け付けない悪い例。こういう時は列挙型を活用するとよい。

 サンプルコードもけっこう長くなってきて読み取るのに時間がかかったり、中には理解しきれなかったところもありました。とはいえ悪い例のBADなコードは一見わからないところにうまくだめ具合を作り込んであったり、工夫のほどが伺えます。
 細かいところをいうと依存の重複の例の図、依存先の四角は図の下側でなく上側に配置したほうが分かりやすいかな?と私的には思いました。UML図での継承の子→親、具象→抽象も矢印を指される側をふつうは上に配置することが多いかと思います。

第7章 コードレビュー

 最後はGitHub前提で、主に可読性についてコードレビュー周りについて解説した章。

  • プルリクはレビューしやすく目的を明確に、小さく、コミットが構造的になるように。
    • そのプルリクで達成することの他にしないこと、今後の計画も立てると小さくまとまりやすい。
    • 一週間や一か月かかる巨大な傑作プルリクはつくらず、1日に複数回できる程度に分割。大きい開発ではクラスなどの骨組みを作っていくトップダウン式、小さい部品を先に作って呼び出し側は後にするボトムアップ式の両方を一緒に活用すると良い。
    • 作業中に追加の修正が発生したら、一緒のプルリクでやらずに別に行う。
    • コミットの並び順からプルリクの意図が読み取れるように並ぶのが理想で、構造的化する。
  • レビューされる側はレビューコメントの適用に注意する。
    • 偉い先輩などからのコメントでも常に正しいとは限らない。またコード内に補足コメントを書いたりロジックの改良で解決できるするケースもある。
    • なぜそのコメントが来たのか意図の理解に努める。何も考えずに適用するのはNG。
    • 指摘は他の部分にも横展開できないか確認する。
  • レビュアー側も、互いの尊重が大前提。暴言や人格否定はぜったいダメ。レビューとは相手の人格とは分離された作業のプロセスである。
    • レビュー依頼は放置しない。X時間以内に返答などルールを決めると良い。忙しくてできなさそうならまずはそこで一報。
    • でかすぎるプルリクなどは受けずに拒否して、フォローしながら作り直してもらう。
    • 忙しかったり締切間際でもレビュー品質を下げるのは問題。緊急対応で間に合せのコードでやる場合はその情報を残しておく。
    • 提案以外のコメントで、相手の成長を促す意図で問題を提起したり確認を依頼したり、詳細に手法を提示したり分ける。
  • 静的解析ツールで任せられるところは任せ、そうでないところを人間でコメント。
    • 実例として、条件付きでエラーダイアログを出す実装で実は処理を行う場所を改善すべきパターンだった場合の例。

 コードを扱った書籍でコードレビューの話も併せて一緒に書いてある本はあまり見ない気がするので、これは面白い着眼点ですね。完全にGitHub前提で書いてあるのが今風です。なるべくプルリクごとのブランチも小さく、個別にやっていこうということでブランチの図も一緒に書いてあります。レビュー依頼を放置するなとか、LINEさんの社内でもこういうのはどこでもあるのだなあと思いながら読みました。

まとめ:Kotlinを題材にした2020年代のモダンなコード指南の書

 370ページぐらいとそんなに長くはなく、文章も詰まっていますがそんなに時間はかからずに読むことができました。この手のテーマの本というと歴史的経緯からコード例は言語はキホンのJavaで書いてあることが多いのですが、Kotlinというのも今っぽくあり、面白いですね。流石のLINEさんらしくAndroidアプリを想定したUI寄りの部分ぽいコード例もけっこうあるのが面白いです。また題材はKotlinといっても他の言語の言及も多く、総合的に学びになります。エンジニアリングに関する書籍の言及も多いです。表面的なテクニックではなく、こういうシチュエーションで使う/使わないまで踏み込んで語ってあるのも良いですね。
 なお本書の末尾には付録としてKotlinの文法サマリも5ページぐらい載っていますが、サンプルコードはけっこうKotlin固有の機能まで活用したもの、BADな点がディープに隠されたコード例もあり、Kotlin完全未経験者やプログラミング学習中ぐらいの方にはやや辛いかな?とも思いました。僕の場合は本書を機に、もっとKotlinの言語仕様を知っておこうかなという気になりました。
後述の『現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法』の作者の増田さんの書評にもあったのですが、著名な『リーダブルコード』を初級向けとすると本書はもうちょっと先の中級以上向け、既に現場でコードを書いているエンジニアでもっとレベルアップしたい方あたりが主なターゲットなのかなと思いました。

 こうしたリーダーブルなコードやコード設計が語られている本は幾つかあり、それぞれ微妙に内容が重複していたり、違うことも言っていたり、主張のベクトルとでもいうべき方向が少しづつ違っていたりします。どれか1冊読んで終わりという訳でもなく、どの本が良い悪いというわけでもなく、読めば読むほど血肉になって力になるぞいというが僕の考えです。おそらく経験あるエンジニアの方ならみんな同意見だと思います。Kotlinが普段使いの方もそうでない方も、読んでおくと学びになるのではないでしょうか。

読みやすいコードのガイドライン

おまけ リンクと関連書籍

P232に1か所コードの誤字が発表されています。

 作者の石川宗寿さんへのインタビュー記事。ディベロッパーエクスペリエンス開発チーム所属ということで、やっぱりこのへんの領域をがっつりやられてるんですね。

engineering.linecorp.com

 元の発表資料「Code Readability」。期待して見たらオール英語でした...!

と思いきやLINE Engineering Blogで日本語で内容の連載解説記事があったので安心です。

engineering.linecorp.com

類書の『良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方』の作者のミノ駆動さん、『現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法』の作者の増田亨さんからも書評が上がっていました。 note.com masuda220.hatenablog.com

読みやすいコードの話が言及されている本を並べてみました。

オライリーの薄めの本『リーダブルコード』はもはやクラシック。みんなもう読んでるよね?という感がありますが定番中の定番。本書よりも基本的な内容が多くなっています。

『プリンシプル オブ プログラミング』は詳細なコード例まである訳ではないですが、本書でもよく言及されているプログラミングの原理・原則系も多く載っているサマリ本です。

Bobおじさんの熱い主張が毎回楽しいCleanシリーズでもコードの話はガッツリ語られています。主には『Clean Code』『Clean Architecture』でしょうか。本書第6章「依存関係」と関係する話も、依存するな! 依存は常に一方向にしろ!とアツく主張されています。

プログラマが知るべき97のこと』もエッセイ式ですがプログラミングにまつわるいろんな話が載っています。

名著では超定番、『達人プログラマーでもコードの話はいろいろ載っていますね。

達人プログラマーに比べるとあまり話題に上がらないのですが、『Code Complete』もコードを書く際の話はかなり深く書いてあってとても学びになった記憶があります。

日本の本では、『良いコードを書く技術』は『リーダブルコード』と比較されることが多いのですが、増補改訂版も出ています。

2023ITエンジニア本大賞の技術書ベスト10にノミネートされた『良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方』もおすすめ。

若干古めですが『現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法』ドメイン駆動設計寄りの観点で、コードの話もいろいろ載っています。

最近の本といえば『ちょうぜつソフトウェア設計入門 ――PHPで理解するオブジェクト指向の活用』も外せませんね。

 ちなみにこの記事のアイキャッチ画像は、Kotlin言語提供のJetBrains社がコードに最適なフリーフォントとして無料配布している JetBrains Mono フォントで作りました。Kotlinのロゴに使われているのは JetBrains Sans という別フォントで、こちらは配布されていないようです。

www.jetbrains.com