CleanCode 7章エラー処理

25846 ワード

1.エラーコードではなく例外を使用


既存のエラーコードは次のとおりです.
public class DeviceController {
	...
	public void sendShutDown() {
		DeviceHandle handle = getHandle(DEV1);
		// 디바이스 상태를 점검한댜.
		if (handle != DeviceHandle.INVALID) {
			// 레코드 필드에 디바이스 상태를 저장한다.
			retrieveDeviceRecord(handle);
			// 디바이스가 일시정지 상태가 아니라면 종료한다.
			if (record.getStatus() != DEVICE_SUSPENDED) {
				pauseDevice(handle);
				clearDeviceWorkQueue(handle);
				closeDevice(handle);
			} else {
				logger.log("Device suspended. Unable to shut down");
			}
		} else {
			logger.log("Invalid handle for: " + DEV1.toString());
		}
	}
	...
}
ご覧のように、発信者コードは複雑になります.関数を呼び出すと、すぐにエラーをチェックする必要があります.次のように変なのは簡単です.論理とエラーコードが混在していないためです.
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) {
		...
		throw new DeviceShutDownError("Invalid handle for: " + id.toString());
		...
	}
	...
}

2.Try-Catch-Filly文を書き出します


try-catch-finally文でtryブロックに入るコードを実行すると、任意の時点で実行が停止しcatchに移動します.
  • tryブロックで何が起こってもcatchブロックはプログラム状態の一貫性
  • を維持する.
  • try-catch-finally文を使用してコードを記述することで、呼び出し者の所望の状態
  • を定義することが容易になる.
    テスト駆動開発(TDD)方式での実施方法
    1.単位テストの作成
    @Test(expected = StorageException.class)
     public void retrieveSectionShouldThrowOnInvalidFileName() {
         sectionStore.retrieveSection("invalid - file");
     }
    実装コードは、
  • ユニットに従ってテストされる.
  • (ユニットテストに失敗)
    public List<RecordedGrip> retrieveSection(String sectionName) {
         // 실제로 구현할 때까지 비어 있는 더미를 반환한다.
         return new ArrayList<RecordedGrip>();
     }
  • ファイルアクセスを実現します.
  • public List<RecordedGrip> retrieveSection(String sectionName) {
         try {
             FileInputStream stream = new FileInputStream(sectionName);
         } catch (Exception e) {
             throw new StorageException("retrieval error", e);
         }
         return new ArrayList<RecordedGrip>();
     }
  • のアップグレードを実現しました.
  • (結果を変更せずにコード構造を再調整)
    public List<RecordedGrip> retrieveSection(String sectionName) {
         try {
             FileInputStream stream = new FileInputStream(sectionName);
             stream.close();
         } catch (FileNotFoundException e) {
             throw new StorageException("retrieval error", e);
         }
         return new ArrayList<RecordedGrip>();
     }

    3.未検査の例外の使用


  • 検査異常
    コンパイルフェーズで決定され、処理しなければならない例外.
    (IOException, SQLException etc...)

  • 未検査異常
    実行フェーズで明示的な処理を強制しない例外を特定します.
    (NullPointerException, IllegalArgumentException, IndexOuntOfBoundException, SystemException)

  • 検査の例外はコストがかかるため、相応の利益があるかどうかを考慮する必要があります.
    (OCP違反、メソッドは確認された例外を放出し、catchブロックが3つのステップの上にある場合は、その間のすべてのメソッドに例外を定義する必要があります)
  • 4.例外の意味を提供する

  • エラーの原因と場所を検索するために、スタック情報を十分に追加する必要があります.
    ->エラーメッセージに情報、失敗した演算名、および失敗タイプ
  • を含める

    5.呼び出し元を考慮して例外クラスを定義する

    // 1
     ACMEPort port = new ACMEPort(12);
    
     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 response exception");
     } finally {
         ...
     }
     
     // 2
     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);
             }
         }
         ...
     }
    1番は外部ライブラリを呼び出し、すべての異常は呼び出し者によって検出されます.
    一方、第2の方法は、呼び出しライブラリAPIを迂回して異常タイプを返すことによって簡略化される

    6.nullを返さない


    nullを返す習慣が悪い
  • 呼び出し者にnullを検査する義務を与える.
  • NullPointerExceptionリスク
  • null確認過剰
  • 
    // 1
    List<Employee> employees = getEmployees();
    if(employees != null) {
    	for(Employee e : employees) {
    		totalPay += e.getPay();
    	}
    }
    
    // 2
    List<Employee> employees = getEmployees();
    for(Employee e : employees) {
    	totalPay += e.getPay();
    }
    
    public List<Employee> getEmployees() {
    	if (..직원이 없다면..)
    		return Collections.emptyList();
    }
    1番のようにnullを返すのではなく、2番のように空のlistを返すかassert文を使用します.