BOOK Preview

Code Complete 第2版 上・下
― 完全なプログラミングを目指して

第6章 クラスの作成

マイクロソフトプレスの書籍紹介ページ
書籍情報のページ
2005/03/29


 本コーナーは、.NET関連の新刊書籍から主要なチャプターをそのまま転載し、その内容を紹介するものです。

 今回は、日経BPソフトプレス/マイクロソフトプレスより2005年3月28日に発行の書籍『Code Complete 第2版 上 ― 完全なプログラミングを目指して』より、同社の許可を得てその内容を転載しています。

 同書は、11年前に出版された名著「Code Complete」の第2版です。第2版では、全体をとおしてオブジェクト指向の考え方が反映され、リファクタリングの章なども追加されています。また、開発言語としてC#やVisual Basic .NETも取り上げられています。“完全な”コーディングのための鉄則を凝縮した本書は、開発者ならば必読といえるでしょう。

 本記事では「第6章 クラスの作成」の前半部分を転載しています。クラスを記述しようとすると、どちらの実装がより美しいのだろうかといった疑問にぶつかることがよくあります。そういった疑問を解決するための指針が本章には満載されています。

 なお、書籍の詳細については書籍情報のページをご覧ください。

ご注意:本記事は、書籍の内容を改訂することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

 コンピューティング時代の幕開け当時、プログラマはステートメント(命令文)の見地からプログラミングについて考えていた。そして1970年代から1980年代にかけてはルーチンの見地からプログラムについて考え、21世紀に入ると、クラスの見地からプログラミングについて考えるようになった。

 クラスとは、強くて明確な責務(responsibility)を共有するデータとルーチンの集まりである。また、共通のデータを使用しない場合でも、一貫したサービスを提供するルーチンの集まりをクラスと呼ぶこともできる。コードのある部分で作業しているときに無視しても問題のない部分をできるだけ増やすことが、優秀なプログラマになるための鍵である。クラスはその目標を達成するための第一のツールである。

 本章では、高品質なクラスを作成するための選りすぐりのアドバイスを紹介する。オブジェクト指向の概念についてまだウォーミングアップ中であれば、本章の内容は少し難解かもしれない。「第5章 コンストラクションにおける設計」を読んだ後、「6.1 クラスの基礎:抽象データ型(ADT)」から読んでいくと、その後の節にすんなりと進むことができる。クラスの基礎ならもう十分に理解しているという場合は、6.1にざっと目を通した後、6.2のクラスインターフェイスの説明に飛び込もう。なお、章末の「6.7 参考資料」で、入門書、専門書、およびプログラミング言語別の資料を紹介している。

6.1 クラスの基礎:抽象データ型(ADT)

 抽象データ型(Abstract Data Types:ADT)とは、データとそのデータに作用する操作をまとめたものである。操作は、プログラムの他の部分にデータを説明し、それらの部分でデータを変更できるようにする。「抽象データ型」の「データ」は、大まかな意味で使用されている。グラフィックスウィンドウとそれに作用するすべての操作、ファイルとファイル操作、保険料率表とその操作はすべてADTであり、他にもいろいろなものが考えられる。

 ADTを理解することは、オブジェクト指向プログラミングを理解するうえで欠かせない。ADTを理解していないプログラマは、名ばかりの「クラス」を作成する。そうしたクラスは、関係の弱いデータやルーチンを無理やりまとめるための携帯用ケースにすぎない。ADTを理解しているプログラマが作成したクラスは、最初の実装が容易で、後からの変更も簡単である。

参照
最初にADTについて考え、次にクラスについて考えることは、言語の中でのプログラミングに対する言語の中へのプログラミングの一例である。第4章の「4.3 テクノロジの波に乗って」、および下巻第34章の「34.4 言語の中へのプログラミング」を参照。

 伝統的に、プログラミングに関する書籍は、ADTの話題になると突然数学的になり、「抽象データ型とは、数学的モデルとそれに対して定義された処理をまとめたものと考えることができる」といった論調になる。このような書籍は、まるでプログラマがADTを睡眠補助薬以外の目的で使ったことがないかのような印象を与える。

 ADTに対するこうした無味乾燥な説明は、論点が完全にずれている。ADTは、下位レベルの実装エンティティではなく現実世界のエンティティを操作するために使用できる、とても刺激的なものだ。リンクリストにノードを挿入するのではなく、たとえばスプレッドシートにセルを追加したり、ウィンドウの種類を1つ増やしたり、列車シミュレーションに新しい客室を追加したりできる。下位レベルの実装ではなく、問題領域で作業できる威力を存分に味わいたい。

6.1.1 ADTの必要性を示す例

 手始めに、ADTが役立つ状況を1つ挙げてみよう。詳しい説明はそれからだ。

 画面へのテキスト出力を制御するプログラムを書いているとしよう。このプログラムは、さまざまな書体、フォントサイズ、フォント属性(太字、斜体など)を使用する。このプログラムにはテキストのフォントを操作する部分がある。ADTを使用すると、フォントルーチンとそれらが処理するデータ(書体名、フォントサイズ、フォント属性)を1つのグループにまとめることができる。このフォントルーチンとデータをまとめたものが、ADTである。

 ADTを使用しなければ、その場しのぎの方法でフォントを処理することになる。たとえば、フォントサイズを12ポイントに変更する必要があり、そのサイズが16ピクセルであるとすれば、次のようなコードを書くことになるだろう。

currentFont.size = 16

 ルーチンをライブラリにまとめている場合は、コードがもう少し読みやすくなるだろう。

currentFont.size = PointsToPixels( 12 )

 あるいは次のように、属性により具体的な名前を付けることもできる。

currentFont.sizeInPixels = PointsToPixels( 12 )

 しかし、currentFont.sizeInPixelsとcurrentFont.sizeInPointsを両方とも定義することはできない。なぜなら、これらのメンバデータを2つとも使用したら、currentFontはどちらを使用すればよいかわからなくなってしまうからだ。また、プログラムの複数の場所でサイズを変更すると、プログラムのあちこちに似たような行が散らばってしまう。

 フォントを太字に設定する必要がある場合は、論理和(or)と16進数定数0x02を使って、次のようなコードを書くとしよう。

currentFont.attribute = currentFont.attribute or 0x02

 運が良ければ、もう少し明瞭な方法が見つかるが、その場しのぎの方法でできることは、せいぜいこれくらいか、

currentFont.attribute = currentFont.attribute or BOLD

あるいは次のようなものだろう。

currentFont.bold = True

 フォントサイズと同様に、クライアントコードはメンバデータを直接制御しなければならないので、currentFontの使用法は制限されてしまう。

 このようなプログラミングでは、プログラムは似たような行だらけになるだろう。

6.1.2 ADTを使用する利点

 その場しのぎの方法が悪いプログラミングプラクティスであることが問題なのではない。それを次のような価値をもたらす良いプログラミングプラクティスに置き換えられることが問題なのである。

■ 実装の詳細を隠ぺいできる
 フォントのデータ型に関する情報を隠ぺいすると、データ型を変更する場合に、プログラム全体に影響を及ぼさずに1か所で変更できるようになる。たとえば、実装の詳細をADTで隠ぺいしないとしたら、データ型を最初の太字の型から別の型に変更するには、1か所ではなく、太字が設定されているすべての場所でプログラムを変更する羽目になる。また、情報を隠ぺいすれば、メモリに格納していたデータを外部記憶装置に格納することにした場合でも、すべてのフォント操作ルーチンを別の言語で書き直すことにした場合でも、プログラムの他の部分に影響が及ばなくなる。

■ 変更がプログラム全体に影響しない
 フォントの数を増やしたり、フォントがサポートする処理(スモールキャピタル、上付き文字、取り消し線など)を増やしたりする必要がある場合に、1か所でプログラムを変更できる。変更がプログラムの他の部分に影響を及ぼすことはない。

■ インターフェイスが提供する情報をより明確にできる
 「currentFont.size = 16」のようなコードは紛らわしい。16はピクセルサイズともポイントサイズともとれるからだ。前後関係からは、どちらのことなのかわからない。同じような処理をすべてADTに集めれば、インターフェイス全体をポイント単位またはピクセル単位で定義するか、またはこの2つを明確に区別することができるので、それらを混同することはなくなる。

■ パフォーマンスを改善しやすくなる
 フォントのパフォーマンスを改善する必要が生じたとき、プログラム全体を苦労して調べていく必要はない。明確に定義されたルーチンをいくつか書き換えるだけでよい。

■ プログラムの正しさがより際立つ
 「currentFont.attribute = currentFont.attribute or 0x02」といったステートメントが正しいかどうかを確認する面倒な作業が、「currentFont.SetBoldOn()」の呼び出しが正しいかどうかを確認するといった簡単な作業で済むようになる。1つ目のステートメントでは、構造体の名前が間違っている、フィールド名が間違っている、処理が間違っている(orのはずがandになっているなど)、属性の値が間違っている(0x02のはずが0x20になっているなど)といった可能性がある。2つ目のcurrentFont.SetBoldOn()呼び出しで何かを間違える可能性があるとしたら、それは呼び出すルーチンの名前くらいのものなので、ステートメントが正しいかどうかの判定は容易につく。

■ プログラムがひとめでわかるようになる
 「currentFont.attribute or 0x02」というステートメントは、0x02をBOLDなど0x02が表すものに置き換えれば改善できるものの、「currentFont.SetBoldOn()」といったルーチンの呼び出しの読みやすさとは比べものにならない。

 Woodfield、Dunsmore、Shenはある調査で、コンピュータサイエンスを専攻している大学院生と大学4年生を対象に、2つのプログラムに関する問題を出した。1つは機能に沿って8つのルーチンに分割されたプログラムで、もう1つは8つのADTルーチンに分割されたプログラムだった(Woodfield, Dunsmoreand Shen 1981)。その結果、ADTプログラムを使用した学生は機能分割バージョンのプログラムを使用した学生よりも、30%以上も得点が高かった。

■ プログラムのあちこちでデータをやり取りする必要がない
 ここまでの例では、currentFontを直接変更するか、フォントを処理するすべてのルーチンにそれを渡す必要がある。ADTを使用すれば、currentFontをあちこちで受け渡す必要もないし、グローバルデータに変換する必要もない。ADTはcurrentFontのデータから成る構造体を持ち、そのデータにはADTに含まれているルーチンからしか直接アクセスできない。ADTに含まれていないルーチンは、そのデータとは関係がないものということになる。

■ 下位レベルの実装構造ではなく、現実世界のエンティティを扱うことができる
 フォント関連の操作を定義する際に、プログラムのほとんどの部分が、配列アクセスや構造体の定義やTrue/Falseではなく、フォントという見地から処理を行うようにできる。

 この場合、ADTを定義するには、たとえば次のように、フォントを制御するためのルーチンをいくつか定義する。

currentFont.SetSizeInPoints( sizeInPoints )
currentFont.SetSizeInPixels( sizeInPixels )
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )

 これらのルーチンに含まれるコードはおそらく短いもので、前述のフォント問題で示したその場しのぎの方法と似たようなものになるだろう。ただし、フォントの操作を一連のルーチンに分離するという違いがある。これにより、プログラムでフォントを処理する部分の抽象性が強化され、フォントの操作を変更するときの防御層となる。

 

 INDEX
  Code Complete 第2版 上・下
  第6章 クラスの作成
  1.6.1 クラスの基礎:抽象データ型(ADT)(1)
    2.6.1 クラスの基礎:抽象データ型(ADT)(2)
    3.6.2 良いクラスインターフェイス(1)
    4.6.2 良いクラスインターフェイス(2)
    5.6.3 設計と実装の問題(1)
    6.6.3 設計と実装の問題(2)
    7.6.4 クラスを作成する理由
    8.6.5 言語固有の問題/6.6 クラスを超えて:パッケージ/6.7 参考資料/6.8 まとめ
 
インデックス・ページヘ  「BOOK Preview」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間