Concise way of logging exception and returning


#1

I frequently end up with code like this:

fun foo(): Boolean {
    try {
       funcThatMightThrow()
    } catch (e: Exception) {
        logger.error("Error calling function: $e")
        return false
    }
    // rest of function
}

And I’m looking for a way to make this shorter. One thing I’ve tried is changing funcThatMightThrow to something like:

fun funcThatMightThrow(logger: Logger): Boolean {
    try {
        throwingFunc()
    } catch (e: Exception) {
        logger.error("Error: $e")
        return false
    }
    return true
}

And then the caller looks like:

if (!funcThatMightThrow(logger)) {
    return false
}

Which is better, but I find it a bit awkward/annoying to have to pass the logger into a helper function. What I really want is a syntax like this:

funcThatMightThrow().onException { e -> logger.error("Error: $e"); return }

I tried using eitherTry/Either for this, so I defined this extension function:

fun <L, R> Either<L, R>.ifError(block: (e: L) -> Unit) {
    if (this is Either.Left<L, R>) {
        block(this.l)
    }
}

And then at the caller did:

eitherTry { funcThatMightThrow() }.ifError { e -> logger.error("Error!: $e"); return }

But I can’t return from the function (foo) like that there, so then I tried:

fun <L, R> Either<L, R>.ifError(block: (e: L) -> Unit): Any? {
    if (this is Either.Left<L, R>) {
        block(this.l)
        return null
    }
    return true
}

and the caller became:

eitherTry { funcThatMightThrow() }.ifError { e -> logger.error("error $e") } ?: return

Which works, but feels pretty deep into ugly hack territory. Is there a nice way of handling code like this?


#2

I normally like to use the arrow library and just work with Try or Either as return values. I therefor don’t need that many try blocks, so I just tend to do it like you did in your first example.

One question, do you return false/true in case of failure/success? I generally consider this an anti-pattern. I prefer to just not to catch the exception in case of a failure and handle them at a later state (where ever you handle the failure anyways). That way you don’t just ignore errors.
Sometimes it is helpful to catch the exceptions and wrap them in a more general exception so you don’t have to catch as many different exceptions at one place. Sadly kotlin does not yet support multicatch.


#3

Hey thanks @Wasabi375.

I normally like to use the arrow library and just work with Try or Either as return values. I therefor don’t need that many try blocks, so I just tend to do it like you did in your first example.

I don’t use arrow in the code at all right now, and it could work well when there is a series of checks at the top of a method, say, but there are also cases where I need to do a single check so being able to string together calls to functions returning Either wouldn’t be a big benefit when there’s just one. From what I saw, just wrapping in eitherTry still results in quite a few lines of code to handle it.

One question, do you return false/true in case of failure/success? I generally consider this an anti-pattern. I prefer to just not to catch the exception in case of a failure and handle them at a later state (where ever you handle the failure anyways). That way you don’t just ignore errors.

Not always. Depends on the situation. The real code that made me think of trying to find a more concise way wouldn’t return anything; it would just succeed or throw.


#4

Totally agree. I hate people who claim you should “always” or “never” do something. I am a great fan of many patterns but I would never claim that you should follow them without thinking about why you follow them in a certain situation.

Sadly true. I don’t think there is a way of doing proper error handling without having some extra code. You can try to make it as nice as possible but you can’t get around it as far as I’m aware.

One thing I want to add though is (as long as you are not working alone on a project) I prefer to not use my own complex error handling logic. That way the code becomes easier to understand. I normally try to just use already existing patterns and libraries.


#5

Looks like you ended just where you started. You last code sample is practically equivalent to

try { funcThatMightThrow() } catch (e: Exception) { logger.error("error $e") }

Was it worth it?


#6

Actually now that you say it, I think it is even worse. With a normal try catch you have the ability to only catch specific exceptions, which this “functional” approach can’t.


#7

Spring is not part of your question, but I would like to throw it in (no pun intended) because it is such a good solution, so clean.

Caller

@Service
    . . .
    fun findNode(id: Long) = nodeRepo.findOne(id) ?: throw NotFoundException("Node id $id not found.")

One File to Rule Them All

@ControllerAdvice()
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class AllExceptionsHandler: ResponseEntityExceptionHandler() {
    . . .
    @ExceptionHandler(NotFoundException::class)
        fun handleNotFoundException(ex: NotFoundException, request: HttpServletRequest): ResponseEntity<ErrorResponse> {
            return logAndReturn(ex, ex.message, request, HttpStatus.NOT_FOUND)
        }