2

I am looking into implementing a standard error handling on my application. I want errors that cant be dealt with(custom unchecked errors) but that also aren't catastrophic to be logged by a fault barrier without having to clutter my code with pesky try catches and logger calls.

Let me illustrate. I have an intake that receives a json string and set it into my object model, one of the fields in said model call a timeHelper function, this function can throw an exception if the arguments are invalid(null or empty). The yield of this function is not critical to the program, in fact this program should never crash( to the best of my abilities) as it should stay up 24/7.

Model

public class MyModel{
   private string myField
   public void setMyField(String myfield){
       this.myField = Helper.DoStuff(myField)
   }
}

Intake

public class Intake{
    public MyModel receiveJson(){
        return JacksonMagic(arguments,MyModel.class)
    }
}

Helper

Public class Helper{

    public String DoStuff(String myField){
        //Check that can throw exception
        //regular operation with return
    }
}

Now, when life is beautiful DoStuff returns a string, in fact the exception should never be thrown because it implies that the source of the json, which is external to my application, sent wrong/missing information. If it does happen I want it to be logged so I can investigate what happened. I also want to set a framework in place, probably with Spring AOP, to handle that logging. But as you can see through the example, I also want execution to continue as this is not some app breaking thing.

The execution flow I am looking for is something like Intake > Model > Helper(THROW EXCEPTION) > Logger > Whoever Called Intake

And again, I want to do that without the try catch logger call cluter

Is this something possible with AOP?

Post answer Edit Just want to leave some sources here.

To set up your IDE for AspectJ compilation, this article is really helpful. https://www.baeldung.com/aspectj

2 Answers 2

2

This is not a good use case for exceptions.

An exception represents something that you're not able to handle, an "exceptional" occurrence that you're not able to deal with. The fact that you're saying this is a possible scenario changes this from an exception to a use-case, in which case logging a warning in your service tier is probably the best solution.

Exceptions have their place, however overusing them makes code harder to follow, since it breaks the "flow" of an application. Exceptions should not be used to control flow.

AOP, in my option, offers little when it comes to exception handling. At best it can log the exception (which can also be achieved in a much clearer way using an ExceptionHandler pattern), however it certainly can't trigger your code to continue as though it didn't happen.

If you haven't already, look into logging strategies, they can be really useful in this kind of scenario.

The bottom line is: if you want control flow to continue, don't throw an exception (checked or unchecked).

Sign up to request clarification or add additional context in comments.

6 Comments

I don't agree with your first statement as exceptions can and should be used to convey multiple outcomes of a method, and when a method has multiple outcomes you usually can do something about it. My example is not a use case as it shouldn't happen, the same way an IO exception shouldn't happen, but I need to save guard my code in case it does. I Agree that exceptions shouldnt' control flow, but I don't believe that's what I am after, I want exactly the opposite, the flow to continue it's regular course, after a pit stop in the logger.
Java is unique in using exceptions to convey multiple outcomes of a method, it's a strange and inefficient pattern. If a method can return multiple results it should return an object that represents these possible states. Checked exceptions have been used in the past to solve this, however I think it's a very blunt tool and leads to lots of 'exception swallowing' and messy code. This is just my opinion though, the crux of my answer is that you can't ignore exceptions, they're too fundamental to the Java Specification.
I agree, when I saw that GO had multiple returns I saw the light. Life was beautiful as you could return all you need with no mess. As for creating an object for multiple return, it seems like a huge overkill plus a huge clutter of objects. I think exceptions can fit this role as the don't always mean something went wrong, just that something deviated from the standard operation, a positive yet rare outcome is still a positive outcome. Anyway this is getting off topic. I will leave this open for a little longer, maybe an AOP Jon skeet comes along to save the day
Stu, your statements are not quite correct. With AOP of course you can handle exceptions and also make the application run without interruption under one condition: The method or constructor throwing an exception does not return anything, so the aspect has to make sure to return something which the application can continue to work with, some kind of default or fall-back value, maybe based on the method's input parameters. This is the real problem, not the exception interception and logging as such. So @Tocchetto, do you have a plan how to deal with that problem? Then I can help you.
@kriegaex So you are saying that in order to achieve the flow I mentioned a method that can throw an exception CAN'T return anything, the only "output" of the method would be throwables and the Aspect would handle logging the exception OR returning the actual value the method caller would expect( but in this case he "wouldn't expect" since the method he call is a return type void). Interesting albeit it looks like it produces very convoluted code. Is there an article/topic I can read/research about this? does this approach have a name?
|
1

Okay, here we go with a complete MCVE, assuming you know how to use the AspectJ compiler in order to compile your project. Sorry for repeating your classes with package names, imports etc., but I like you to see all details:

First we need our helper which randomly throws unchecked exceptions so we can see the aspect in action later:

package de.scrum_master.app;

import java.util.Random;

public class Helper {
  private static final Random RANDOM = new Random();

  public static String doStuff(String myField) {
    if (RANDOM.nextBoolean())
      throw new RuntimeException("uh-oh!");
    return "processed " + myField;
  }
}
package de.scrum_master.app;

public class MyModel {
  private String myField;

  public void setMyField(String myField) {
    this.myField = Helper.doStuff(myField);
  }

  @Override
  public String toString() {
    return "MyModel(myField=" + myField + ")";
  }
}
package de.scrum_master.app;

public class Intake {
  public MyModel receiveJson(String... arguments) {
    return jacksonMagic(arguments, MyModel.class);
  }

  public MyModel jacksonMagic(String[] arguments, Class<?> clazz) {
    MyModel myModel = new MyModel();
    myModel.setMyField(arguments[0]);
    return myModel;
  }

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++)
      System.out.println(new Intake().receiveJson("foo"));
  }
}

Now when you run the little driver application via Intake.main you will see unhandled exceptions on the console. Here is how to handle this using an aspect. I am limiting the aspect to matching all method executions with a String return type, stupidly returning a dummy value whenever an exception occurs. You just add your more sophisticated logic in there as you see fit and also adjust the aspect's pointcut to match the methods you want to handle.

package de.scrum_master.aspect;

public aspect ErrorHandler {
  String around() : execution(String *(..)) {
    try {
      return proceed();
    } catch (Exception e) {
      System.out.println("Exception handled: " + e);
      return "dummy";
    }
  }
}

I love the expressive native AspectJ syntax, but I know that some people for whatever reason feel more comfortable with annotation-based syntax. Just look at the throws declaration, the pointcuts in string constants, the explicit joinpoint declaration, the cast - yuck! Anyway, here we go:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class ErrorHandler {

  @Around("execution(String *(..))")
  public String handleErrors(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    try {
      return (String) thisJoinPoint.proceed();
    } catch (Exception e) {
      System.out.println("Exception handled: " + e);
      return "dummy";
    }
  }
}

The console log looks like this with the aspect in place:

MyModel(myField=processed foo)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)

1 Comment

Thank you, that's exactly what I was looking for =)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.