오늘은 RepositoryImpl에서 API 요청할 때 반복되는 코드를 한 번에 해결하는 방법을 공유하려고 합니다. 이 부분도 코드리뷰로 달렸던 부분 중 하나인데요! 사실 생각해보니 서버 붙일 때는 항상 똑같은 일을 하고 있다고만 생각했지; 이를 개선해볼 생각을 따로 하진 않았네요..(반성) 비슷한 코드를 반복해서 작성하다 보면 지치고, 유지보수할 때는 더 고생스럽잖아요. 그런 고생을 미리 막기 위해 간단한 글을 작성해봤습니다!
class MyPageRepositoryImpl @Inject constructor(
private val myPageService: MyPageService
) : MyPageRepository {
override suspend fun getMyHobby(token: String): Result<ResponseMyHobbyModel> = runCatching {
val response = myPageService.getMyHobby(token)
response.result?.let {
ResponseMyHobbyModel(hobby = it.hobby)
} ?: throw Exception("데이터를 불러오는데 실패했습니다")
}
제가 과제로 제출했던 코드 중 일부인데
myPageService.getMyHobby(token))response.result?.let)ResponseMyHobbyModel(hobby = it.hobby))throw Exception)이런 과정으로 대부분의 API에서 반복된 경험 있지 않으신가요!!
반복은 시간이 갈수록 코드 가독성을 해치고, 유지보수를 어렵게 만듭니다.

이렇게 반복됐던 경험이..
그럼 이제 바꿔보자!
‘반복되는 흐름 → 함수로 추출’이라는건 너무 자명한 사실이죵
공통 로직을 하나의 함수로 추출해서 많은 API 호출에서 재사용해보려고 합니당
suspend fun <T, R> apiCall(
call: suspend () -> T?,
transform: (T) -> R
): Result<R> = runCatching {
call()?.let(transform) ?: throw Exception("데이터를 불러오는데 실패했습니다")
}
이제 직접 함수에 적용해보면?
override suspend fun getMyHobby(token: String): Result<ResponseMyHobbyModel> =
apiCall(
call = { myPageService.getMyHobby(token).result },
transform = { ResponseMyHobbyModel(hobby = it.hobby) }
)
API 요청마다 성공 시 데이터 변환, 실패 시 예외 처리를 반복하면 코드가 지저분해질 수 있습니다. 이걸 깔끔하게 처리하려면 Result 확장 함수를 쓰면 됩니다!
inline fun <T, R> Result<T>.mapResult(transform: (T) -> R): Result<R> =
this.mapCatching {
it?.let(transform) ?: throw Exception("데이터를 불러오는데 실패했습니다")
}
runCatching {
val response = apiService.getSomething()
response.result?.let { ResponseModel(it.data) } ?: throw Exception("데이터 없음")
}
runCatching { apiService.getSomething().result }
.mapResult { ResponseModel(it.data) }
실제 적용 결과는?
override suspend fun getMyHobby(token: String): Result<ResponseMyHobbyModel> =
runCatching { myPageService.getMyHobby(token).result }
.mapResult { ResponseMyHobbyModel(hobby = it.hobby) }
코드가 반복되지 않게 개선했지만, 에러 처리나 메시지가 상황에 따라 달라야 할 때도 있겠죠?
suspend fun <T, R> apiCall(
call: suspend () -> T?,
transform: (T) -> R,
errorMessage: String = "데이터를 불러오는데 실패했습니다"
): Result<R> = runCatching {
call()?.let(transform) ?: throw Exception(errorMessage)
}
class CustomException(message: String) : Exception(message)
suspend fun <T> apiCall(call: suspend () -> T): Result<T> = runCatching {
call() ?: throw CustomException("커스텀 에러 메시지!")
}
suspend fun <T> apiCallWithEmptyList(
call: suspend () -> List<T>?,
errorMessage: String = "데이터를 불러오는데 실패했습니다"
): Result<List<T>> = runCatching {
call() ?: emptyList()
}.onFailure {
println("Error: $errorMessage, ${it.message}")
}