엄범

 

```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번 케이스

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

 

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

 

3번 케이스

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마다 별도 코드 다 따는게 아니라 코드 하나를 같이 쓸 수 있는 경우가 많아서 필요한 경우에만 따면 된다.

`` 성공, 실패`` 같은건 어디든 공통으로 사용할 수 있는거니까

 

통합 코드와 별개로 개별 코드도 필요할 수 있음.

통합 코드는 말그대로 보편적인 것만 나타내는거고,

비즈니스에서 사용하는 세부적인 개별 코드 (e.g. U, Y, N 등)들을 모두 통합 코드에 넣는건 구조적으로 좋지 않다.

 

게다가 반환하고 싶은 코드가 2개인 경우는? 통합코드는 1개만 반환할 수 있는데. 통합 코드만 사용하면 이런 상황에 마주칠 수 있다는 말이지...

그리고 해당 코드를 사용하는 곳이 1군데 밖에 없는데? 통합 코드에 추가한다? 뭔가 애매하다.

 

그래서 통합 코드는 보편적인 것만. 세부적인건(필드 응답으로 나가는 enum값 정도?) 개별 api에서 관리.

 

응답 코드를 구성할 때는 클라이언트의 관심사가 무엇이냐를 생각해야 한다.

외부에서 받은 응답 코드 그대로 내려주는 것이 항상 좋은 방법은 아닐 수 있음.

클라이언트가 필요로 하지 않는 정보라면 응답 코드를 적당히 추상화해서 내려주는 것도 필요하다.