かとじゅんの技術日誌

技術の話をするところ

意図に関係する大事なことがら

最近、DDDの"意図の明白なインタフェース"というパターンの章を読みなおしています。このパターンが一環して主張していることは"名前が重要"ということです。その名前の重要性について、いろいろな文献からの引用を用いて考えてみたいと思います。

名前重要

"名前が重要"といえば、「プログラマが知るべき97のこと」で、まつもと ゆきひろ氏が 「名前重要」というタイトルで名前の重要性について語っています。

適切な名前をつけられると言うことは、その機能が正しく理解されて、設計されているということで、逆にふさわしい名前がつけられないということは、その機能が果たすべき役割を設計者自身も十分に理解できていないということではないでしょうか。

名前が設計と強く結び付いていることがわかる、深イイ言葉です。
名前の決定が難航すると「えぃ、面倒だから適当に名前を付けてしまえ」となりがちです。油断すると結構適当になるもんですw
でも、面倒だからといって横着をせずに、その時にこの言葉を思い出して、「この変数の本質はなんだろうか」と考えて「本当の名前」を探し出す努力をしないといけないと思います。

プログラマが知るべき97のこと

プログラマが知るべき97のこと

命名に関するパターン

そして、Kent Beck氏の実装パターンでも命名に関するパターンがあります。

変数名には、役割を示す名前をつけましょう。

Role-Suggesting Name(役割を示す名前) - Strategic Choice

計算において演じる役割に基づいて変数の名前を付けなさい。

名前を通して自分の意図を完全に伝えるため、長い名前をいとわない姿勢が必要です。名前は入力される回数の何倍も読まれるので、入力の容易さよりも、読みやすさのほうを優先すべきです。同じ理由で「略語」の使用も「間違った節約」といえます。
とはいえ、複数の単語をつなげた極端に長すぎる名前も、逆に情報が多すぎて読みにくくなります。そのような状況に陥ったら、周囲のコンテキストを調べるようにします。「この変数の役割と他の変数の役割を区別するのに、なぜそんなにたくさんの単語が必要となるのだろうか?」と自問自答し、その原因を探ると、役割の再分配のトリガになり、設計をシンプルにできることが多いのです。

たとえば、人の名前を表すバリューオブジェクトに姓名を表す属性が以下のように定義されているとします。果たして、どちらが姓?名?ですか。fがfirstName、lがlastNameと、脳内補完をする人もいますが、fがfamilyNameと誤解する人もいるかもしれません。

public class PersonName {
    private String f;
    private String l;
    // setter, getter 省略
}

はっきりと意図を伝えるために、

public class PersonName {
    private String firstName;
    private String lastName;
    // setter, getter 省略
}

とすべきでしょう。
この場合だと省略はよくない(長すぎるのもよくないのですが)という例ですが、省略だけでなく名前の選び方も気をつけたいところです。

Eclipseで学ぶはじめてのJava 第2版(DVD付)

Eclipseで学ぶはじめてのJava 第2版(DVD付)


言わずと知れた"きむきむ"の本です。この本のP70から「よいプログラムは変数名から」という章があります。あわせて読んでみるとかなり参考になると思います。

メソッド名には、意図を示す名前をつけましょう。

Intention-Revealing Name(意図を示す名前) - Strategic Choice

実行内容の意図に基づいてメソッドを命名しなさい。

確かに、前者のほうがメソッドについて多くの情報を伝えています。しかし、実装戦略(アルゴリズムに線形検索を使用していること)がメソッドを使う側にとって重要でないのなら、「名前」には含めないでおきます。関心があれば、メソッドの本文を見て、実装方法を知ることができるので、名前には意図に専念してもらいます。

この例では、linerCustomerSearchという内部の実装そのものをメソッド名にしていますが、それはメソッドを呼び出すクライアントの関心事ではないので、findの方がベターということを言っています。

実装パターン

実装パターン

PIEの原則

プログラミングの原則にはPIE(Program Intently and Expressively)の原則というのがあります。アジャイルプラクティスを読めばわかりますが、これも命名と密接な関連があります。
PIEの原則 - Strategic Choice
例えば、Javaではクラス名、メソッド名、フィールド名、インターフェイス名、パッケージ名などの名前を使って、プログラムコードを記述します。これらが意図を明確に伝える名前でなければ、必然的にプログラムコードも意図を明確に示すものにはならないでしょう。

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

英語で名前を付けること

これは蛇足ですが、名前にローマ字を使うプロジェクトがありますが、個人的には"バッドノウハウ"だと思っています。このブログを読めば理由がわかります。

日本語は同音異義語が沢山あるので発音だけでは理解しづらいことが多々あります。それを解消するために日本語は漢字を使い分けています。
すべて”ひらがな”で書いた文書が理解しづらい理由は、前後の文脈を読まないと、単語の意味が分からないからです。
また、ローマ字は発音を表記しています。
つまり、ローマ字で単語を書くことは、”ひらがな”で単語を書いていると考えることが出来ます。だから、ローマ字の単語だけでは同音異義語を区別するのが難しいのです。
結局のところ、日本語には同音異義語が多い事と、ローマ字は発音表現であることが組み合わさって読みづらくなっているのです。

ローマ字を採用している理由は、そもそも既存のDBのテーブルやカラムがローマ字だからそれに合わせないと現実的でない、などの、理由で既存のシステムだとどうしようもないシガラミがあるものですが、少なくとも新規開発ではローマ字は回避したほうがいいと思います。
まぁ、ローマ字がだめなら漢字でどうよって発想もあるけど、プログラミングの文化はそもそも英語の文化だし、ソーシャルネットワークが広がれば益々ボーダレス化が進むので、英語を難しいなんて言ってられないと思います。もうあきらめてやるしかないw 私も英語難民ですが、クラス名やメソッド名、変数名を決める時は、ALCで検索してから名前を付けています。
地球人ネットワークを創るアルク:スペースアルク
対義語・反対語などにも気を付けるとよいです。以下、参考URL。
変数名補足 - いいプログラムを書こう
対義語・反対語で時間を取らないために | WebSpaceの中の人
追記:
id:rch850 さんに教えていただきました。これはいい。ありがとうございます!
カラム名/メソッド名の命名のための英語辞書 - CODIC

DDDの意図の明白なインタフェース(INTENTION-REVEALING INTERFACES)パターン

本題のDDD P247から引用。

Name classes and operations to describe their effect and pur-pose, without reference to the means by which they do what they promise.This relieves the client developer of the need to under-stand the internals.
These names should conform to the UBIQUITOUS LANGUAGE so that team members can quickly infer their meaning. Write a test for a behavior before creating it, to force your thinking into client developer mode. All the tricky mechanism should

JavaEE勉強会の訳はこちら。

クラスや操作には、その効果や意図を表す(どうやって実現しているかを参照しなくてもわかる)名前をつけよ。
こうすることで、クライアント開発者は内部詳細を知る必要がなくなる。
これらの名前は、チームメンバがすぐにその意味を推測できるように、ユビキタス言語に沿っているべきである。
クライアント開発者の視点で考えることを忘れないために、クラスや操作を実装する前にテストを書け。
トリッキーなメカニズムはすべてカプセル化によって(意味というより)意図を表す抽象化されたインタフェースの背後に隠すべき。
ドメインの公開されたインタフェースでは、Whatを記述し、Howを記述してはならない。

What How
表すもの 表さないもの
ルールや関連 どのようにして強制されるか
イベントやアクション どのように実行されるか
方程式 どのように解くか
質問 どうやって回答を見つけるか

そして、佐藤さんの記事から引用。

● Intention-Revealing Interfaces(意図の明白なインタフェース)パターン
オブジェクトの内部実装を見なければ正確な使い方が分からないようでは、カプセル化が正しく機能しているとは言えない。意図が分かりにくければ、誤った使い方を誘発することにもなる。クラスやメソッドの名前というのは、開発者同士がコミュニケーションするための絶好の場所である。クラスやメソッドには、どういう方法で実装されているかでなく、その意図を示した名前を付けること。その名前が、ドメインモデルのユビキタス言語に従うようにする。テストファーストを実践して、常にクラスを使う側の視点で考えるようにする。

DDDでも冒頭で紹介した設計思想と同様に意図を持つ名前が重要としています。
さらに、クラスやメソッド内部の詳細を知らなければ理解できないような名前は適切ではなく、名前はユビキタス言語に沿っていなければならないとしています。ユビキタス言語とはドメイン(=問題領域)に登場する言葉です。
また、クライアントにとって意図的なメソッド名を付けるにはテスト駆動開発(TDD)の視点が重要といってますね。そのメソッド名にはHowではなくWhatを付けるのは、前述したIntention-Revealing Nameに通じます。

本書で紹介されている例では、以下のような絵の具(Paint)のクラスが紹介されていますが、このフィールド名は短すぎるし、メソッド名もクラス名と同じ名前で何をするのか、わかりません。

public class Paint {
    private double v;
    private int r;
    private int y;
    private int b;
    // setter, getter省略
    public void paint(Paint other) {
        // ...
    }
}

言わずもがな、量(volume)と色(red,yellow,blue)がわかるように省略しない名前に変更し、絵の具を混ぜるメソッドはmixInとして意図がわかる名前に変更すると、理解しやすいのです。

public class Paint {
    private double volume;
    private int red;
    private int yellow;
    private int blue;
    // setter, getter省略
    public void mixIn(Paint other) {
        // ...
    }
}

DDDでは、つまるところ、命名時には以下のようなことを心がけるべきということなのだと思います。

  • その名前は、意図を示しているか
  • その名前は、ユビキタス言語に沿っているか
  • その名前は、クライアント開発者の視点で分かりやすいか

忘れちゃいけないJavadoc

コードに関しては前述の通りに心がければよいですが、そもそもコードで表現できない意図はどのように表現したらよいでしょうか?
それはずばりJavadocです。(ScalaならScaladoc)
Javadocというとこの本です。

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方


P3から引用

「そのコードをどのようにして使うのか」という情報は、コードだけでは表現しきれません。

List<Customer> findCustomers(String name) throws IllegalArgumentException

というメソッドがある場合、「顧客名を渡すと顧客のリストを返す」メソッドだと想像できます。そういう意味からすると良い名前です。
しかし、本書で以下のように続けています。

  • 顧客が誰一人見つからなかった場合、このメソッドは何を返すのか?
  • 引数nameには顧客の完全な名前を渡す必要があるのか、名前の一部分だけでもよいのか?
  • 引数namelこnullを渡しでもいいのか? 空文字列ではどうか?
  • 結果のリスト内の顧客の順番はどうなっているのか?

確かにコードだけではわかりませんね。。
短絡的に考えれば、コードの中身見ればわかるじゃんということですが、果たしてそうでしょうか。
以下のようにインターフェイスが規定されていて、このJavadocのないメソッドの戻り値はnullを返すのか、空のリストを返すのか、どっちでしょうか?

public interface CustomerRepository {
    List<Customer> findCustomers(String name) throws IllegalArgumentException
}

これに対する実装が2つ存在した場合はどちらの挙動が正しいといえるのでしょうか?

public class CustomerRepositoryForDb1 {
    List<Customer> findCustomers(String name) throws IllegalArgumentException {
        // ...
        if ( /* 顧客が見つからない場合 */ ) {
            return null;
        }
        // ...
    }
}
public class CustomerRepositoryForDb2 {
    List<Customer> findCustomers(String name) throws IllegalArgumentException {
        // ...
        if ( /* 顧客が見つからない場合 */ ) {
            return Collections.emptyList();
        }
        // ...
    }
}

nullを返すのが正しいとしたら、CustomerRepositoryForDb2が間違い、空のリストを返すのが正しければ、CustomerRepositoryForDb1が間違い、もしかしたら、そもそも両方とも正解ということもあるかもしれませんw これじゃコードみてもわかりませんよね。
それに、人によって都合よく解釈されてしまう恐れもありますね。だから Javadocでコードで示せない意図、つまり"仕様"を記述しようということです。この場合だと、例えば、インターフェイス側にJavadocで"検索結果が0件の場合は空のリストを返す"と仕様が、記述されていればCustomerRepositoryForDb1が間違いというのは一目瞭然です。仕様不明による無駄なコミュニケーションも発生しません。
ということで、Javadocは面倒臭いです。でも、仮に、「Javadocを書かない」という選択肢を取るなら、コードで示せない意図をどうやって示すか、代替え案がなければ、その選択肢はあまり意味がない気がします。個人的には、結局、その代替案を実践するほうが、後々面倒なことになるのではないかと思います。
だから、面倒臭いのひとことでは片付けられないと思うんです。意図を大事にするプログラミングだからこそ、Javadocも忘れずに書かねばなりませんね。
以下の記事がおすすめです。
Javadocを書く - しげるメモ
Javadocを書かない - しげるメモ *1

まとめ

どこまで実践するかは、そのプロジェクトの方針によると思いますが、だからと言って簡単にやり過ごすことはできないと思います。
プログラムは書くことより読まれることが多いです。だから、読み手のことを考えて、意図を明確に伝えれないプログラムはプログラムとしての価値が下がってしまいます。手間をかけて作るプログラムだからこそ、読み手にも分かりやすくなければ、費用対効果のあるプログラムとは言えないのではないでしょうか?
ともあれ、何事も”面倒臭い”と思うところに、よいプログラムを作るためのヒントが隠されているのかもしれません。自戒の念を込めて。

(しかし、上記の設計原則やプラクティスから鑑みると、テスト駆動開発(TDD)は設計手法だということが改めてわかりますね。これは収穫でした。)

あわせて読みたい
きれいなソースコードを書くために必要な、たったひとつの単純な事 - よくわかりません

*1:Javadocを書かないことを勧めている記事ではないですw無意味に冗長に書くのではなく、効率的に意図を伝えるJavadocはどう書くのかって意味合いですw