반응형
필자는 '깨끗한' 예외 처리가 클린 코드를 위한 요소 중 하나라고 말한다.
그리고 이를 위한 '깨끗한' 오류 처리의 기준은 아래와 같다.
- 오류 코드를 반환하지 말고 예외 처리를 하라
- 이는 예컨데 DeviceHandle.INVALID와 같은 오류를 값으로 return하고 받지 말고 try..catch로 묶어야 한다.
- try.. catch를 사용하라
- try .. catch 문 자체가 트랜잭션과 같이 범위를 정의할 수 있어 구조적으로 깔끔해진다.
- 미확인 예외를 사용하라
- 이건 뭔가 반대로 생각했었던거임
- 필자는 확인된 예외는 OCP(Open Closed Principle)을 위반한다고 한다.
OCP (개방폐쇄원칙) : 확장에는 열려있어야 하고 변경에는 닫혀있어야한다. - 이는 메서드에서 추가로 확인된 예외를 던졌는데 catch 블록이 3단계 위에 있다면 이 사이의 모든 선언부에 예외를 정의해야 한다는 것으로 즉 변경이 이루어졌을 때 최상위 단계까지 연쇄적인 수정을 해야한다는 것이다.
- 아주 핵심적인 라이브러리를 작성한다면 모든 예외를 잡아주는 것이 맞지만 일반적인 개발 코드라면 의존성이라는 비용이 예외를 확인하는 이익보다 크다고 보고 있다.
- 예외에 의미를 제공하라
- 호출 스택만으로는 예외처리한 코드의 의도를 파악하기는 쉽지 않다.
- 오류 메세지에 정보(실패한 연산 이름, 실패 유형 등)를 충분히 추가한다.
- 호출자를 고려해 예외 클래스를 정의하라
- 전형적인 어떤 오류를 미리 잡아낼지가 아닌 어떻게 오류를 잡아낼 것인가에 대한 내용이다.
- 예시로 호출하는 외부 라이브러리가 있다면 이는 따로 클래스로 만들어 주는 것이 깔끔한 코드를 만들기 좋다.
- 이렇게 하면 일단 라이브러리와 프로그램 사이에 의존성도 줄어들고
- class안에서 세부적인 예외처리를 하고 이를 사용하는 프로그램의 코드에서는 간단하게만 예외처리를 받을 수 있게 작성할 수 있다.
<감싸는 클래스 (Wrapper)없이 예외처리한 케이스>
ACMEPort port = new ACMEPort(12);
try{
port.open();
} catch (DeviceResponseException e) {
reportPortError(e);
} catch (ATM1212UnlockedException e) {
reportPortError(e);
} catch (GMXError e) {
reportPortError(e);
} finally {
...
}
<Wrapper Class를 통해 예외 처리>
- 실제 프로그램에서 외부 라이브러리를 사용할 때는 이렇게 개발자가 임의로 만든 PortDeviceFailure를 받아주기만 하면 된다.
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
- 그리고 세부적인 예외처리는 Wrapper class, 여기서는 LocalPort안에서 작성해준다.
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);
}
}
...
}
- 정상흐름을 정의하라
- 이건 평소에도 많이쓰는 try .. catch를 if else처럼 사용하는 거
- Null을 반환하지 마라
- 이것도 내가 은근히 습관적으로 사용하는거 같은데 Null을 리턴하고 이를 if로 체크하고 그러지 말라는 거임
- 객체나 함수에 대해 Null을 체크를 해야한다면 이를 함수를 사용한 곳에서 체크하는 것이 아닌 함수 안에서 미리 처리하고 밖에서는 똑같이 사용할 수 있게 해야한다.
// Before Code
List<Employee> employees = getEmployees();
if (employees != null) {
for(Employee e : employees) {
totalPay += e.getPay();
}
}
// After code
List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}
public List<Employee> getEmployees() {
if ( ...직원이 없다면... ) {
return Collections.emptyList();
}
}
- Null을 전달하지도 마라
- null을 받는게 정상적인 상황이 아닌 이상을 Null을 받는다고 Null를 그대로 반환해선 안 된다.
- 이런 경우 assert를 통해 의도적으로 예외를 발생시키는 것도 괜찮다고 한다.
반응형
'개발 일반' 카테고리의 다른 글
잊을 때쯤 한번 다시 읽어봐야할 Clean code에 관하여 (시스템) (0) | 2021.04.06 |
---|---|
개발 일반잊을 때쯤 한번 다시 읽어봐야할 Clean code에 관하여 (경계) (0) | 2021.03.23 |
잊을 때쯤 한번 다시 읽어봐야할 Clean code에 관하여 (주석) (0) | 2021.03.15 |
잊을 때쯤 한번 다시 읽어봐야할 Clean code에 관하여 (함수) (0) | 2021.03.15 |
잊을 때쯤 한번 다시 읽어봐야할 Clean code에 관하여 (의미 있는 이름) (0) | 2021.03.15 |