안전한 API 호출 방법에 대한 개선(Feat. 보일러플레이트 코드)

송규빈·6일 전
0

개요

상위 클래스를 두어 해결한 문제점에 대해 제시했던 팀원의 의견과 나의 생각을 적어보고자 한다.

보일러플레이트 코드 줄이기

새롭게 합류한 팀의 코드를 보다가 RemoteDataSource에서 아래와 같은 보일러플레이트 코드를 발견했다.

override suspend fun getUser(): Result<User> = try {
        service.getUser().toResult()
    } catch (e: IOException) {
			...
        )
    }

RemoteDataSource 내 모든 함수들이 try-catch문으로 래핑되어있었고, 그 양은 어마어마했다.
마침 내게도 개발할 새로운 피쳐가 생겼기에 RemoteDataSource를 건드려야 했고, 이를 개선하고자 했다.

불필요한 중복 작업

위와 같은 방식으로 개발을 진행하게 된다면 보일러 플레이트 코드로 인해 코드의 양이 많아지고
관리가 힘들어지고 매번 try-catch를 써야한다는 것은 굉장히 귀찮은 일이다.

또한, RemoteDataSource 클래스를 파악할 때 쓸데없는(?) 코드가 많기 때문에 가독성이 저하된다.
물론 try-catch 자체가 쓸데없다는 것은 아니고, 예외 처리를 하는 것을 굳이 RemoteDataSource 클래스가 알고있어야 하냐는 것이다.

RemoteDataSource의 책임은 API 호출을 담당하는 것이므로 예외 처리를 해도 RemoteDataSource 클래스에서 해도 되지 않냐라는 질문에는 맞다라는 대답을 할 것이다.

하지만 굳이? 라는 생각이 1000번도 넘게 들 것 같다. 예외처리에 대한 역할은 다른 클래스에게 위임하면 되는 것 아닌가..?

개선안

마침 개발해야 할 피쳐가 새로운 API를 사용해야 했기에 RemoteDataSource를 건드려야 하는 상황이었다.
그래서 이참에 try-catch 지옥에서 벗어나기 위해 개선을 시도했다.
(특히 나는 불필요한 반복 작업을 싫어한다..)

우선 설계를 시작했다.
RemoteDataSource는 API 호출에 대한 책임만을 갖게하고,
이를 안전하게 처리하는 책임을 가진 클래스를 하나 두면 내가 느낀 불필요한 중복의 문제점을 해결할 수 있다고 생각했다.

open class BaseRemoteDataSource @Inject constructor() {
    inline fun <T> safeApiCall(apiCall: () -> Response<T>): Result<T> {
        return try {
        ...
        } catch(throwable: Throwable) {
        ...
        }

BaseRemoteDataSource는 이제 안전하게 API 호출을 처리하는 책임을 갖게 되었고, 각 RemoteDataSource에서는 이를 상속받아 safeApiCall를 사용하면 된다.

// 기존
override suspend fun getUser(): Result<User> = try {
        service.getUser().toResult()
    } catch (e: IOException) {
			...
        )
    }
    
 // 개선
    override suspend fun getUser(): Result<User> = safeApiCall {
        service.getUser()
    }
    

팀원의 제안

이러한 개선안을 팀 내 공유했고 의견을 받았다. 여러 의견들이 있었다.
클래스명에 대한 의견이 있었고 다수결에 따라 결정했다.(위 예시에 나온 BaseRemoteDataSource는 아니다.)

이외에 한 팀원이 이러한 제안을 했다. "사용하기 편하게 단일 파일 함수로 빼서 관리하는 건 어때요?"
여기서 많은 의문이 들었다. "과연 정말 사용하기 편할까?", "지금 방식대로 클래스로 관리하는 것이 무슨 장점이 있을까?" 등등
고민 끝에 해당 제안에는 반대되는 입장을 표했다.

애초에 저 클래스를 만들게 된 의도는 'API 호출에 대한 안전성 보장' 책임을 줌으로써 DataSource에서는 호출의 책임만 가지게 한 뒤 중복 제거 및 안전성을 가져가려 했던 것이기 때문이다.

내가 생각했을 때 단일 파일 함수로 빼서 관리하면 '사용'자체에는 편리한 부분이 있을 것이다. RemoteDataSource에서 BaseRemoteDataSource를 상속받지 않아도 바로 함수 호출이 가능하기 때문이다.

하지만 이러한 점이 문제가 된다고 생각했다. 상속을 받지 않아도 아무곳에서나 접근이 가능하기 때문이다. 즉 책임의 경계가 불명확해진다.
이렇게 된다면 로컬 api 호출을 사용하기 위해 누군가가 호출을 했고, 당연히(?) 로컬 호출에 대한 책임은 없음에도 불구하고 잘 작동하게 하기 위해 safeApiCall 함수를 수정할 수도 있다.
이 순간 safeApiCall을 만든 의도자체가 퇴색되어 버리는 것이고, OCP에 대한 원칙도 깨져버리는 것이다.

또한, 실제로 사용 시 receiver를 제네릭으로 받고 있기 때문에 safeApiCall가 필요없는 상황에서도 IDE에 의해 제안되어 개발에 불편함을 초래할 수도 있다.

결론

그렇기에 BaseRemoteDataSource의 구조는 그대로 유지했고, 각 RemoteDataSource에서는 BaseRemoteDataSource를 상속받아 safeApiCall를 사용하여 보일러플레이트 코드를 줄이는 개선을 맛봤다.

profile
🚀 상상을 좋아하는 개발자
post-custom-banner

0개의 댓글