Exceptions auto wrapping


#1

This topic is idea for a new language feature: wrap throwing exceptions automatically.

Syntax example:

fun ProfileService.renameProfile(profileId: Int, newName: String)
    throws ProfileException("profileId: $profileId, newName: $newName") {
    // implementation code
}

This means: if function code throws some exception, it would be automatically catched and wrapped with ProfileException, like this:

fun ProfileService.renameProfile(profileId: Int, newName: String) {
    try {
        // implementation code
    } catch (e: Throwable) {
        throw ProfileException("profileId: $profileId, newName: $newName", e)
    }
}

Further, one can skip message string in ‘throws’ declaration, like this:

fun ProfileService.renameProfile(profileId: Int, newName: String)
        throws ProfileException {
    // implementation code
}

This ‘throws’ declaration tells complier to generate message automatically by passed parameters with a standart default pattern.

It is very important to know context of errors, especially in production support. Some broken profiles throw exceptions? It would be nice to see ids of broken profiles in production logs to check it. One needs to write such try-catch boilerplates very often, it would be nice to generate it automatically.


#2

Why not a helper function?

fun ProfileService.renameProfile(profileId: Int, newName: String) throws ProfileException

Is this Joklin?


#3

How do you suppose to implement this feature with a helper function?

I do not know how, I think, this is possible only at the level of the language.

Is this Joklin?

No! I’m absolutely serious.


#4
fun <R>throws(exception: Exception, block: () -> R){
    try{
        return block()
    } catch(e: Exception) {
        throw exception;
    }
}

fun ProfileService.renameProfile(...) = throws(ProfileException(...) {
    ...
}

#5

Ok, i see your idea, thank you.
But wrapping requires exception cause, therefore one needs to transform your decision to this:

fun <R> throws(exception: (cause: Exception) -> Exception, block: () -> R): R {
    try {
        return block()
    } catch (e: Exception) {
        throw exception(e)
    }
}

fun ProfileService.renameProfile(profileId: Int, newName: String) =
        throws({ ProfileException("profileId: $profileId, newName: $newName", it) }) {
            // implementation code
        }

This desision has the next problems:

  1. Heavy syntax, redundant symbols: = ({ })
  2. Human factor: programmers may forget to pass exception cause via ‘it’
  3. Ugly indents formatting in IntelliJ Idea
  4. There are no short syntax to generate message by parameters automatically. The main idea is for using short syntax ‘throws ProfileException’ in 99% use cases.

#6

Catching general exceptions like Exception or even Throwable is usually considered very bad style. For example you don’t want to accidentally catch OutOfMemoryError (which indicates some serious VM problem) or NullPointerException (which indicates a programming error).

You typically only want to catch specific categories of Exceptions like IOException.


#7

I could agree to pass Error: catch Exception rather then Throwable

But ‘catching general exceptions is usually considered very bad style’ - no.
This is very common pattern in practice.

Consider a web server, rendering html pages via servlets or other application level code.

If some application programmer make a bug, and render code for the page ‘HelloWorld.html’ throws NPE or IndexOutOfBoundsException, does web server should ignore this exception and shut down?

Surely not, server catchs every exception in ‘main loop’, prints it to log and send 500 http error to the user.

Typically i dont know exception types to make specific catch blocks. I call some library function, it calls some other functions and so on. What exception types could back to me from the dark mysterious depth of the callstack? Any. But it is very important to be able read context of the bug from the logs.

The most useful quality of exceptions (comparing to other error handling approaches) - is the ability to get stacktrace automatically. Error happens -> it goes to log -> developer checks logs -> see stacktraces -> understand the reason of the bug -> developer fixes bug.

Stacktrace is the part of the bug context, the evidence for the bug investigation.
Function parameters values is the part of the bug context too, so let gather this evidences automatically or semi-automatically.


#8

Catching Exception makes sense if you’re executing user supplied code like in a plug in interface or when rendering a website and also to prevent threads dying in some concurrent scheduling code or thread pools or something like that. In the general case, it’s not a good idea, though and it doesn’t warrant a language feature.


#9

Let function calls some other functions and exception happens.

Compare two cases:

  1. Ignore exception, let it go to the upper level.
  2. Catch exception, wrap it and throw wrapped exception to the upper level.

First case:

  • Upper level doesn’t know types of throwed exceptions.
  • Context of the bug is lost, developer doesn’t know values of the passed parameters to investigate the bug.

Second case

  • Function declares type of its exception explicitly.
  • Values of the passed parameters automatically saved in exception message.

I think the second case is certainly better.
I think catch-all pattern is much more general then web servers, plugins, thread pools and so on.


#10

If the objective is to know the parameter values, the line that threw the exception can just be wrapped in a try/catch when you’re actually looking for them. Most of the time, you don’t need parameter values to understand the problem. There’s also the powerful debugger at your disposal. You just need a test environment, something you should always have anyway.


#11

Coincidentally, I just finished reworking our exception handling in a Kotlin-Spring REST app. Spring’s @ControllerAdvice is a super solution to all this.

  • My AllExceptionsHandler file holds all my two-line custom exceptions, an ErrorResponse data class, and the @ControllerAdvice methods to handle all exceptions, even a default 500 error wrapper.

@ResponseStatus(value = HttpStatus.NOT_FOUND) // 404
class NotFoundException(msg: String) : Exception(msg)
. . .
@ExceptionHandler(NotFoundException::class)
fun handleNotFoundException(ex: NotFoundException, request: HttpServletRequest): ResponseEntity {
return logAndReturn(ex, ex.message, request, HttpStatus.NOT_FOUND)
}

  • App code simply throws the exceptions.
    throw NotFoundException("Node id ${nodeData.nodeId} not found.")

I’m thinking of putting more snippets and explanation in a DZone article.


#12

This is for production.
Log messages are the only information about bug in production in most case.

But also it’s good and fast to know bug reason looking at message rather then hard debug in development.

Programmers are lazy, they don’t handle exceptions right way, don’t provide context information in the message string.

My favorite example is BindException.

For example, some application you support fails with:

Exception in thread "main" java.net.BindException: Address already in use: bind
	at sun.nio.ch.Net.bind0(Native Method)
	at sun.nio.ch.Net.bind(Net.java:433)
	at sun.nio.ch.Net.bind(Net.java:425)

WTF?? Where is the bug context? What net address is required for this f^^^ng application? Why should i waste my time to investigate this piece of s^^t, instead of just see required binding port from the message?


#13

Looks neat! What about passing values from the below the Throws code (vals generated within the virtual try)? Is it allowed in the first invocation?

Also, I’m a bit concerned that it resembles the Java throws which looks exactly the same but means a different thing.

Just by the way, is it possible to leverage the space after the closure instead by any chance? That would make it stand out, resemble the familiar try/catch sequence, and more logical for leveraging any locals for reporting back (even though not exactly in a Java sense).
i.e.

fun ProfileService.renameProfile(profileId: Int, newName: String)  {
    // implementation code
    val time = ...
} throws ProfileException("profileId: $profileId, newName: $newName, Time: $time")

#14

Human factor: programmers may forget to pass exception cause via ‘it’

you can use initCause method.


#15

Your request makes me think you would like to use this yourself, in your own applications, meaning you have access to the source code, unit tests, debug environments in most cases.
However, in your reply you appear to be complaining about lackluster exception handling of applications you’re using.
Judging by the request, I found it fair to assume that the conditions I just mentioned hold, in which case what I said earlier still stands. There’s no absolute need for this feature in that situation. You’re now complaining about applications other than your own with lazy exception handling. Requesting this feature is unlikely to help, at least not within a couple of years, before new applications that would otherwise have poor exception handling might finally start using it, should it be implemented.