バグが発生し易いコードのパターン

 以下の様なコードの状態を発見したら、できるだけリファクタリングによる修正を試みましょう。

 同じようなコードが複数のクラスに存在している

状態

 同一のフィールド定義を持ち、一部のみ異なっている同じようなコードが複数のクラスの中に十数行にわたって存在している。

発生するバグ

 バグを修正しても、同様のバグが別の処理で発生する。
 「とりあえず同じような処理をしているソースをコピーして利用しよう」という発想から発生し易い。

修正・予防

 可能な限り共通コードを括り出し新しいメソッドとして定義する。共通的な処理を新しいメソッドの呼び出しで対応するように変更する。
 コードのコピー&ペーストを安易に行わないようにしましょう。

 null値で空状態を表すクラスがある

状態

 規定(引数を取らない)コンストラクタや全てのプロパティを受け取らないコンストラクタにおいて、自分自身を表すプロパティまたは再帰的に使用するデータ型に null値を設定している。
 複合型クラスのインスタンス化において対象となるクラスを戻す際、条件に合わない時に null値を戻している。

発生するバグ

 特定の条件で生成されたクラスにおいて "null pointer exception"が発生する。
 或いは、特定の条件で生成したクラスを利用すると "null pointer exception"が発生する。
 空状態、或いは未定義の状態を表すのに null値を使用することにより発生します。

修正・予防

 空状態を表すクラスを定義(作成)し、null値の設定を定義された空状態のクラスに代入に置き換える。
 空(初期)状態を表現するのに null値の使用は避ける、特に再帰的に使用される複合データ型では使用しない。

 エラー処理の戻り値が nullになっている

状態

 エラー処理の終了が、"return null;"となっている。
 また、エラーの発生が予測されるメソッドの呼び出しで、例外のキャッチも戻り値の nullチェックもしていない。

発生するバグ

 メソッド呼び出しで取得したオブジェクトの使用箇所で"noll pointer exception"が発生する
 Javaでは戻り値として1つのオブジェクトしか指定できないため、エラーとなった通知で null値を戻しがちである。また、オブジェクトを戻すメソッド呼び出しの連鎖で記述することが可能なので nullチェックを怠りがちになるために発生します。
 例外を発生させないのが堅牢なクラスであるという発想からエラーの発生時に null値を戻す事があるが、これは誤っています。予想外の例外(作成者も意図しておらず、ドキュメントにも記載されていない例外)を発生させるべきではないが、明示的に例外を発生させるのは正しい処理と言えます。
 例外のスローを定義することにより、利用者に対して明確にエラーの対応が必要なことを伝えることになり、また、詳細なメッセージを設定し、伝えることが可能となります。

修正・予防

 エラー処理で null値を戻すのではなく、例外をスローするように変更する。それに伴いこのメソッドの呼び出し側で例外をキャッチするように修正する。(呼び出し側は修正をしないとコンパイルエラーとなります)
 或いは、該当メソッドを呼び出している処理をすべて洗い出し、null値の対応を行う。  エラー状態の通知には例外を発生させるようにする。また、コアパッケージにも null値を戻すものもあるので、JavaDoc等で戻り値に null値の記載がある場合には、null値の対応を忘れないようにする。

 同じオブジェクトに属するクラスで定義されているメソッドが異なる

状態

 状態が異なる事を表す同じオブジェクト(同一のクラスを継承している子クラス群)のクラスにおいて定義されている publicメッソドが異なっている。
 また、子クラスのみに定義しているメッソドを利用するためにダウンキャストの処理を行っている。

発生するバグ

 データ構造を再帰的にたどって行く処理や、特定の状態のときのみ処理を行うところで"class cast exception"が発生する。
 子クラスのオブジェクトが実装すべきメッソドを親(supper)クラスが定義していないために、子クラスで定義されているメソッドを使用するために、取得したクラスをダウンキャストしなくてはならなくなっている。
 親クラスで未定義なため、子クラス間で定義されている publicメソッドが異なってしまった。
 子クラスへのダウンキャストで instancsof によるチェックは省略されがちである。

修正・予防

 子クラスで定義されている publicメソッドを親クラスで abstract属性で定義し、未定義の子クラスに対してメッソドの追加を行う。また、ダウンキャストの処理を行わないように修正する。
 或いは、ダウンキャストしている処理で instanceof によるチェックを行い、適切な処理を追加する。
 同一オブジェクトを表すクラスで必要となる publicメソッドは、親クラスで abstract属性で定義し、ダウンキャストが必要とならないようにしましょう。

 外部からの入力データに対してチェックを行っていない

状態

 外部ソース(システム内部で生成されたのではないデータソース)からの登録処理などで、データの整合性などのチェックを行っていない。
 他システムでプログラムで生成されたデータや、旧システムからの移行データなどは信用してしまい、データチェックを省略しがちとなっていしまう。

発生するバグ

 データの保管および操作を行う箇所で、同一処理を行っているタスクの中でノーマルに処理されるケースと異常終了するケースが発生する。
 内部データの一部に破壊された(不整合な)データが存在するために発生してしまった。

修正・予防

 破壊されたデータを修復するか、或いは除去してしまう。
 外部ソースを利用する際は、少なくとも構文チェックや形式チェックは行うようにしましょう。

 オーバーロードしたメッソドが異なった処理を行っている

状態

 オーバーロードしたメソッドの処理がオーバーロードされたメソッドとは全く異なる処理を行っている。
 より明確な引数となるオーバーロードにより、新しい処理を追加することは一般的な手法と言えますが、その際、オーバーロードしたメッソドを使用することとなる処理を洗い出すのは困難です。その為、予想していない処理でオーバーロードしたメソッド呼び出しが行われてしまいます。

発生するバグ

 メソッドのオーバーロードを追加した直後に、変更をしていない処理が異常終了する。或いは、予想していない処理結果となってしまう。
 オーバーロードを行ったことにより、それまでオーバーロード前の処理を行うはずのところで、オーバーロードしたメソッドが呼び出されたことにより発生します。

修正・予防

 新しいメソッドをオーバーロードではなく別のメソッド名で定義する。
 エラーとなっている処理でメソッドの呼び出しにおいてアップキャストする。
 xxxCall(parm) ⇒ xxxCall((Object)parm) など
 メソッドの引数はできるだけ限定したクラスを使用するようにし、オーバーロードを避けるようにする。
【OverLoad と Override】
 ・オーバーロード:既に存在するメソッドに対してより限定したクラスを引数としたメッソドを定義すること
 ・オーバーライド:親クラスに損じするメソッドと同一の引数のメッソドを子クラスで定義すること

 十数行を含むif...else if...が連続している

状態

 異なるデータ型や異なる状態を扱う為に、if(..){...} else if(..){...} else if ...や switch(..) case :... case :...文による特定フィールド(タグ:一般にクラス変数による定義かパラメータで指定される)値の判定による処理を行っている。
 オブジェクト指向型言語以外の言語では他に手段がないので一般的な方法であり、Javaでも記述が可能なため用いられやすい。また、想定していない値が発生した場合の処理が考慮されていないケースも多い。

発生するバグ

 バグというよりは、「概念的に異なるデータ型を一つのクラスで処理を行っている(オブジェクト指向的でない)」、「特定のタグ値に対する処理の変更やタグ値の追加に対して修正や拡張がしづらい(Open-Closed Principle「モジュールは修正に対して閉じており、拡張に対して開かれていない」を満たしていない)」といった問題である。
 指定するタグ値によっては正しい結果を得ることができないケースが発生する。

修正・予防

 概念的に異なるデータ型や異なる状態に対しては個別のクラスとして定義し、条件文による振り分けではなく使用するクラスを指定することにより個別の処理に対応するようにする。
 if(..){...} else if(..){...}や switch case文となる処理が必要な場合は、それらが実は別のオブジェクトの処理でないかを考慮し、別々のクラスを用意することによる対応を行いましょう。
 Java言語の特徴の一つは強く型付けされていて、型エラーの可能性を実行以前のコンパイル段階で除去できることです。この特徴を有効に利用するにはクラス(オブジェクト)を必要数分正しく定義することが必要です。

 リソースの取得と解放が別のクラスになっている

状態

 リソースの獲得処理と解放処理が別々のクラスで行われている。
 通常このようなケースが発生するのは呼び出し元のクラスでリソースを獲得し、リソースを置けとったクラスが終了処理時にリソースの開放を行う処理となっています。

発生するバグ

 リソースが早く解放されたことによる例外の発生、或いは解放されないことによるリソースのリークが発生する。
 実行経路の幾つかで、正確に一回のみ開放すべきリソースを正しく解放していない。

修正・予防

 リソースを獲得しているメソッド又はクラスの中でリソースの開放を行うようにする。

 インターフェースで定義されているメソッドで特定のクラスのみ動きが異なっている

状態

 明らかに必要と思われるチェックを特定のクラスのみ行っていない。一般的に考えて特定のクラスの戻り値が異常である。常識的に考えて指定のメソッドとしては明らかに的外れの処理を行っている。など
 一般的に常識と思っていることは、説明を省略しがちでありプログラマに伝わっているかの確認も行われない。一方、interfaceのメソッド名などから実装の機能を想定するのは困難なことが多い。

発生するバグ

 インターフェースで規定しているメソッドが暗黙の期待に従った動きをしない。或いは同じインターフェースを実装しているクラスのうち特定のクラスの場合のみ期待通りの結果を取得できない。
 interface または abstract属性のメッソドの機能が正しく伝わらず誤った実装が行われた結果です。

修正・予防

 説明が不完全なメソッドに対してJavaDoc形式で、「契約による設計(Design by Contract)」に基づいた条件を表明し、その表明に基づき実装されているコードをチェックする。
  ・事前条件:コードブロックの実行前に成立すべき条件(引数チェック、状態チェックなど)
  ・事後条件:コードブロックが終了した際に成立すべき条件(戻り値の条件など)
  ・不変条件:オブジェクトの状態において常に成立する条件
 最も効果的な方法はこのインターフェースに対してユニットテストを記述することです。junitに対応した各メソッドに対する unit test class を作成することにより、実装すべき機能がより明確になります。

 スレッドクラスにおいて例外をスローしていない

状態

 スレッドクラス或いはスレッドから利用されるクラスが例外をスローしていない。また、スレッドを予備ダウプログラムが例外をキャッチしていない。
 スレッドクラスは異常終了させずに終了させるのが基本となります。即ち、全ての例外をスローしなければなりません。同様にスレッドから呼び出される処理にも同様の処置が必要となります。
 スレッドの呼び出し側では例外発生時にそれをキャッチし適切な処置を行う必要があります。

発生するバグ

 プログラムが突然フリーズする。或いは、プログラムは終了しているのにスタックトレースが出力される。
 プログラムがスレッドからの入力を待機しているが、対象のスレッドで発生した例外がスローされないまま終了することにより発生します。

修正・予防

 スレッドクラスでは全ての例外をスローするように指定する。メインとなるスレッドに例外処理を追加し、適切な処理を行うように修正する。
 スレッドを利用する場合には全ての例外をキャッチし必要な処理を行うようにしましょう。

 コンストラクタで未設定のままのクラス変数が存在する

状態

 一部或いは全てのクラス変数に対して初期化(初期値の設定)を行っていないコンストラクタがある。
 クラス設計においてそのクラスを利用する処理及び利用方法が限定されていて、利用者はそのクラスの作成者が意図した使用手順を理解しており、その通りに使用されるものとして作成されている。

発生するバグ

 未初期化のクラス変数へのアクセスにより null pointer exception が発生する。
 また、処理コードがクラス変数が初期化されていないことを考慮していないために、想定外の結果が発生する。

修正・予防

 1.全クラス変数を null値以外の初期値を設定するように、全コンストラクタを修正する。
  ・null pointer exceptionの発生を抑止します。ただし、初期値での処理結果を保証するものではありません。
 2.全クラス変数を設定するコンストラクタを追加し、問題のあるコンストラクタはそれを呼び出すように修正する。
  修正方法が異なるだけで、基本的には上記と同じことです。
 3.isInitializedメソッドを追加して、処理コードの前に呼び出してチェックし、エラーの場合は適切な処理を行うように修正する。
  ・Errorの原因が分かりやすい例外の発生が可能となります。ただし、呼び出し側も例外をキャッチする修正が必要となります。
 4.未設定状態を表すクラスを用意し、初期値を設定していないクラス変数に対して初期値としてこのクラスのインスタンスを設定するように修正する。
  ・null pointer exceptionの発生を抑止するだけでなく、初期化されていないクラス変数へのアクセスに対して柔軟な例外な発生の制御が行えます。