Unfortunately, in work, we’re using Spring Framework. Not too long ago I was able to discover an interesting detail in our approach to exception handling. Catching guy Usually it looks like some class (most often a service) throws an exception. There is an exception handler in another part of the program that does the job.

// class that throws
public class DrinkService {
    public drinkAndDoThings() {
        throw new HangoverException("You have a headache!")
    }
}

// class that handles
@ControllerAdvice
public class ExceptionHandler {
    
    @ExceptionHandler(HangoverException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<?> onHangoverException(final HangoverException exc) {
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(exc.getMessage());
    }
}

And user of our API gets response like

{
    "error": "You have a headache!"
}

So, first of all, this looks like a mess. You probably didn’t want to read this code, or you had to struggle to realize what is written here. But that’s beside the point, just a quick note. Let’s look at it from the other side. When a programmer writes the throw keyword, he or she knows that somewhere in the program he or she has a piece of code that will be executed. Looks familiar, doesn’t it? To refresh your memory, take a look at this

_start:
    ; Entry point of the program

    ; Initialize a counter (e.g., ECX) with a value (e.g., 5)
    mov ecx, 5

loop_start:
    ; Your code here
    ; For example, print something to the screen, perform some calculations, etc.

    ; Decrease the counter
    dec ecx

    ; Check if the counter is zero
    jz loop_exit  ; If it is zero, exit the loop

    ; Jump back to the start of the loop
    jmp loop_start

loop_exit:
    ; Your code here after the loop
    ; Exit the program
    mov eax, 1       ; syscall number for sys_exit
    int 0x80         ; Call kernel

Take a closer look at the jmp keyword, it does the same thing as throw. In fact, we are still using assembler instructions, in 2023. It seems we still live in a procedural paradigm. I don’t think it can be avoided in Java, but for example, in Rust they handle errors as follows

use std::fs::File;

fn main() {
    let data_result = File::open("data.txt");

    // using match for Result type
    let data_file = match data_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the data file: {:?}", error),
    };

    println!("Data file", data_file);
}

In my opinion, this looks much better. First, you can see where the error was handled. Second, it obliges you to handle the exception yourself. Unfortunately, the errors in EOLANG have the same goto or jmp way of thinking (example was taken from here).

[x] > check
  if. > @
    x.eq 0
    error "Can't divide by zero"
    42.div x

Line

error "Can't divide by zero"

looks same as

throw new ZeroDivisionException("Can't divide by zero")

to me.

This forces programmers to use exceptions as control flow. I hope that EOLANG will move on from this to a different way of handling errors.