第7章-エラー処理


第七章


きれいなコードとエラー処理は確かに関連しています.ほとんどのコードベースは、エラー処理コードに完全に依存します.この言葉の意味は,乱雑なエラー処理コードにより,実際のコードが何をしているのか把握しにくいためである.この章では、いくつかのエラー処理のテクニックと注意事項を紹介します.

エラー・コードではなく例外の使用

public class DeviceController {
        ...
        public void sendShutDown() {
            DeviceHandle handle = getHandle(DEV1):
            if(handle != DeviceHandle.INVALID) {
                ...
                if(record.getStatus() != DEVICE_SUSPENDED) {
                    ...
                }
                else {
                    logger.log(...);
                }
            }
            else {
                logger.log(...);
            }
        }
    }
前述したように、エラーコードを使用すると、呼び出し元のコードが複雑になります.関数を呼び出すと、すぐにエラーをチェックする必要があります.そしてこの段階は忘れがちです.したがって、以下の例外処理を用いることが望ましい.
 public class DeviceController {
        ...
        public void sendShutDown() {
           try {
               tryToShutDown();
           } catch (DeviceShutDownError e) {
               logger.log(e);
           }
        }

        private void tryToShutDown() throws DeviceShutDownError {
            DeviceHandle handle = getHandle(DEV1);
            DeviceRecord record = retrieveDeviceRecord(handle);

            pauseDevice(handle);
            clearDeviceWorkQueue(handle);
            closeDevice(handle);
        }
        
        private DeviceHandle getHandle(DeviceId id) {
            throws new DeviceShutDownError("Invalid handle for: "  + id.toString());
        }
    }
    ...
以上のコードの改良により,コードは非常に簡潔になった.ハイブリッドデバイスを閉じるアルゴリズムとエラーを処理するアルゴリズムが分離された.これでそれぞれの概念を独立して見つめることができます.

Try-Catch-Filly文から


try blockはトランザクションに似ています.tryブロックに何が起こってもcatchブロックはプログラム状態の一貫性を保たなければならない.したがって、例外コードを記述する場合はtry-catch-finally文から始めるのが望ましい.
  • tryブロックで何が起こっても、呼び出し者が望む状態を容易に定義することができる.
  • まず、ファイルがない場合に例外が放出されることを確認するテストがあります.
    @Test(expected = StorageException.class)
    public void retireveSectionShouldthrownOnInvalidFileName() {
    	sectionStore.retrieveSection("invalid - file");
    }
    以下に示すように、retrieveSection()では最初は例外は発生しません.したがって、この関数に例外が発生する可能性があるコードを記述しながらtry-catch文の範囲を定義し、テストに合格できます.
    public List<RecordedGrip> retrieveSection(String sectionName) {
            try {
                ...
            } catch (FileNotFoundException e) {
                ...
            }
            return new ArrayList<RecordedGrip>();
        }
    最初はExceptionを作成してキャプチャできますが、再構築中にFileNotFoundExceptionに例外タイプを縮小できます.
    以上の手順はTDDを利用する.tryブロックのトランザクション範囲から自然に実装されるため、トランザクションの本質を範囲内で維持しやすくなります.

    未確認の例外の使用


    確認された例外は、過去には良い考えだったが、実際にはいくつかの利点も提供されたが、今では必要ではないようだ.多くの言語でも確認の例外は使用されていません.

    確認された例外はOCP違反です。


    確認された例外が投げ出され、catchブロックが3つのステップの上にある場合、その間のすべてのメソッドは宣言子で例外を定義する必要があります.
    ->サブステップでコードを変更する場合は、すべての高度なメソッド宣言を変更する必要があります.
    最上位関数からサブ関数を呼び出す構造では、最下位関数に新しい例外が放出されます.
    try-catch文は、上位関数と最下位関数の間のすべての関数で作成できます.つまり、宣言にはthrows節を増やすべきだ.
  • すなわち位相関数が変化した.
  • サブ関数が投げ出す例外を知る必要があるため,カプセル化は破られる.
  • 例外に意味を与える


    JAvaは、すべての例外で呼び出しスタックを提供していますが、これはまだ十分ではありません.エラーメッセージに情報を含め、例外とともに投げ出す必要があります.

    呼び出し元の例外クラスの定義


    エラーは、デバイス障害、ネットワーク障害、プログラミングエラーに分けられます.アプリケーションでエラーを定義するとき、プログラマーが最も関心を持っているのは、エラーをどのように見つけるかです.
    try {
    	port.open();
    } catch (DeviceResponseException e) {
    	reportPortError(e);
        logger.log("device response exception", e):
    } catch (ATM1212UnlockedException e) {
    	reportPortError(e);
        logger.log("unlock exception", e):
    } catch (GMXError e) {
    	reportPortError(e);
        logger.log("Device reponse exception", e):
    } finally {
    	...
    }
    
    上記のコードは重複しています.しかし、エラーを処理する方法は固定されています.
    1.記録エラー
    2.プログラムの実行を続行できるかどうかを確認します.
    上記のコードは、呼び出しライブラリAPIをカプセル化することによって改善することができる.
    LocalPort port = new LocalPort(12);
    try {
    	port.open();
    } catch (PortDeviceFailure e) {
    	reportError(e);
        logger.log(e.getMessage(), e);
    } finally {
     ...
    }
    
    public class LocalPort {
    	private ACMEPort innerPort;
        
        public LocalPort(int portNumber) {
        	innerPort = new ACMEPort(portNumber);
        }
        
        public void open() {
        	try {
        		innerPort.open();
            } catch (DeviceResponseException e) {
    			throw new PortDeviceFailure(e);
    		} catch (ATM1212UnlockedException e) {
    			throw new PortDeviceFailure(e);
    		} catch (GMXError e) {
            	throw new PortDeviceFailure(e);
            }
        }
    }
    著者らは,LocalPortクラスのように,外部APIをカバーするWrapperクラスを作成することは非常に有用で最良であると述べた.
  • の依存性は大幅に減少した.
  • の他のライブラリを交換するコストも低い.
  • 外部APIを呼び出すのではなく、テストコードを追加する方法で
  • Wrapperクラスをテストする.
  • は、特定のベンダーがAPIを設計する方法に制約されない.
  • 通常フローの定義


    現在,ほとんどのコードはクリーンで簡潔なアルゴリズムのように見えるが,エラー検出は後回しにされている.エラーを検出し、割り込みの計算を処理しますが、割り込みが適切でない場合があります.
    try {
    	MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
        m_total += expenses.getTotal();
    } catch(MealExpensesNotFound e) {
    	m_total += getMealPerDiem();
    }
    以上のコード
  • 食事代を清算し、従業員が清算した食事代を合計します.
  • に食費を請求しなければ、毎日の基本食費を合計に加算します.
  • 例外処理は論理的に感覚的な障害となっている.例外処理と比較してMealExpenseを修正してMealExpenseに戻り、デフォルト値で要求された食事代がない場合はMealExpenseを作成して毎日のデフォルトの食事代を返すだけです.
    これを「特殊事例パターン」と呼ぶ.これにより、クライアントコードは例外を処理する必要がなくなります.

    nullを返さないで


    nullを返すコードは、作業を増やし、問題を呼び出し元に転嫁することです.
    null確認を無視すると、アプリケーション・フローに致命的な問題が発生する可能性があります.
    null漏れ確認は容易に認識できません.
    上のどこがNullPointerExceptionを処理しているのか分かりません.
    List<Employee> employees = getEmployees();
    if (employees != null) {
    	for(Employee e : employees) {
        	totalPay += e.getPay();
        }
    }
    上のコードのgetEmployees()関数がnullを返さない場合はnullをチェックする必要はありません.
    ここでgetEmployees()関数を収集します.EmptyList()を返すように変更した場合は、空のチェックを行うことなく既存の操作と同じになります.

    nullを渡さないで


    メソッドがnullを返すのもよくありませんが、nullを渡すのはもっと悪いです.
    2つの点間の距離を計算するxProjection関数があります.
    nullをパラメータに渡すと、問題が発生する可能性があります.
    この場合、xProjection()関数でnullチェックを行い、異常を放出するために修正する必要があります.
    あるいは、assertを使用して、以下に示すようにしてもよい.
    
    public double xProjection(Point p1, Point p2)
    {
    	assert p1 != null : "p1 should not be null";
        assert p2 != null : "p2 should not be null";
        return (p2.x - p1.x) * 1.5;
    }
    ほとんどのプログラミング言語ではnullのオーバーフローを方法で制御できません.したがってnullを超えることを禁止する政策は合理的である.
    このような政策に従うと、不注意を犯す確率が減ります.