ArrayListについて

今回は JavaArrayList に焦点を当ててみたいと思います。
ArrayList は配列のようなものですが、配列とは異なり、初期化時に長さを指定しなくても良い、後から自由に長さを変更できる便利なクラスです。
 
まず、配列の例を見てみましょう。

String array1[] = new String[2]; // 要素数2の配列を宣言
array1[0] = "りんご";
array1[1] = "いちご";
array1[2] = "みかん"; // 3個目の要素は設定できない(例外が発生する)

 
そして、ArrayList の例を見てみましょう。

List array2 = new ArrayList(); // 宣言時に要素数の指定は不要
array2.add("りんご");
array2.add("いちご");
array2.add("みかん"); // 好きなだけ追加可能

あらかじめ扱う要素数が決まっている場合は配列でも十分ですが、要素数が不明確な場合、ArrayList は非常に便利です。
実際の実務では、多くの場面で「要素数が不明確な場合」がほとんどです。
 
ただし、ArrayList をそのまま使用すると、いくつかの不便な点があります。
例えば、以下のコードはコンパイルは正常に終了しますが、実行時に
 「(String) array1.get(0)」
の箇所で例外が発生します。

List array3 = new ArrayList();
array3.add(1);  // 文字列ではなく、数値型を設定してしまった
String strItem = (String)array3.get(0);

 
実行時のエラーメッセージは次の通りです。

java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String

サンプルコードの記述ではArrayListの引数はObject型となっています。
そのため、どんな型でもaddできてしまいます。
今回の例では、文字列型の「"1"」をaddするつもりで、誤って数値型の「1」をaddしてしまいました。
しかし、引数の型はObject型なので、エラーにはなりません。
その後、getする際に文字列型に型変換を行っているため、実行時例外となってしまいました。
 
この不具合の原因の特定は意外と大変です。
get の部分で例外が発生していますが、真の原因は add している箇所にあります。
今回のサンプルはたった4行しかありませんが、コードが複雑になるにつれ、add する部分と get する部分は離れていき、原因特定が難しくなります。
 
そこで、「ジェネリクス(Generics)」の登場です。
ジェネリクスArrayList 専用の機能ではなく、もっと汎用的な概念ですが、今回は ArrayList の使い方を改善する方法に焦点を当てて説明します。

List<型> 変数名 = new ArrayList<型>();

List<型> 変数名 = new ArrayList<>(); // java 7以降は省略が可能

ジェネリクスを使用した ArrayList の宣言方法はこのようになります。
クラスの後に"<"と">"で囲み、型を指定できます。
この型指定部分は「型パラメータ」と呼ばれます。
型パラメータを指定することで、ArrayList で扱う要素の型を特定できます。
 
これによってどう変わるのか、先ほどの例で説明します。

List<String> array4 = new ArrayList<>();
array4.add(1);  // String 型の引数のみを受け付ける

上記の例では array4 は「String を扱う ArrayList」として宣言され、その結果、 array4.add(1) はビルド時にエラーとなります。
 
エラーメッセージ

型 ArrayList<String> のメソッド add(int, String) は引数 (int) に適用できません

これにより、コーディングミスを早期に発見できます。
また、型パラメータを指定しない場合、get メソッドの戻り値は Object 型となり、キャストが必要です。
しかし、型パラメータを指定すると、戻り値も指定した型となり、型変換が不要になります。(地味に便利です)
 
型パラメータ指定あり

List<String> array5 = new ArrayList<>();
array5.add("1");
String strGenItem = array5.get(0); // getの戻り値はString型になる

型パラメータ指定なし

List array6 = new ArrayList();
array6.add("1");
// getの戻り値はObject型なのでStringにキャストが必要
String strItem = (String)array6.get(0);

型パラメータを指定するということはコードの簡潔さと可読性を向上させます。
ArrayList を含むコレクションクラスを使用する際には、型パラメータを活用してコードの安全性、利便性、保守性を向上させましょう。

Java開発者のためのJavadocハンドブック

プログラムのコードを書くのは一つのステップですが、そのコードが他者に理解され、効果的に使用されるためには、適切な説明が不可欠です。
JavaプログラムにはJavadocというコードを文書化するためのツールがあります。
本記事では、Javadocの重要性や利点、使用方法について記載していきます。
 

Javadoc(ジャバドック)とは?

Javadocは、Javaプログラムのコードにコメントを追加するツールです。
メソッド、クラス、およびフィールドにコメントを追加することで、そのコードの目的や機能を明確に説明します。
これにより、他の開発者がコードを理解しやすくなり、プロジェクトで一緒に作業するのも楽になります。
また、Javadocコマンドを使用することで、追加したコメントからAPI文書を自動的に生成し、使用方法を提供することができます。
※今回はJavadocコマンドの説明は含みません。

公式:JavaDoc Guide(JavaSE/21)
docs.oracle.com  

Javadocの基本

Javadocの基本は以下の通りです。

  • /** ~ */ の間に説明を記載
  • クラス、定数、メソッドがコメントを記載する対象
  • パラメータや返却値、作成者などの情報を@(アノテーション)を使用し、表現することができる
  • コメントのフォーマットでHTMLタグを使用する
  • その他にも便利なJavadocタグが存在する

下記がJavadocを記載したソースの例です。

/**
 * Javadocのサンプルクラス
 * @author 作成者
 * @version 1.0
 */
public class JavadocSample {
    
    /** 半角スペース */
    private String SPACE_HALF = " ";
    
    /**
    * 社員IDと社員名から、社員IDと苗字を結合したものを返却<br>
    * 社員名がnullの場合は空文字を返却
    *
    * @param empId 社員ID
    * @param empNm 社員名(半角スペースで結合された苗字と名前)
    * @return 社員IDと苗字を結合した文字列、または空文字
    */
    public String joinEmpIdWithEmpLastName(int empId, String empNm) {
        if(empNm == null) {
            return "";
        }
        String[] empNmArray = empNm.split(SPACE_HALF);
        return empId + (empNmArray.length > 0 ? empNmArray[0] : "");
    }
}

 

Javadocの重要性

Javadocはチーム開発を行う上で非常に強力なツールです。
クラスやメソッドの使用方法が記載されたJavadocの有無で、開発や保守のスピードが大幅に変わります。

具体的には以下の例で説明します。

    // Javadocあり
    /**
    * 契約書の条件と指定された住所コードから、条件に合う社員の一覧を返却
    * <ul>
    * <li>・契約Noから契約テーブルを検索し、該当の契約データを取得</li>
    * <li>・契約データに記載している条件で、入力済みのものを抽出</li>
    * <li>・都道府県コードと契約データの検索条件を基に、該当の社員一覧を取得返却する</li>
    * </ul>
    * 
    * @param contractNum 契約番号
    * @param addressCd 住所コード
    * @return マッチング対象の一覧
    */
    public List<MEmployee> selectMatchingEmployeeList(String contractNum, String addressCd) {
        // 契約情報を取得
        // 契約情報から条件を抽出
        // 社員情報を返却
    }


    // Javadocなし
    public List<MEmployee> selectMatchingEmployeeList(String contractNum, String addressCd) {
        // 契約情報を取得
        // 契約情報から条件を抽出
        // 社員情報を返却
    }

Javadocがある場合、引数に対する返却値が一目でわかります。

Javadocがない場合、引数が理解しにくい場合があり、呼び出し元も含めてソースコードを順に見ていかなければなりません。
さらに、このメソッドが別の処理を呼び出し、その処理がさらに別の処理を呼び出すと、一つのメソッドを理解するのが難しくなります。

上記の例を見るだけで、Javadocの記載が開発や保守にとっていかに重要かが理解できると思います。

Javadocの記載方法

Javadocには記載のルールがあります。
記載する際に気を付けなければいけないのは、他の開発者が見て理解できるように書くということです。
ここからは実際に開発の現場でよく見かけるJavadocの例をご紹介したいと思います。

クラスの説明:
/**
 * Javadocのサンプルクラス<br>
 * Javadocの理解を深め、成長につなげる
 * 
 * @author 作成者
 * @version 1.0
 */
public class JavadocSample {
}

まずクラスのJavadocでは初めにクラスの目的を記載します。
クラスの役割や目的を簡潔に書くのがポイントです。

 * Javadocのサンプルクラス<br>
 * Javadocの理解を深め、成長につなげる

次に作成者やバージョンを記載します。
作成者やバージョンは、プロジェクトによっては記載しない場合もあります。

 * @author 作成者
 * @version 1.0
メソッドの説明:
   /**
    * メソッドの概要<br>
    * どんな処理をするのか記載
    *
    * @param num どんな値
    * @param str どんな値
    * @return 何を返却するのか
    */
    public String javadocSample(int num, String str) {
        return num + str;
    }

まずクラスのJavadocではクラスの目的を記載します。
クラスの役割や目的を簡潔に書くのがポイントです。

 * Javadocのサンプルクラス<br>
 * Javadocの理解を深め、成長につなげる

次に製作者、バージョンを記載します。
この部分は記載が無いする現場も存在します。

 * @author 作成者
 * @version 1.0
定数の説明:
   /**
    * ほげほげ
    */
    private String HOGE_HOGE = "";

    /** ほげほげ */
    private String HOGE_HOGE = "";

定数名については上記の通り、値の説明を記載します。

HTMLタグ:

Javadocで記載したコメントが長くなり、見づらくなることはありませんか?

   /**
    * この処理はJavadocの説明用のメソッドです。
    * どうしても処理を書きたいので記載しています
    * 1.処理は特に記載していない
    * 2.処理が存在するメソッドをイメージしてもらいたい
    */
    public void javadocSample() {
    }

このようなコメントが書かれている場合、EclipseIDE)などでは次のように表示されます。

この表示ではどこで区切りがあるのか、どの部分が実際の処理を表しているのかが一目でわかりません。
このような場合、Javadoc内でHTMLタグを使用してフォーマットを行うことができます。

   /**
    * この処理はJavadocの説明用のメソッドです。<br>
    * どうしても処理を書きたいので記載しています
    * <ol>
    * <li>処理は特に記載していない</li>
    * <li>処理が存在するメソッドをイメージしてもらいたい</li>
    * </ol>
    */
    public void javadocSample() {
    }

フォーマット後の表示は以下の通りです。

このようにすることで処理の内容がわかりやすくなります。
JavadocではHTMLタグを使用してフォーマットを行うことができ、その中でもよく使用されるものには次のものがあります。

  • <br>タグ・・・改行
  • <p>タグ・・・段落を付けたい
  • <ul><ol><li>タグ・・・箇条書き、番号付きリスト
  • <b><strong>タグ・・・強調

その他に<a>タグなどもありますが、あまり使用されることはありません。
<html>や<head>などは使用するとJavadocの構造が壊れる可能性があるので注意が必要です。

Javadocタグ:

HTMLタグとは別に、Javadoc内で使用できるタグも存在します。
よく使用されるタグとその内容は以下の通りです。

  • @paramタグ:引数の説明
@param 引数 引数の説明

メソッドの引数に関する説明を記述します。
引数の順番通りにすべての引数について説明します。
引数が何を表しているかを説明する際、単に英単語を翻訳するのではなく、その意味を説明します。

  • @returnタグ:返却値の説明
@return 返却値

返却される値に関する説明を記述します。
たとえば、booleanの場合は「true: チェック成功、false: チェックエラー」といった書き方をすることで、返却値の状態も伝わります。
詳細でわかりやすい説明が大切です。

  • @throwsタグ:例外の説明
@throws Exception Exceptionの説明

メソッドがthrowする例外をすべて記述します。 例外にラッパークラスを指定している場合、例外の説明まで記述するかはプロジェクトによって異なります。

その他にも、@author(開発者、作成者)、@version(バージョン)、@see(関連事項として参照の追加)、{@link}(インラインのリンクを作成)、{@inheritDoc}(@Overrideなどで継承している場合、継承クラスのJavadocを参照)などが利用できます。

一覧(JavaSE/21)
docs.oracle.com  

まとめ

長文となりましたが、私が最低限気を付けているポイントは以下の通りです。

  • 説明はわかりやすく、わかりやすい形で表現する
  • クラス、メソッド、定数に対するコメントは必須である
  • @param、@return、@throwsの情報は漏れなく記載する

フォーマットであれば<br>だけでも十分であり、自分が知らないタグを調べてまで無理に使う必要はないと思います。
綺麗に見せたい、かっこよく書きたいとか考えるかもしれませんが、何よりも優先すべきは他の人が理解できるか?というポイントでそれが満たせればJavadocとして正しい記載だと思います。

簡単ではありますが、以上がJavadocの説明でした。
Javadocコマンドについては機会があれば追加していきます。

リーダブルコード 未来の自分を助ける 06

今回はリーダブルコード6章より「良いコメント」を書くための方法を解説します。

www.oreilly.co.jp

本シリーズ:リーダブルコードの解説 各記事のリンクはこちらをクリック リーダブルコード 未来の自分を助ける 01 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 02 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 03 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 04 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 05 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 06 - こだわりデベロッパーズノート

内容紹介

6章 コメントは正確で簡潔に

  • 6章 コメントは正確で簡潔に
    • 6.1 コメントを簡潔にしておく
    • 6.2 あいまいな代名詞を避ける
    • 6.3 歯切れの悪い文章を磨く
    • 6.4 関数の動作を正確に記述する
    • 6.5 入出力のコーナーケースに実例を使う
    • 6.6 コードの意図を書く
    • 6.7 「名前付き引数」コメント
    • 6.8 情報密度の高い言葉を使う
    • 6.9 まとめ

前章(5章)ではコメントを書くこと自体に対する注意点を解説しました。

6章では実際にコメントを書くにあたり、より正確な情報を、より分かりやすい表現で、より簡潔に短く書くための方法を解説しています。 本章はコメントの書き方の解説ですが、2章や3章で取り扱っていたコードの書き方とリンクする内容が沢山あります。

コードにしろ、コメントにしろ、重要な点は一緒だということですね!

本章はリーダブルコードに具体例が豊富に記載されているため、それを引用しながら内容を深掘りしていきます。

6.1 コメントを簡潔にしておく

「コメントを簡潔にして分かりやすくする」ということですが、さすがにこれだけですと抽象的過ぎてちょっと難しいです。

もう少し具体的に考えると、ここで言う「コメント」とは「コード上に記載するコメント」です。 つまり、そのプログラミング言語にマッチする良い感じの表現が色々とあるものです。 プログラミング言語が持つ表現をコメントにもぜひ活用しましょう。

リーダブルコードの例

// intはCategoryType
// pairの最初のfloatは'score'
// 2つめは'weight'
typedef hash_map<int, pair<float, float>> ScoreMap;

C++の言語表現を活用すると1行でいい感じに各要素の意味を記述できます。

// CategoryType -> (score, weight)
typedef hash_map<int, pair<float, float>> ScoreMap;

6.2 あいまいな代名詞を避ける

「あれ」「これ」「それ」を使わずに、代名詞が指す名詞を利用しましょう。

代名詞が利用されている場合、コメントを読んだ人が代名詞に合致する名詞を判断する必要があり、意味の取り違えが起きます。

リーダブルコードの例

// データをキャッシュに入れる。ただし、先にそのサイズをチェックする。

上記コメントの場合、「そのサイズ」は「データのサイズ」を表すのか、「キャッシュのサイズ」を表すのか分かりません。では名詞を代入してみましょう。

// データをキャッシュに入れる。ただし、先にデータのサイズをチェックする。

一目瞭然ですね!!

6.3 歯切れの悪い文章を磨く

「歯切れが悪い」という言葉をブレイクダウンすると、コメントには「計画」よりも「行動」を書きましょう。

コードが実際に処理している行動をコメントに書くことで、より明確な情報となります。

リーダブルコードの例

ウェブクローラにコメントを記載する場合

// これまでにクロールしたURLかどうかによって優先度を変える。

「計画」ではなく、このコードが実際に行っている「行動」を書いてみると

// これまでにクロールしていないURLの優先度を高くする。

直接的かつ明確に表現できます。「クロールしてないURLの優先度が高くなる」という実際の行動も読み取れます。これは「優先度を変える」という計画からは読み取れない情報ですね。

6.4 関数の動作を正確に記述する

実装にコメントを行う場合は「やりたい事」ではなく「やっている事」を書きましょう。

全体像を把握するためのコメントとして「やりたい事」を書くことは非常に有効です。前回の記事(「全体像」のコメント)で触れておりますのでご参照ください。

それに対して、具体的な実装に関するコメントを書く際は「やっている事」を書きます。

「やりたい事」を解決する手段は複数あるものです。そのため、コメントで「やりたい事」を読んでも、結局どの手段を選んだのか?は分かりません。実装にコメントを書くのであれば、コードで実際に「やっている事」を書く必要があります。

リーダブルコードの例

ファイルの行数を数える関数にコメント書く場合

// このファイルに含まれる行数を返す。

「行数を数えたい」という「やりたい事」が書いてあります。しかし、このコメントでは以下の疑問が解消しません。

  • 空ファイルは、0行なのか1行なのか
  • "hello"は、0行なのか1行なのか
  • "hello\n"は、1行なのか2行なのか
  • "hello\n world"は、1行なのか2行なのか
  • "hello\n\r cruel\n world\r"は、2行なのか3行なのか4行なのか

「やっている事」をコメントすることで、上記の疑問は解消します。

// このファイルに含まれる改行文字('\n')を数えて返す。

こうすると改行文字('\n')が無い場合は、0を返すことが分かりますし、キャリッジリターン('\r')が無視されることも分かります。

6.5 入出力のコーナーケースに実例を使う

関数のコメントに「やっている事」をちゃんと書いても、まだちょっと理解するのが難しいなと感じた場合は実例を書きましょう。

どんな入力を入れたらどんな出力が返ってくるか?が実例で記載されていると、呼び出したときのイメージが掴みやすいです。

「テストコードは後世の人にとってドキュメントになる」とされている理由がまさしくこれですね。テストコードが実例となりますので、関数がどのように動くかを理解する大きな手助けとなります。

リーダブルコードの例

文字列の一部を除去する関数の場合

// 'src'の先頭や末尾にある'chars'を除去する
String strip(String src, String chars) { ... }

このコメントで何をするかは分かりますが、細かい部分でどう動くのかがイメージできず不安が残ります。

  • charsは、除去する文字列なのか、除去する文字列集合で順序は関係ないのか?
  • srcの末尾に複数のcharsがあった場合はどうなるのか?

このような疑問を解消する実例をコメントに書いておきましょう。

// 'src'の先頭や末尾にある'chars'を除去する
// 実例 : strip("abba/a/ba", "ab") は "/a/"を返す。
String strip(String src, String chars) { ... }

実例があることで、どのような入力を行ったら、どんな出力が返ってくるかがイメージできるようになります。

6.6 コードの意図を書く

コードを見たままのことをコメントに書くのではなく、なぜそのようなコードを書いたのか意図を表すようにしましょう。

これは前回の記事「5.2 自分の考えを記録する」とリンクする内容です。 意図をコメントに書くことで、後世の人がコードを読んだときに「何をしているかは分かるが、何でしているのかが分からん」という状態を防ぐことができます。

リーダブルコードの例

以下の例はコードの動作をそのままコメントしています。

prices.sort(comparePrice);
// 逆順にイテレートする
for(ListIterator it = prices.listIterator(prices.size()); it.hasPrevious(); ) { ... }

意図を記載することで、何で逆順にイテレートするのかが分かるようになります。

prices.sort(comparePrice);
// 値段の高い順番に表示するため逆順にイテレートする
for(ListIterator it = prices.listIterator(prices.size()); it.hasPrevious(); ) { ... }

値段が高い順番に処理したいという意図が分かりますので、自然とfor文の直前に行っている「prices.sort(comparePrice);」が値段の昇順ソートであることも分かります。

もし「prices.sort(comparePrice);」が値段の降順ソートであるならば、for文の書き方が間違っていることも分かります。

コメントに意図を書き出すことが難しいと感じたときは、以下を試してみてください。

  • 書いたコメントの先頭に「のために」を付けたす
  • 書いたコメントの末尾に「なぜなら」を付けたす

これをやることで、自然と意図まで書くことができるようになります。前述した例は「書いたコメントの先頭に『のために』を付けたす」パターンですね。

6.7 「名前付き引数」コメント

名前付き引数に対応していない言語では、名前付き引数のようなコメントを入れることでコードが読みやすくなることがあります。

java言語は現時点(2023年)では名前付き引数の機能を持っていません。IDEを利用していればjavadocをすぐに参照できますのでそこまで大きな問題と感じていない人も多いかと思います。

それでも、以下のような場合には名前付き引数コメントを利用すると、より読みやすいコードになるかもしれません。

  • javadocを見ても引数名が分かりにくいし、外部ライブラリで引数名を変更できない
  • オーバーロードを利用していて同じ数の引数でも引数の型により解釈が異なる

リーダブルコードの例

名前付き引数が利用できるpythonで接続処理を呼び出す場合

Connect(10, False)
Connect(timeout = 10, use_encryption = False)

名前付き引数を利用した方が、引数の意味が分かりやすくなります。

同じような処理がjavaにあったとして

voic connect(int timeout, boolean useEncription) { ... }
...
connect(/* timeoutMs = */ 10, /* useEncription = */ false);

名前付き引数のようなコメントを入れることで情報を追加することができます。 この例のポイントは「第一引数の名前に単位が入っていないのでコメントで単位を補っている」ことです。 「javadocを見ても引数名が分かりにくい」場合に情報を追加する形ですね。

オーバロードを利用している場合の例としては

find(String src, String chars)
find(String src, String chars, boolean backwordsSearch)
find(String src, String chars, int fromIndex)
find(String src, String chars, int fromIndex, boolean backwordsSearch)

オーバーロードされているメソッドの中から、同じ引数だけど引数の型が異なるメソッドを呼び出してみます。

find("hello world", "o", 3);
find("hello world", "o", true);

第三引数の型が異なるため、オーバーロードされていることは分かりますが、第三引数の意味はさすがに分かりません。 気になった方はIDEjavadocを確認するでしょう。

find(/* src = */ "hello world", /* chars = */ "o", /* fromIndex = */ 3);
find(/* src = */ "hello world", /* chars = */ "o", /* backwordsSearch = */ true);

コメントで情報を追加すると、javadocを見なくても第三引数の意味が分かりますね!

よくわからない引数名に対して情報を追加してあげたいとき、最もコストがかからず簡潔に実現する方法ですので「ちょっとわかりにくいな」と思ったときに活用ください。

6.8 情報密度の高い言葉を使う

IT業界やプログラミングの世界で利用される専門用語、業界用語を活用しましょう。

専門用語や業界用語は外部の人には全く理解できないという問題はありますが、代わりに言葉が持つ情報量が高いです。

上手く専門用語、業界用語を活用することで、短く簡潔なコメントで明確な意味を沢山持たせることができます。

リーダブルコードの例

// このクラスには大量のメンバがある。同じ情報はデータベースにも保管されている。ただし、
// 速度の面からここにも保管しておく。このクラスを読み込むときには、メンバが存在してい
// るかどうかを先に確認する。もし存在していればそのまま返す。存在しなければ、データベー
// スから読み込んで、次回のためにデータをフィールドに保管する。

4行に渡るコメントに対して、専門用語を活用すると以下の1文で表現できます。

// このクラスの役割は、データベースのキャッシュ層である。

一度取得したデータをメモリ上に保管しておいて、二度目以降の利用時に性能を改善するパターンが「キャッシュ」になります。

こう見ると「キャッシュ」の5文字にものすごい情報量が詰まっていますね!

まとめ

本記事では、リーダブルコード6章の内容を解説しました。

良いコメントを書く技術を説明しましたが、何よりも大事なことは「コメントを書く」ことそのものです。

前回の記事にもあった「ライターズブロック」を乗り越えること。これが一番大事です。

もし、この記事を読んで「うわっ、コメント書くのめんどくさいな!」と思うことがありましたら、一旦この記事の内容はすべて忘れてください。

コメントを書くことに慣れて「どうせ書くならもうちょっと良いもの書きたいな」と思ったときに振り返っていただけましたら幸いです!

リーダブルコード 未来の自分を助ける 05

今回はリーダブルコードの5章より「読みやすいコード」とするために記載する「コメント」の重要な考え方について触れていきます。

www.oreilly.co.jp

本シリーズ:リーダブルコードの解説 各記事のリンクはこちらをクリック リーダブルコード 未来の自分を助ける 01 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 02 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 03 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 04 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 05 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 06 - こだわりデベロッパーズノート

内容紹介

5章 コメントすべきことを知る

  • 5章 コメントすべきことを知る
    • 5.1 コメントするべきでは「ない」こと
    • 5.2 自分の考えを記録する
    • 5.3 読み手の立場になって考える
    • 5.4 ライターズブロックを乗り越える
    • 5.5 まとめ

5章ではコメントに何を書くのが良いのか? どのようなコメントを書くことで「読みやすいコード」になるのか? 逆に、どのようなコメント書くとかえって「読みにくいコード」になるのか?を解説しています。

人間はコメントに記載されていることを読まずに理解することはできません。 コメントが記載されていれば、そのコメントを読む分必要な時間が増えます。 そのため、読む価値のないコメントは「読みやすいコード」(読んで理解するまでにかかる時間が短いコード)とは真逆のものとなります。

それでは、価値のないコメントと価値のあるコメントの違いに触れていきましょう。

5.1 コメントするべきでは「ない」こと

以下に該当するコメントは改善しましょう。

  • コードを読んで直ぐに分かることをコメントする
  • 分かりにくい名前を解説するためにコメントする

コードを読んで直ぐに分かることをコメントする

このパターンのコメントで最も多いものが「自動生成機能」です。

javaであれば最近のIDEは「javadoc自動生成機能」を持っています。 この機能を利用して生成したjavadocをちゃんとメンテナンスしないと「コードを読んで直ぐに分かること」がjavadocに記載されます。

結果として、コメントを読んでも何ら得られる情報はなく、コメントを読む分だけ時間が増えてしまうことになります。

分かりにくい名前を解説するためにコメントする

変数名、定数名、クラス名、メソッド名などなど、ソースコードを書く際は常に命名と向き合う必要があります。

何時も素晴らしい命名ができれば良いのですが、残念ながらうまいこと命名できず、後から読んでみて分かりにくいと気が付く場合があります。 このようなとき、分かりにくい名前の解説にコメントを利用するのはやめましょう。

名前が良くないのであれば、それをコメントで補うのではなく、名前自体をもっとより良いものに変更しましょう。 「ひどいコード + 優れたコメント」よりも「優れたコード」の方が理解しやすく、保守もやりやすいです。

リーダブルコードの具体例

コードを読めば直ぐに分かる

以下のコメントはAccountクラスがどのような目的を持つクラスか?フィールドのprofitがどのような意味を持つかは何も説明されていません。

コードを読んで直ぐに分かることしかコメントされていないので、このコメントには価値がありません。ちゃんとコメントをメンテナンスして目的や意味を記載しましょう。

// Accountクラスの定義
class Account {
    // コンストラクタ
    public Account() { ... }

    // profitに新しい値を設定する
    public void setProfit(double profit) { ... }

    // このAccountからprofitを返す
    public double getProfit() { ... }
}
分かりにくい名前の解説

Replyの内容をルールに則って綺麗にしたいのでメソッド名に「clean(綺麗)」を使っています。

// Replyに対してRequestで指定した制限を適用する。
// 例えば、返ってくる項目数や、合計バイト数など。
void cleanReply(Request request, Reply reply) { ... }

しかし、ITでは「clean」という名前はデータを全て削除してまっさらな状態を表すことが多いです。 そのため、誤解を生まないように「clean」をコメントで説明しています。

このような場合は「enforce limit(制限を課す)」という名前にすればコメントが無くても理解できます。

// replyをrequestで指定された項目数やバイト数の制限に合わせる。
void enforceLimitsFromRequest(Request request, Reply reply) { ... }

5.2 自分の考えを記録する

コードを書いている時に考えていたことをコメントに記録することで、そのコードにどのような意図が有ったのか?を後から読んで理解できます。

「悩んだこと」「複数の方法から一つの方法を選んだ理由」など、自分がコードを書くために決断したときの「大切な考え」をコメントに記録しましょう。

  • コードを書いたときに考えたこと
  • コードに存在する欠陥
  • その値を定数とすることに決めた背景

コードを書いたときに考えたこと

コードを書く際に、複数の方法から一つを選ばなければならないときがあります。

トレードオフが発生する中で「何を選択して、何を諦めたのか?」をコメントとして記録しましょう。後世の人がコードを読んだとき、より大切としているものが何であるか?を読み取ることができます。

// ヒューリスティックだと単語が漏れることがあるが仕方ない。100%は難しい。

もし、どれを選んでも大差がないのであれば、それも記録しましょう。「ちゃんと考えて比較した結果、どれを選んでも差が無かった」という情報があれば、後世の人が「もしかして、これ検討が漏れてたんじゃないの?」と疑念を持つことがなくなります。

// nested loop結合で性能が悪かったのでmerge結合とhash結合を検証したが大差はなかった。

コードに存在する欠陥

技術、サービス、コードは進化を続けます。そのため、現時点では妥当であった方針が徐々に実情と合わなくなってくることがあります。このような、問題となることが分かったものはコードに記録しましょう。

現在はデータ量が少ないため問題ないものの、データ量が増えてきたら問題となることがあるのであれば、それを頭と心に留めるのではなくコメントに記録しましょう。

// 50万件までならArrayListで問題ない。50万件超えるならTreeListを検討した方が良い。

現在のリリースでは意図的に対応していないものがあるのであれば、それをコメントに記録しましょう。

// 1stリリースではJPEGのみ対応。他の画像フォーマットは今後のリリースで順次対応予定。

このようなコメントを残しておくことで、後世の人はどんな課題があるのか?どんな対応が予定されているのか?をコードから把握することができます。

その値を定数とすることに決めた背景

とある値を定数とすることを決めた場合、その値には「システム的な理由」か「業務的な理由」があるはずです。

どのような理由からこの定数を作ったのか?背景をコメントに残しておきましょう。

定数名をちゃんと付けていれば、定数がどんな意味を持つのか?は定数名とその値から読み取れます。しかし、その値にした背景を読み取ることはできません。 コメントに記録することで、その値を定数としたときの「大切な考え」を残しておきましょう。

後々その定数を変更する必要があるのか?ないのか?を判断する際にとても役立ちます。

// 同時に実行するワーカースレッドの数
// 性能試験の結果、処理時間、CPU消費量、メモリ消費量のバランスが良い数が8だった
public static final int NUM_THREADS = 8;

5.3 読み手の立場になって考える

この内容は経験がものを言う部分が大きいです。そのため、若手エンジニアの方にはちょっと難しいかと思います。

若手エンジニアの方は、自分が実際にその状況になった場合にコメントに記録するところから始めてみましょう。

  • 質問されそうなことを想像する
  • ハマりそうな罠を告知する
  • 「全体像」のコメント

質問されそうなことを想像する、ハマりそうな罠を告知する

若手エンジニアの方が、自分の書いたコードに対して思案を巡らせながら実装するのはなかなかにハードルが高いです。 そのため、まず最初は自分が経験したことをコメントに記録してみましょう。

自分が悩んで質問したことは、後世の人も同じように悩んで質問する可能性が高いです。 自分がハマったことは、後世の人も同じようにハマる可能性が高いです。

自分が質問したこと、自分がハマったことをコメントに記録することで、後世の人が同じ轍を踏まないようにしてあげましょう!

「全体像」のコメント

こちらも若手エンジニアの方にはなかなかに難しい内容となっています。

自分が担当する箇所に関して、自分が何のために何をしようとしているかを把握しながら進めるために、大まかにやりたいことをコメントするのは良い方法です。

まずは自分が担当している範囲を中心に、全体像を把握する練習と合わせてコメントしてみましょう。

5.4 ライターズブロックを乗り越える

コメントをどのように書こうか悩んでしまい、結局書かなかったというのはあるあるではないかと思います。

とは言っても、コメントを書かないことには慣れていくことができません。だからこそ、自分の頭で考えている内容をとりあえず書いてみましょう。 後で読み返してみて不要だと感じたら改めて消せば問題ありません。

リーダブルコードでは「ヤバイ。これはリストに重複があったら面倒なことになる」と思ったら、それをそのままコメントに書けば良い。と言っています。 このコメントがあるだけでも、後世の人は潜在的な課題が存在していることを把握できます。

慣れるためにも、まずは頭の中にある大切な考えをそのまま書き出してみましょう!

まとめ

本記事では、リーダブルコード5章の内容を解説しました。

最初の方では「コメントに書かないこと」、最後の方では「コメントに書くこと」を解説しています。

慣れるまでは、「このコメントは書いた方がいいのだろうか?価値が無いのなら書かない方がいいのだろうか?」と悩むこともあるのではないかと思います。

とりあえず頭に浮かんだことを書いてみましょう!「ライターズブロックを乗り越える」これが非常に重要です。

悩んだら「書きましょう!」

書かないことには慣れないですし、コメントを考える練習もできません。書いていればレビューで指摘を受けてより良い方法を学ぶこともできます。

コメントを書くことで「この実装もうちょっと綺麗にできるな」とか「この名前は分かりにくいな」とか気が付く点もありますので「物は試し」まずは書いてみましょう!

リーダブルコード 未来の自分を助ける 04

今回はリーダブルコードの4章より「読みやすいコード」とするための「見た目、レイアウト」について触れていきます。

www.oreilly.co.jp

本シリーズ:リーダブルコードの解説 各記事のリンクはこちらをクリック リーダブルコード 未来の自分を助ける 01 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 02 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 03 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 04 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 05 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 06 - こだわりデベロッパーズノート

内容紹介

4章 美しさ

  • 4章 美しさ
    • 4.1 なぜ美しさが大切なのか?
    • 4.2 一貫性のある簡潔な改行位置
    • 4.3 メソッドを使った整列
    • 4.4 縦の線をまっすぐに
    • 4.5 一貫性と意味のある並び
    • 4.6 宣言をブロックにまとめる
    • 4.7 コードを「段落」に分割する
    • 4.8 個人的な好みと一貫性
    • 4.9 まとめ

4章ではコードを読みやすくするために、余白、配置、順序をどのように工夫するのが良いかを解説しています。

この読みやすさを考えるにあたり、まず最初に3つの原則を挙げています。

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使う。
  • 似ているコードは似ているように見せる。
  • 関連するコードをまとめてブロックにする。

「見た目」には個人の好みがあるため、全員が満足するものにはできません。しかし、この原則を用いることで大体の方が見やすいと感じる見た目にできます。

4.1, 4.2, 4.4 コードフォーマッタの活用

4.1 なぜ美しさが大切なのか?」、「4.2 一貫性のある簡潔な改行位置」はインデント、改行位置の使い方を統一することで、視覚的に見やすくすることの大切さを述べています。この視覚的な見やすさについてはコードフォーマッタを活用しましょう。

インデントの深さや、文字数による改行位置の調整は人間が頑張るよりも、機械にお願いした方が楽なうえに正確です。

利用しているEditor、IDEでコード保存時に自動的にコードフォーマットを行う設定を導入しましょう。意識しなくても勝手に綺麗になる仕組みを作ってしまうのが良いです。

コードフォーマッタを使うと縦を揃えられなくない?

4.4 縦の線をまっすぐに」では、コードを読みやすくするために、スペースを使って縦を揃える方法が紹介されています。しかし、コードフォーマッタを導入すると余分なスペースが自動的に削除されるため、スペースを使って縦を揃えることができません。

このような場合は、コードフォーマッタを一時的に無効化する機能があります!

Eclipseでコードフォーマッタを一時的に無効化する

以下はEclipseの設定になります。

「上部メニュー → ウィンドウ → 設定 → Java → コード・スタイル → フォーマッタ」を選択して右側に表示される「編集」ボタンクリック

コードフォーマッタ設定画面

左下の「off/on タグを使用可能にする」にチェックを付けます。これでコメントを利用して一時的にコードフォーマットを無効化することができます。

off/on タグを使用可能にする

列挙体、JUnitのテストデータなど、関連するものが並ぶ箇所は縦を揃えたくなります。このような箇所にはコードフォーマッタの無効化を活用しましょう。

コードフォーマッタを一時的に無効化することでこんな感じに縦を揃えられます。

public enum RoleType {

    //@formatter:off
    //Enum名(DDL表示順,   日本語表示,        英語表示
    GUEST   (0,          "一次利用",        "Guest"),
    USER    (1,          "利用者",          "User"),
    ADMIN   (9,          "管理者",          "Administrator"); 
    //@formatter:on

    /** ドロップダウンリストに表示する順番 */
    private final int ddlOrder;
    /** 日本語表示 */
    private final String nameJp;
    /** 英語表示 */
    private final String nameEn;

    ...
}

但し、この機能を乱用してしまうとコードフォーマッタの意味がなくなってしまいます。どのような箇所に利用するかルールをちゃんと決めて運用しましょう。

4.3 メソッドを使った整列

同じ処理を何度も実行している場合は、その処理をまとめたメソッドを作成して重複コードを減らしましょう。 重複コードをメソッドにまとめることでコードが簡潔になりますしDRY原則を順守できます。

但し、重複コードをメソッドにまとめたとき、メソッド名の命名で手を抜くと読みにくいコードになります。命名は非常に大事です!手を抜かないようにしましょう!

リーダブルコードの具体例

テストコードで同じようなアサート処理が繰り返されており読みにくいコードになっている。

// DBから略称に対応する正式名称を取得するメソッドのテスト
assertEquals("Mr. Douglas Adams", expandFullName(dbConnection, "Doug Adams", error));
assertEquals("", error.getMessage());

assertEquals("", expandFullName(dbConnection, "No Such Guy", error));
assertEquals("no match found", error.getMessage());

assertEquals("", expandFullName(dbConnection, "John", error));
assertEquals("more then one result", error.getMessage());

何度も実行している処理をまとめてテスト用のヘルパーメソッドを作成することでテストコードが読みやすくなります。

// テスト用のヘルパーメソッド
void checkFullName(String name, String expectedFullName, String expectedError) {
    String actualFullName = expandFullName(dbConnection, name, actualError);
    assertEquals(expectedFullName, actualFullName);
    assertEquals(expectedError, actualError.getMessage());
}

// DBから略称に対応する正式名称を取得するメソッドのテスト
checkFullName("Doug Adams", "Mr. Douglas Adams", "");
checkFullName("No Such Guy", "", "no match found");
checkFullName("John", "", "more then one result");

今後テストパターンを追加するとき、簡単に追加できるようになりました。

4.5, 4.6, 4.7 意味のある形でコードを並べる、まとめる

4.5 一貫性と意味のある並び」「4.6 宣言をブロックにまとめる」「4.7 コードを「段落」に分割する」では、読みやすくするためにコードの並び順やまとめ方について解説しています。

ここで重要なことは「意味のある並び順」、「意味のあるまとまり」です。コードが行っていることをちゃんと理解した上で「意味のある」形で並べたり、まとめたりすることで、後からコードを読んだ人も意味が理解しやすくなります。

意味のある並び順

Web画面でお客さんに「住所、氏名、年齢、電話番号」を入力してもらった場合、そのリクエストを受け取ったサーバ側も同じように「住所、氏名、年齢、電話番号」の順番でリクエストから値を取得しましょう。

フロントの画面を見た人がバックエンドのコードを読んだときに読みやすいです。

受け取った値をバリデーションチェックするときも「住所、氏名、年齢、電話番号」の順番で行いましょう。

一貫性を持った意味のある並びとすることでコードが読みやすくなります。

意味のあるまとまり

ただ処理を羅列している状態は読みにくいです。意味のあるまとまりで「空行を入れる、コメントを入れる、メソッドにまとめる」などコードの中にも段落を作ることで読みやすくなります。

どのようにまとめたら良いのか分かりません!という方はまず「入力、処理、出力」に分けてみましょう。なぜなら、プログラムの構成要素は大きく分けるとこの三つになるからです。

  • 入力:引数、リクエスト、ファイル、DBなど何らかの形で入力する
  • 処理:入力されたデータを元に何らかの処理を行って処理結果を作成する
  • 出力:処理結果を戻り値、レスポンス、ファイル、DBなど何らかの形で出力する。

殆どのプログラムはこの流れになります。なのでこの形でまとめると処理の流れが読みやすくなります。

逆に言うと、処理の途中で逐次データを読み込んで入力したり、処理の途中で結果が確定していないのに出力を行ったり(この場合、最終的に確定した値で出力を上書きしたりしていると非常に理解しにくい)、処理をまとめずに混ざっている状態だと後から読んだとき理解が難しくなります。

まとめ

本記事では、リーダブルコード4章の内容を解説しました。

一貫性のある統一された見た目とする際は、コードフォーマッタがとても役立ちます。

コードフォーマッタを導入しても一時的に無効化することが可能です。コードフォーマッタを導入したことでできなくなることは一切ありません。

見た目の整理整頓は機械がうまいことやってくれます。機械が得意なことは機械にお願いして人間は楽をしましょう!!

リーダブルコード 未来の自分を助ける 03

今回はリーダブルコードの3章より「読みやすいコード」を書く方法として「誤解されない名前」について触れていきます。

www.oreilly.co.jp

本シリーズ:リーダブルコードの解説 各記事のリンクはこちらをクリック リーダブルコード 未来の自分を助ける 01 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 02 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 03 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 04 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 05 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 06 - こだわりデベロッパーズノート

内容紹介

3章 誤解されない名前

  • 3章 誤解されない内容
    • 3.1 例:filter()
    • 3.2 例:Clip(text, length)
    • 3.3 限界値を含めるときはminとmaxを使う
    • 3.4 範囲を指定するときはfirstとlastを使う
    • 3.5 包括/排他的範囲にはbeginとendを使う
    • 3.6 ブール値の名前
    • 3.7 ユーザの期待に合わせる
    • 3.8 例:複数の名前を検討する
    • 3.9 まとめ

3章では2章で取り扱った「命名」に対して、命名時に気を付ける点を深掘りして

  • どのような名前だと「誤解される」のか?
  • どういうことに気を付ければ「誤解されない」ようになるのか?

を解説しています。

本章はリーダブルコードにて具体例を上げているものが多いため、それらをささっと紹介しつつ「3.6 ブール値の名前」「3.7 ユーザの期待に合わせる」の内容を取り上げて、もう少し分かりやすくなるようにブレイクダウンします!

リーダブルコード「3.1 ~ 3.5」の具体例

filterをかけたら選択される?除外される?

DBへの問い合わせ、C#LINQJavaのStreamAPIなどでデータをフィルタリングする場合、素直に「filter」と命名するのは避けましょう。 何故なら、フィルタリングは「手段」だからです。

なぜこのデータをフィルタリングしたのでしょうか? 特定のデータが欲しかったから?特定のデータを省きたかったから?

特定のデータが欲しかったのであれば、それは沢山あるデータの中から一部を「選択」しています。つまり目的である「select(選択)」の方が明確です。

特定のデータを省きたかったのなら、それは沢山あるデータの中から一部を「除外」しています。つまり目的である「exclude(除外)」の方が明確です。

限界値(境界値)にはminとmaxを利用する

限界値には「min(minimum)」「max(maximum)」を利用することで「最小のhoge」、「最大のfuga」と最小値、最大値が含まれることを表現できます。

限界値が含まれるのか?含まれないのか?を取り違えて起こす「off-by-oneエラー」は本当にあるあるの不具合です。

Off-by-oneエラー - Wikipedia

この「off-by-oneエラー」を防止するためにも「min」「max」を利用して値が含まれることを明確に表現しましょう。境界値テストも大事です!忘れずに行いましょうね!

範囲を指定する場合の工夫

○○以上、××以下

範囲を指定したい場合に「○○以上、××以下」を表現したい場合は「first」「last」を使用しましょう。

first

最初は
ここ
                        last

最後は
ここ

「first(最初)、last(最後)」を利用することで「最初と最後が含まれる」つまり「以上、以下」であることが表現できます。

○○以上、××未満

終点を含めたくない場合の「○○以上、××未満」を表現したい場合は「begin」「end」を使用しましょう。

begin

開始
                        end

終了

ファイルが終了する文字コードのことを「End Of File(略してEOF)」と言います。EOFに到達することでファイルから文字列の読み込みが終了します。そして、ファイルから読み込んだ文字列はEOF直前の文字までになります。

このようにITでは「End」で「Endの直前までが対象」と解釈することが多いです。そのため「begin」「end」を利用することで「○○以上、××未満」であることが表現できます。

3.6 ブール値の名前

ブール値に名前を付けるときは「trueの意味が明確となる名前」を付けましょう。

変数名、引数名

以下の変数名は、trueとなったときの意味が明確ではありません。

boolean readPassword;
  • パスワードを読み込む必要がある
  • 既にパスワードを読み込んでいる

どちらを表しているのか判断に悩むため、実際に変数が利用されているコードを読む必要があります。

  • パスワードを読み込む必要があることを表したい:「needPassword」
  • 既にパスワードを読み込んでいて認証を行う:「isAuthenticated」

とすると、trueとなった時の意味が明確になります。

メソッド名

メソッド名にも同様のことが言えます。

public boolean validationCheck( ... ) { ... }
  • trueを返したら問題が無い
  • trueを返したらバリデーションエラーが起きている

どちらであるか判断に悩みます。そもそも「validationCheck」という名前も何をチェックしているのか分かりません。

メールアドレスのバリデーションチェックを行っているのであれば「isMailAddress()」とすれば、trueが返ってきたらメールアドレスとして問題ないと分かります。

boolean型に利用する接頭語一覧

よく見かけるboolean型の接頭語をまとめてみました。

# 接頭語 意味 trueの場合 falseの場合
1 is 状態 isDelete 削除されている 削除されていない
2 can 可能、不可能 canDelete 削除できる 削除できない
3 has ある、ない hasLicence 免許を持っている 免許を持っていない
4 contains 含む、含まない containsKey Keyが含まれる Keyが含まれない
5 shoud 必要、不要 shoudPassword パスワードが必要 パスワードが不要
6 need 必要、不要 needPassword パスワードが必要 パスワードが不要
7 use 利用する、利用しない useSsl SSLを利用する SSLを利用しない
8 exists 存在する、存在しない userExists ユーザが存在する ユーザが存在しない

3.7 ユーザの期待に合わせる

ITに根ずく文化が原因で自然とコードに期待を持ってしまう場合があります。この期待が原因で誤解を招くことがあります。

このような先入観を取り除くのは非常に難しいです。そのため、無理に頑張らずに受け流してしまいましょう!

頑張らずに「誤解を招いてしまう場合は名前を変えて対応」です!

get****()

getで始まるメソッドはメンバの値を返すだけの「軽量なメンバアクセスメソッド」をイメージします。そのため、getメソッドが重たい処理をしていると思いません。 値を取得する際にコストが重い処理を行う場合は「get」を使わずに「compute」などに変えましょう。

また、getメソッドは値を戻り値で返す処理をイメージします。そのため、getメソッドを利用して値が変更される部分は戻り値を受け取った変数だけで、引数で入力した値が変わるとは思いません。 引数で入力した値に副作用がある場合は「get」を使うのはやめましょう。

size()

大体の言語の標準ライブラリでは「size()」の計算量が「O(1)」となるように実装されています。そのため、sizeメソッドの計算量がデータ件数に依存するとは思いません。 sizeメソッドで重い処理を行うのはやめましょう。

引数の副作用で値を返す場合はreturnしない

getメソッドの解説ともリンクしますが、メソッドが戻り値を返している場合、そのメソッドが処理を行った結果は戻り値で受け取ると考えます。

逆に、戻り値が無いメソッドの場合は、そのメソッドが何か値を変更したとき、その変更を受け取れるものは入力した引数の副作用しかありません。

そうすると、メソッド戻り値の有無によって以下の期待が生まれます。

  • 戻り値があるから「引数は変更されない」
  • 戻り値が無いから「引数が変更されるかもしれない」
    • もし、このメソッドが内部で値を変更していたら、引数が変更されていないと呼び出し元が処理結果を受け取れない

戻り値もあるけど、引数も副作用で変更しているという場合は、上述した期待で「引数は変更されない」と思っていると不具合を作り込んでしまいます。

引数に副作用を起こしている場合、そのメソッドは戻り値が無い方が誤解が起きません。

まとめ

本記事では、リーダブルコード3章の内容を解説しました。

3章の内容も明日から即実践できるものになっています。まずはできる範囲から取り入れてみてください。

本記事の中で記載しました中でも「○○Check」は特によく見る機会が多いです。

「○○Check」はチェックした結果が「良いのか?」「悪いのか?」が分かりにくいことが多いので気を付けましょう!ほとんどの「○○Check」は「is○○」「can○○」「has○○」で解決すると思います!

リーダブルコード 未来の自分を助ける 02

前回の記事ではリーダブルコードの中心となる「読みやすいコード」についてと、読みやすいコードがもたらす価値とはどういうものなのか?について解説を行いました。

tech-kodawari-japan.hatenablog.com

今回からリーダブルコードの2章以降に記載されている「読みやすいコード」を書くための方法について触れていきたいと思います。

www.oreilly.co.jp

本シリーズ:リーダブルコードの解説 各記事のリンクはこちらをクリック リーダブルコード 未来の自分を助ける 01 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 02 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 03 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 04 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 05 - こだわりデベロッパーズノート
リーダブルコード 未来の自分を助ける 06 - こだわりデベロッパーズノート

内容紹介

2章 名前に情報を詰め込む

  • 2章 名前に情報を詰め込む
    • 2.1 明確な単語を選ぶ
    • 2.2 tmpやretvalなどの汎用的な名前を避ける
    • 2.3 抽象的な名前よりも具体的な名前を使う
    • 2.4 名前に情報を追加する
    • 2.5 名前の長さを決める
    • 2.6 名前のフォーマットで情報を伝える
    • 2.7 まとめ

2章では名前を付けるときのテクニックを解説しています。

コードを書く際は、変数、メソッド、クラスなどの命名と向き合うことになります。そんなとき、道を示してくれる光となる内容です。

本記事では「2.1 明確な単語を選ぶ」「2.3 抽象的な名前よりも具体的な名前を使う」「2.4 名前に情報を追加する」「2.6 名前のフォーマットで情報を伝える」を取り上げて、もう少し分かりやすくなるようにブレイクダウンします!

2.1 明確な単語を選ぶ

本記事ではこの言葉を以下のようにブレイクダウンしました。

まず修飾語を付ける。そのあとにより具体的な表現を探す

「まず修飾語を付ける」

例として、船に詰める荷物の量を記録したい場合を考えます。限度を表す単語は「capacity」です。これを使ってクラスを作成するとこうなります。

public class ship {
    long capacity;
}

しかし、これは「capacity」が「船に詰める荷物の限度」なのか「乗船できる人数の限度」なのか判断できません。capacityには定員という意味もあるからですね。

ということで「船荷の限度」とするとこうなります。

public class ship {
    long loadingCapacity;
}

「船荷の」という修飾語を付けたことで「船に詰める荷物の限度」であることが明確になりました。 もう「loadingCapacity」が乗船できる定員であるかも?と悩む人はいません。

「より具体的な表現を探す」

ここから更にもう一歩踏み込んでみましょう。「船荷の限度」をもっと明確にした言葉はないのでしょうか?

あるんです!もっと明確に表現する単語が!!

public class ship {
    long burden;
}

「burden」は明確に「(船の)積載量」を表す単語です。「船荷の限度」とは「(船の)積載量」を指しているわけですね。

「背の高さ = 身長」、「体の重さ = 体重」のように、「hogefuga」と修飾語で表現している場合、もし明確にその言葉を表す単語が存在するようであればそれを使いましょう。より明確な名前となり誤解を生む余地が減ります。

リーダブルコードの具体例

リーダブルコードに記載されている具体例を引用します。

def GetPage(url);

「get」は取得することしか表現していません。引数でurlを入力してインターネットから取得するのなら「fetchPage()」や「DownloadPage()」の方が明確です。

class BinaryTree {
    int size();
};

二分探索木の「size()」が何を返すのか分かりません。ツリーの高さなら「height()」、ノードの数なら「numNodes()」、メモリ消費量なら「memoryBytes()」の方が明確です。

class Thread {
    void stop();
};

この「stop()」が取消ができない重い操作なら「kill()」、後から再開できるのなら「pause()」にすると「どのように止まるのか」が明確です。

2.3 抽象的な名前よりも具体的な名前を使う

本記事ではこの言葉を以下のようにブレイクダウンしました。

手段や属性ではなく目的、機能、挙動を名前で表す

なぜ手段や属性を名前に使わない方が良いのか

手段や属性は目的を実現する一部であり、それだけでは目的を表現しきれないからです。

例として、Javaのロギング処理ではログレベルとして「debug / info / warn / error / fatal」の5つがあり、それぞれが明確に目的を表しています。

# レベル 目的 実用例
1 debug デバッグに利用する詳細な情報 引数で受け取った値の全情報
2 info 後で閲覧する可能性がある事象 ユーザのログイン・ログアウト
3 warn エラーではないが例外的な出来事 存在しないユーザIDでログイン試行
4 error 予期しない実行時エラー 非チェック例外の発生
5 fatal 異常終了を伴う致命的なエラー プロセスダウン

ここで「logger.badLog()」というメソッドを作成した場合、どのレベルが含まれるでしょうか? 「badLog()(悪いログ)」と言われると「fatal、error」は含まれそうですが「warn」が含まれるかは悩ましいです。

「good、bad(良い、悪い)」という属性を名前に利用すると、この「bad(悪い)」だけでは目的が明確にならないのです。

もしかしたら、「ログフォーマットがおかしい、ログの本文が無い」などログとして品質が良くないという意味で「bad(悪い)」を使っているのかもしれません。 そうすると「badLog()」メソッドを利用しようとしている開発者の想定と全く違う動きをします。

名前を呼んで何をしているか?が伝わるようにするためには「目的、機能、挙動」を名前で表しましょう。

リーダブルコードの具体例

リーダブルコードに記載されている具体例を引用します。

属性が名前になっていて明確ではない

C++メモリリークを防止するために「コピーコンストラクタ」と「=演算子オーバーロード」をprivateにして呼び出せないようにしています。

class ClassName {
    private:
        DISALLOW_EVIL_CONSTRUCTORS(ClassName);
    public:
        ... 
};

しかし名前が「EVIL(悪の)コンストラクターを許可しない」となっており、「何が悪で許可されていないのか?」、「何は許可されているのか?」が明確になっていません。

そのため、より具体的な名前に変更されました。変更後の名前は「COPY(コピー)」と「ASSIGN(代入)」が許可されていないことが明確になっています。

        DISALLOW_COPY_AND_ASSIGN(ClassName);
手段が名前になっていて明確ではない

ローカルPCの開発時により詳細なデバッグ情報を出力するために「--run_locally(ローカルで実行する)」オプションを用意しました。

しかし「--run_locally(ローカルで実行する)」は手段であり、「より詳細なデバッグ情報を出力する」という目的を表していません。

そのため、新しいチームメンバーはローカルで開発する際に「--run_locally」オプションを使用していたものの、このオプションがなぜ必要なのか?このオプションで何が起きるのか?は把握していませんでした。

目的で命名するなら「--extra_logging」、機能で命名するなら「--debug_logging」にするとより明確になります。

オプションを入れるとロギング処理が重くなるから開発時にしか使用して欲しくない。という場合は「--debug_logging」の方が「デバッグで使う」という意図が伝わります。

障害発生時などに、本番に適用して調査に利用できる実装になっている。という場合は「--extra_logging」の方が「ログ情報を追加する」という意図が伝わります。

2.4 名前に情報を追加する

本記事ではこの言葉を以下のようにブレイクダウンしました。

定数名、変数名、引数名に単位を入れる
定数名、変数名、引数名に重要な属性を入れる

単位を入れる

利用する値に単位が存在する場合は定数名、変数名、引数名に単位を入れましょう。

以下のメソッドはJavaのORM「MyBatis 3」でタイムアウト時間を設定するものです。

void setDefaultStatementTimeout(Integer defaultStatementTimeout)

この引数にはどの単位で入力すれば良いでしょうか。マイクロ秒?ミリ秒?秒?分?

void setDefaultStatementTimeout(Integer defaultStatementTimeoutSec)
void setDefaultStatementTimeout(Integer seconds)

こんな感じで引数に単位が入っていれば悩むことなく利用できます。

重要な属性を入れる

利用する値に紐つく重要な属性がある場合は、定数名、変数名、引数名に入れましょう。

パスワードを格納する変数を用意してみます。

String password;

パスワードを格納する変数であることだけ分かります。ユーザが入力したパスワードなのか?DBなどから取得したパスワードなのか?も分かりません。

String hashedEnteredPassword;

ユーザが入力したパスワードがハッシュになっていることが分かります。なので、この後どこかに保存されているハッシュ済みパスワードとの照合が必要と予想できます。

さらに、このシステムではパスワードをハッシュで管理しているので、パスワードに対して暗号化、複合を行っていないことも読み取れます。

リーダブルコードの具体例

リーダブルコードに記載されている具体例を引用します。

値の単位
# 関数の仮引数 単位を追加した仮引数
1 Start(int delay) delay → delay_secs
2 CreateCache(int size) size → size_mb
3 ThrottleDownload(float limit) limit → max_kbps
4 Rotate(float angle) angle → degrees_cw
その他の重要な属性を追加する
# 状況 変数名 改善後
1 passwordはプレインテキストなので処理する前に暗号化すべきである password plaintext_password
2 ユーザが入力したcomentは表示する前にエスケープする必要がある comment unescaped_comment
3 htmlの文字コードUTF-8に変えた html html_utf8
4 入力されたdataをURLエンコードした data data_urlenc

2.6 名前のフォーマットで情報を伝える

本記事ではこの言葉を以下のようにブレイクダウンしました。

言語の規約、現場の規約を把握して守る

言語ごとに規約(コーディングルール)がありますので守りましょう。 また、現場でも規約(コーディングルール)を定めていることが多いので守りましょう。 規約を守ってコードを書くことで、統一感のある一貫性を持ったコードとなり理解しやすくなります。

規約のチェックはコンピュータにやらせる

規約を丸暗記して守るのはさすがに無理があります。そもそも、そんなところに人間の貴重なリソースを使いたくありません。

なので、規約が守られているかのチェックには静的解析ツールを活用しましょう!

# 種類 機能
1 Linter 文法の誤り、バグの原因となりやすい記述などをチェックして警告
2 Formatter ソースコードを自動整形してフォーマット差異が出ないようにする
3 SpellChecker スペルミスを検出して警告

規約が守れていない場合は静的解析ツールに怒ってもらえば人間は楽ができます。 ついでに、CI/CDに組み込んでしまえば静的解析ツールの警告を無視してマージされることも予防できます。素晴らしいですね!

上記以外にもバグ検出、品質チェック、脆弱性チェックなど静的解析ツールは色々とあります。プロジェクトにマッチするものは活用して人間が楽できるようにしたいですね。

筆者がJavaで利用した経験がある静的解析ツールはこんな感じです。

# 種類 ツール名
1 Linter Checkstyle
2 Formatter Eclipseフォーマッタ
3 SpellChecker Eclipseスペルチェック
4 バグ検出 SpotBugs
5 品質チェック Coverity
6 脆弱性チェック SonarQube

静的解析ツールに限らず、自動化できる部分は自動化したほうが楽ができますし、ヒューマンエラーが予防できます。

過去にAWS Lambdaを利用したWebアプリ開発の際、追加したAWS Lambdaが出力するCloudwatchに対して監視設定の追加が頻繁に漏れていました。

そのため、Githubにpushした際にCloudFormationのテンプレートをチェックしてAWS Lambdaに対応するCloudwatchの監視設定が存在しない場合は「hoge LambdaにCloudwatchの監視設定がありません」とエラーを出力してGithubActionsがこけるツールを作りました。

まとめ

本記事では、リーダブルコード2章からすぐに実行できて効果が高い部分を解説しました。

「2.6」の静的解析ツールはプロジェクト運用が絡むので「すぐに」とはいかないかもしれません。

しかし、それ以外は明日から即実践できるものになっています。まずはできる範囲から取り入れてみてください。

余談ですが、昔の仕事でDBを検索する「findAll()」メソッドの中にwhere句が書かれていたことがありました。本番障害の対応でソースコードを読んでいたのですが、何度読んでも原因が特定できないまま半日が経過しました。しょうがなく全てのメソッドの中を読み始めたのですが「findAll()」しない「findAll()」メソッドが原因でした。

いい加減な命名は「ダメ。ゼッタイ。