BOOK Preview

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

第34章 ソフトウェア職人気質とは

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

Page1 Page2 Page3 Page4

34.6 問題領域に立ったプログラム

 複雑さに対処するもう1つの方法は、できるだけ高い抽象化レベルで作業することである。高い抽象化レベルで作業する方法の1つは、コンピュータサイエンスの解決策ではなく、プログラミングの問題の観点で作業することである。

 最上位レベルのコードに、ファイル、スタック、キュー、配列の詳細、およびi、j、k以外の名前が思い付かなかった文字についての説明が含まれているのはよくない。最上位レベルのコードは、解決すべき問題を説明するものでなければならない。プログラムが何をするものなのかを正確に示すわかりやすいクラス名やルーチン呼び出しが含まれていなければならず、ファイルを「読み取り専用」で開くといった詳細が含まれている必要はない。最上位レベルのコードに、「iは社員ファイルのレコードのインデックスを表す変数で、その後のコードでは顧客台帳ファイルのインデックスとして使われる」といったコメントの山が含まれていてはならない。

 そのようなプログラミングプラクティスは要領を得ない。プログラムの最上位レベルで、社員データがレコードとして扱われることやファイルに保存されることを知る必要はない。そのような詳細な情報は隠しておくべきである。最上位レベルでは、データの保存方法など見当がつかないくらいでよい。iの意味やそれが2つの目的で使用されることを説明するコメントもいらない。目的が2つあるなら別々の変数を使用すべきであり、それらにはemployeeIndex、clientIndexのような区別のつく名前を付けるべきである。

34.6.1 抽象化レベルでのプログラムの分割

 どこかで実装レベルの観点で作業する必要があるのはもちろんだが、実装レベルの観点で作業する部分と問題領域の観点で作業する部分とを分けることができる。プログラムを設計する際には、図34-1に示す抽象性のレベルについて検討しよう。

図34-1 プログラムはいくつかの抽象化レベルに分割することができる。良い設計では、作業時間のほとんどを上位のレベルに割くことができ、下位のレベルは無視することができる

●レベル0:オペレーティングシステムの処理とマシン命令

 高級言語を使用する場合、下位レベルは言語が自動的に処理してくれるので、それについて心配する必要はない。低水準の言語を使用する場合は、その上位の層を作成して、そこで作業すべきである。しかし、多くのプログラマはそのようなことはしない。

●レベル1:プログラミング言語の構造とツール

 プログラミング言語の構造とは、言語の基本データ型や制御構造などである。ほとんどの一般的な言語には、追加のライブラリやシステムコールなどが含まれている。それらなしにはプログラムできないので、必然的にそれらを使用することになる。多くのプログラマは、これよりも高いレベルの抽象性を扱うことはない。それにより、自分たちの作業を必要以上に難しくしている。

●レベル2:下位レベルの実装構造

 下位レベルの実装構造とは、言語自体が提供するものよりも少し高いレベルの構造である。一般的には、スタック、キュー、リンクリスト、ツリー、インデックス付きファイル、シーケンシャルファイル、ソートアルゴリズム、検索アルゴリズムなど、大学のアルゴリズムやデータ型の授業で学ぶ処理やデータ型がこれにあたる。プログラム全体がこのレベルのコードで構成されている場合、あまりの詳細さに圧倒されて、複雑さを克服することはできないだろう。

●レベル3:下位レベルの問題領域の表現

 このレベルには、問題領域の観点から取り組むのに必要な要素が含まれている。このレベルは、その下にある実装構造と、その上にある問題領域のコードとを結び付けるつなぎの層である。このレベルでコードを書くためには、問題領域の語彙を理解し、プログラムで解決する問題に取り組むための構築要素を作成しなければならない。多くのアプリケーションでは、ビジネス層やサービス層がこれにあたる。語彙と構築要素はこのレベルのクラスが提供する。このレベルのクラスはあまりにも原始的なので、直接問題を解決できないかもしれないが、その上のレベルのクラスが問題への解決策を構築するためのフレームワークを提供する。

●レベル4:上位レベルの問題領域の表現

 このレベルは、独自の表現で問題に取り組むための抽象化を実現する。このレベルのコードは、コンピュータサイエンスの専門家でなくてもいくぶん読みやすく、場合によっては技術者でない顧客にも読めるものとなる。このレベルのコードは、プログラミング言語の特定の機能にほとんど依存しない。なぜなら、問題に取り組むためのひととおりのツールを独自に作成しているからだ。したがって、このレベルのコードは、使用している言語の機能ではなく、レベル3で独自に作成したツールに依存する。

 実装の詳細は、この層の2つ下にある層(実装構造の層)に隠ぺいして、ハードウェアやオペレーティングシステムの変更がこの層に一切影響しないようにすべきである。このレベルでは、ユーザーの世界観をプログラムで体現する。なぜなら、プログラムが変化するとしたら、それはユーザーの観点で変化するからだ。問題領域の変化はこの層に大きく影響すると思われるが、下の層の構築要素を使って問題領域でプログラミングすれば、簡単に対応できるはずである。

 多くのプログラマは、これらの概念的な層に加えて、これらの層を別の「層」に分けると効果的であることに気付く。たとえば、一般的な3層アーキテクチャは、これらの層にまたがって、設計やコードを頭で理解しやすくするツールを他にも提供する。

34.6.2 問題領域で作業するための下位レベルテクニック

 問題領域の語彙を使って取り組むための完全にアーキテクチャ化された手法がなくても、本書で解説したさまざまなテクニックを使用すれば、コンピュータサイエンスの解決策ではなく、実世界の問題の観点から取り組むことができる。

  • クラスを使って、問題領域の観点で意味のある構造を実装する。

  • 下位レベルのデータ型とそれらの実装の詳細に関する情報を隠ぺいする。

  • 名前付き定数を使って、文字列や数値リテラルの意味を文書化する。

  • 中間変数を割り当てて、計算の途中の結果を文書化する。

  • ブール関数を使って、複雑な論理評価を明確にする。

34.7 落石注意

 プログラミングは、完全な芸術でも完全な科学でもない。一般に行われているように、それは芸術にも科学にも分類されない「職人技」である。言うなれば、芸術と科学が融合してできた、エンジニアリングの一分野である(McConnell 2003)。芸術であろうと、科学であろうと、職人技であろうと、エンジニアリングであろうと、動くソフトウェア製品を作成するには、やはり個人の判断力が問われる場面が多い。そして、コンピュータプログラミングにおいて的確な判断を下すことには、さまざまな危険信号、つまり、プログラムに問題があることを示すかすかな兆候を見逃さないことが含まれる。プログラミングの危険信号は、問題の可能性に対して注意を促すものの、通常は「落石注意」といった道路標識のように目立つものではない。

 あなたや他のだれかが、「これはずいぶん凝ったコードだな」と言ったとしたら、それは危険信号であり、通常はひどいコードであることを意味する。「凝ったコード」は「悪いコード」の婉曲表現である。凝ったコードであると思うなら、そうではなくなるように書き直すことを検討しよう。

 クラスのエラーの数が平均を超えたら、危険信号である。エラーが起きやすいクラスは、プログラムにおいて最も高価な部分になりがちだ。エラーの数が平均を超えるようなクラスがあるとしたら、そのクラスはその水準を保ち続けるだろう。書き直すことを検討しよう。

 プログラミングが科学であったなら、危険信号は明確に定義された特定の修正措置を示唆するだろう。だが、プログラミングはまだ職人技の域を超えないので、危険信号は検討すべき問題を指し示すだけである。凝ったコードを書き直せる、あるいはエラーの起きやすいクラスを改善できるとは限らない。

 クラスの異常な数のエラーがクラスの品質の悪さを警告するのと同様に、プログラムの異常な数のエラーは、プロセスに欠陥があることを暗示する。良いプロセスは、エラーの起きやすいコードが開発されることを許さない。良いプロセスでは、アーキテクチャのレビュー、設計のレビュー、コードのレビューによって、アーキテクチャ、設計、コードの抑制と均衡が保たれる。コードがテストできる状態になるころには、ほとんどのエラーが取り除かれているだろう。ひときわ優れたパフォーマンスを達成するには、一生懸命働くことに加えて、賢く働く必要がある。プロジェクトのデバッグ作業が多いことは、人々が賢く働いていないことを示す危険信号である。大量のコードを1日で書き上げ、そのデバッグに2週間かけるとしたら、賢く働いているとは言えない。

 設計の評価指標も、危険信号として利用することができる。ほとんどの設計の評価指標は、設計の品質を示唆するヒューリスティックなものだ。クラスに7つ以上のメンバが含まれているからといって、設計がまずいとは限らないが、それはクラスが複雑であることの警告である。同様に、ルーチンに判定ポイントが10か所以上ある、3レベルを超える論理ネストがある、変数が異常なほど多い、他のクラスとの結び付きが強い、またはクラスやルーチンの凝集度が弱いことも、警告と受け止めるべきである。これらの兆候はいずれもクラスの設計がまずいことを意味するとは限らないが、それらが存在するとしたら、クラスを懐疑的に見るべきだろう。

 いずれかの危険信号に気付いたら、プログラムの品質を疑うべきである。Charles S. Peirceが言うように、「疑いとは、不安な満足のいかない状況であり、私たちはそこから抜け出して信念を持てる状態になろうともがく」のである。危険信号を、より満ち足りた信念の状態を探し求めることを促す「疑いの炎症」と考えよう。

 同じようなコードを何度も書いていたり、同じような変更を複数の場所で行っていたりすることに気付いたら、「不安と不満」を感じ取り、制御がクラスやルーチンに適度に一元化されているかどうかを疑うべきである。個々のクラスを簡単に使用できないため、テストケースの足場を組むのが難しいと感じたら、「疑いの炎症」を感じ取り、クラスが他のクラスと密結合しすぎていないかどうかを確認すべきである。相互依存の強いクラスがあるために、他のプログラムでコードを再利用できないことも、クラスの結合が強すぎることを示す危険信号である。

 プログラムの奥深くでは、プログラムの設計にきちんと定義されていない部分があり、コーディングするには不十分であることを示す危険信号に目を光らせる。コメントを付けにくい、変数に良い名前を付けるのが難しい、問題を分解して明確なインターフェイスを持つ凝集度の強いクラスにするのが難しいといった症状は、どれもコーディングの前に設計についてよく考える必要があることを示している。意味のない名前や、コードの一部を簡潔なコメントで説明するのが難しいことも、問題の兆候である。設計を明確に捉えることができれば、下位レベルの詳細はおのずと生じるものだ。

 プログラムが理解しにくいという兆候も見逃してはならない。不快に感じることはすべて手がかりである。あなたにとって難しいものは、他のプログラマにとってはさらに難しい。あなたがそれを改善すれば、彼らはそれに時間を割いてくれたことに感謝するだろう。コードを読むのではなく解明しているとしたら、そのコードは複雑すぎる。難解であるとしたら、そのコードは間違っている。単純になるように書き直そう。

 危険信号を十分に活用したい場合は、そうした警告を出すようにプログラムする。その兆候が何であるかを知った後でさえ、それらを見落とすことは意外に多いので、それを警告するようにしておくと効果的である。Glenford Myersは、エラーの修正に関する調査を実施して、エラーを発見できない最も一般的な原因が、単純な見落としであることに気付いた。エラーはテスト出力に表れていたが、それに気付かなかったのである(Myers 1978b)。

 プログラムの問題を見逃しにくくしよう。1つの例は、ポインタを解放したらそれらをNULLに設定して、解放済みのポインタを誤って使用するとやっかいな問題が起きるようにすることだ。ポインタは解放された後もメモリ上の有効な場所をポイントしていることがある。ポインタをNULLに設定しておけば、常に無効な場所をポイントするようになるので、嫌でもエラーに気付くようになる。

 コンパイラの警告も、まさに見落とされがちな危険信号である。コンパイル時に警告やエラーが出力されたら、それらが出力されないようにプログラムを修正すること。実際に「WARNING」と書かれているものを無視するようでは、微妙な危険信号などとても見抜くことはできない。

 頭の中で危険信号を灯らせることがソフトウェア開発において特に重要なのはなぜだろうか。それは、プログラムについてどの程度思考をめぐらせるかが、プログラムの品質に大きく影響するからである。したがって、思考の程度に対する警告に注意を払うことは、最終的な製品に直接影響を及ぼす。


 INDEX
  Code Complete 第2版 上・下
  第34章 ソフトウェア職人気質とは
    1.34.1 複雑さの克服
    2.34.3 人間が1番、コンピュータは2番
  3.34.6 問題領域に立ったプログラム
    4.34.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 記事ランキング

本日 月間