엄범

 

```java

Result<T, R> 타입

{

    "code" : T

    "message" : 

    "data" : R

}

```

 

보통 개발을 하다 보면, 자주 마주치는 상황이, 예를 들면 

  1. `` 인증성공/추가인증필요/인증실패/에러`` 와 같은 경우.
  2. `` 성공/1번사유로실패/2번사유로실패/3번사유로실패`` 와 같은 경우.
  3. `` 성공1,URL리턴 / 성공2,JWT리턴`` 같은 형식으로, 한 api가 상황에 따라 형태가 다른 리턴값을 반환해야 하는 경우.

 

그러면 뒤따르는 고민은

  • [code & data]를 묶은 타입을 하나 정의하고, 이 타입을 반환할지?
  • 성공 빼고는 다 throw Exception 해서 처리할지?

 

클린 코드에서는, ErrorCode 리턴 보다는 Exception을 던지는 것이 좋다. 라고 말하고 있는데 (아래쪽에 있음)

정말로 ErrorCode만 리턴할 바에는 Exception이 낫고,

ErrorCode가 아니라 ResultCode라면 Exception에 던지는 것 보다는 Result<T> 리턴이 낫다

 

1번 케이스에서 [추가인증필요] 같은 결과는 예외라기 보다는 엄연한 결과이고, 예외 케이스와는 구분되어야 한다고 생각됨.

이런 케이스는 성공 빼고는 모두 throw Exception 해버리기 보다는 [code & data]를 묶은 리턴 타입으로 처리하는 것이 더 나아보임.

 

반면 2번 케이스 같은 경우 성공 빼고는 모두 실패, 또는 예외 이므로 Exception으로 처리하는 것이 자연스러울 수 있다.

 

조금 더 세부적으로 생각하면 실패와 예외를 구분해서, 실패 또한 [code & data]에 집어넣을 수 있겠고, 이런 경우 2번 케이스도 예외가 아닌 정상 실패라면 [code & data]를 묶은 리턴 타입으로 처리하는 것이 자연스럽다.

 

3번 케이스는 무조건 `` Result<T,R>``을 사용하는 편이 좋은게...

  • '성공1 빼고 다 Exception'으로 처리하게 되면, '성공2,JWT'라는 반환값에 대해서 코드를 봤을 때 잘 파악이 안될 수 있다.
  •  docstring에 성공2 케이스는 Exception으로 처리했다~ 라고 적어두어야 하는데... 어글리하다.
  • 그리고 성공2를 여기서 밖에 안쓰는데 GlobalExceptionHandler에서 처리한다? 더 더 어글리하다. 

 

3번 케이스 같이 어차피 Client 쪽으로 나가는 응답의 형태가 2가지가 되는 경우에는 그 아래 layer에서 부터 Result<>를 끌고 와야 할 수도 있는데, 이는 어쩔 수 없음.

  • 이렇게 하위 layer부터 Result<>를 끌고 오면 상위 layer들의 반환값이 모두 Result로 강제된다는 단점이 있긴 하니까...
  • layer가 많다면, 최하위 layer에서 Exception을 던지고 쭉~ 상위 layer까지 올라와서 받은 다음 Result<>로 바꿔주는 방법을 사용할 수도 있음.
  • 이런건 상황에 맞게 하는거지.

 

 

클린 코드 : ErrorCode 리턴 보다는 Exception을 던지는 것이 좋다. (return vs throw)

  • 간단한 메서드라면 boolean 등을 리턴해도 되지만, 이러한 메서드 들을 조합하는 메인 public 메서드(유형1)인 경우 리턴 타입이 애매해진다.
  • 메서드 내에서 실패 사유가 여러가지라, {성공 여부, 실패 사유, T} 를 같이 리턴해야 한다.
  • 이렇게 에러와 관련된 정보를 return하게되면, return type이 Response<T>와 같은 타입으로 강제될 수 밖에 없다.
  • 물론 이렇게 해도 되기는 하는데... 그 보다는 Exception을 던지되 CommonMessageException 처럼, isIgnorable 을 따지는 것이 괜찮음.

 

https://nesoy.github.io/articles/2018-02/CleanCode-ErrorHandle

위 링크에서 맨 위에 있는 예제를 보면, if 중첩을 try-catch 구조로 변경함.

try-catch로 변경하면서 꽤 많은 추상화 작업을 했다는게 주목할만함.

 

1. 먼저 DeviceShutDownError를 하나 만들어야 하고, 

2. getHandle() 함수도 throw하도록 수정해야하고, 

3. retrieveDeviceRecord()함수도 throw하도록 수정해야 한다.

4. 로직을 tryToShutDown 이라는 별도 메서드로 빼서 sendShutDown은 try-catch를 하는 임무, tryToShutDown은 자잘한 메서드를 호출하는 임무로 나눴다.(이건 굳이 안해도 되는 작업인 것 같다. 단, catch가 여러개가 된다면 빼는게 좋긴 하겠지)

 

리턴 기반 함수를 Exception을 던지도록 wrapping할 때, ...OrException 같은 네이밍은 좋지 않다. 예외야 어차피 항상 발생할 수 있는거니까.

 

Exception을 담아서 던지는게 좀 복잡해져서 단순 catch문에서 이들을 구분하기가 어려워졌다면 다음과 같이 사용하는게 도움이 된다.

```java

Throwable e = ExceptionUtils.getRootCause(_e);

if (!(e instanceof SocketTimeoutException)) {

```

 

 

번외 ) 통합 Code를 쓰는 것의 장점

결국 controller에서 응답 나갈 때, `` { code, data }`` 형태의 응답을 내려주고 싶은 경우

각각의 api 마다 code를 따로 쓸지, 통합 code를 쓸지가 고민이 될텐데

통합 code를 쓰면 backend 응집도는 좀 떨어질지 몰라도, front-end에서 code 관리하기가 굉장히 편하다.

 

그래서 유저 응답으로 나가는, 맨 바깥쪽 layer만은 통합 Code를 사용하는 편이 좋은 듯.

 

이 api에서는 코드 몇번이 뭘 의미하고, 이 api에서는 코드 몇번이 뭘 의미하는지가 다르면... 조금 더 헷갈릴 여지가 있긴 하니까.

차라리 1000번대는 뭐, 2000번대는 뭐, 이런 식으로 번호를 통해 비슷한 응답을 그룹핑하는게 낫고.

어차피 통합 코드 써도 api마다 별도 코드 다 따는게 아니라 코드 하나를 같이 쓸 수 있는 경우가 많아서 필요한 경우에만 따면 된다.