warning handling
OK, so everyone knows what exception handling is in java:
try{
thrownew Exception("Error!");
}
catch (Exception ex){
System.out.println("An error occured: " + ex.getMessage());
}
...but what if I want to throw an exception that is not fatal--i.e. my program can continue with its normal operation, but it needs to notify the calling method that there was a problem. This cannot be done with the existing exception handling mechanism because once an exception is thrown, control is handed over to the calling method and never returned.
Take the following example:
publicclass DataStore{
publicvoid store(List<String> data)throws StoreException{
for (String s : data){
if (s ==null){
// WARNING! - null data -- how do I handle this?
}
else{
try{
// store s
}
catch (Exception ex){
thrownew StoreException("fatal error: " + ex.getMessage());
}
}
}
}
}
Assume that the user needs to know immediately if there is null data so that the problem can be corrected the next time the program runs. In other words, sticking the warning message in a log file is not good enough. The best way I know of to fix this is to write a "warning handler" interface as follows:
publicinterface WarningHandler{
publicvoid handleWarning(Exception e);
}
publicclass DataStore{
publicstaticvoid main(String[] args){
List<String> data = getSomeData();
WarningHandler handler =new WarningHandler(){
publicvoid handleWarning(Exception ex){
System.err.println(ex.getMessage());
}
}
try{
new DataStore().store(data, handler);
}
catch (StoreException ex){
System.err.println(ex.getMessage());
}
}
publicvoid store(List<String> data, WarningHandler handler)throws StoreException{
for (String s : data){
if (s ==null){
handler.handleWarning(new NullDataException());
}
else{
try{
// store s
}
catch (Exception ex){
thrownew StoreException("fatal error: " + ex.getMessage());
}
}
}
}
}
With this solution, control is briefly passed to the warning handler and then execution continues.
I think it would be better if warning handling was actually included in the java language. It could be implemented using the same exception handling structure that already exists, but if the object that is caught is a subclass of "Warning" rather than "Exception", then control returns to the method that threw the warning after it has been handled:
publicclass Warningextends Throwable{
// ...
}
publicclass NullDataWarningextends Warning{
// ...
}
publicclass StoreExceptionextends Exception{
// ...
}
publicclass DataStore{
publicstaticvoid main(String[] args){
List<String> data = getSomeData();
try{
new DataStore().store(data);
}
catch (NullDataWarning wn){
// execution returns to store method after evaluating this block
System.err.println(wn.getMessage());
}
catch (StoreException ex){
// execution does not return to store method after evaluating block
System.err.println(ex.getMessage());
}
}
publicvoid store(List<String> data)throws StoreException{
for (String s : data){
if (s ==null){
thrownew NullDataException();
}
else{
try{
// store s
}
catch (Exception ex){
thrownew StoreException("fatal error: " + ex.getMessage());
}
}
}
}
}
Does anyone have any better ideas as to how to handle warnings?
# 1
There are lots of ways, but your requirements seem to me to be contradictory. You want the user to receive warning feedback "immediately", suggesting a break in execution, yet your entire design is hell-bent on continuing execution.
One possible idea is to extend java.lang.Exception with a class such as AbstractWarning. You can then use a FrontController (design pattern, implemented out of the box by most web frameworks ala JSF or Struts) to handle the warnings separate from the vanilla exceptions. Here is one possibility:
public class ValidationWarning extends Exception {
private final List<String> messages;
public ValidationWarning() {
super();
messages = new ArrayList<String>();
}
public final void add(final String message) {
messages.add(message);
}
public final boolean doThrow() {
return (messages.size() > 0);
}
@Override
public final String getMessage() {
// Iterate over the warnings and concatenate them together
}
}
public void foo() throws ValidationWarning {
ValidationWarning warnings = new ValidationWarning();
// Spiffy code here
if (itemThatShouldNotBeNull == null) {
warnings.add("itemThatShouldNotBeNull is null!");
}
// More spiffy code here
if (warnings.doThrow()) {
throw warnings;
}
}
- Saish
# 2
Hmm, that is an interesting solution. It allows me to avoid passing the warning handler to the function, and it also allows me to handle the warning like a traditional exception.
But what if a fatal error occurs? The user will never know about the warnings because the fatal error will take precedence and only one exception can be thrown.
I guess there are tradeoffs to any solution...
Thanks for the idea.
Message was edited by:
kcraft4826
# 3
You can always 'chain' exceptions together:
public class FatalException extends Exception {
private final List<Throwable> chained;
public FatalException() {
super();
}
public final void addNextException(final Throwable t) {
chained.add(t);
}
public final List<Throwable> getChainedExceptions() {
return chained;
}
}
Have your fatal exception chain the warnings exception and then have your handler inspect whether warnings were present in the fatal exception.
- Saish
# 4
It kind of sounds like you want what is called a 'continuation' in Ruby (as I understand it.)
The way you can emulate a continuation in Java is to use what I call an 'Object as Method' approach. In this approach, you create an object type that represents a single method. In other words, you create a new instance for each execution of your method. Instead of using local variables, you use instance variables for anything that must span different parts of the method execution. That is a loop counter might still be local if it's not in a restartable part of the method but if you want to come back into the middle of the loop, you need it to be an instance variable. Basically everything except a constructor (or factory method) and a exec-type method will be private.
Then, in your case, when the warning condition occurs, you record where you were in processing in the instance. Then after dealing with the warning the caller can call the exec method again (or a continue method, if you prefer) and the process picks up where it left off. I would break it down into methods so that the continue action just calls the next method.
This does have some impact on performance because you are putting things on the heap instead of the stack (at least until they add escape analysis; keep your fingers crossed) but this should not be significant in most cases since the Object will be short-lived and not leave the young generation.
# 5
>
> Assume that the user needs to know immediately if
> there is null data so that the problem can be
> corrected the next time the program runs. In other
> words, sticking the warning message in a log file is
> not good enough. The best way I know of to fix this
> is to write a "warning handler" interface as
> follows:
The "user"?
And the "next time" the program is run?
What if the user simply ignores whatever you do?
What if the user takes a two week vacation? Or quits?
What if the user never runs the program again?
What if this 'warning' comes up when the "user" is trying to enter a 100 million dollar order for a customer?
What sort of software, and only software, do you think that you can write to solve the above solutions?
My guess is none.
So the solution is to gather requirements on the work flow of the "user" and determine exactly what it is that they need to do and can do. And you also need to determine what priority this has and how it escalates if it isn't fixed (or doesn't escalate as the case maybe.)
Only once you have that do you try to create a design (and you do that before code) to solve the problem.
# 6
> The "user"?
> And the "next time" the program is run?
>
> What if the user simply ignores whatever you do?
> What if the user takes a two week vacation? Or
> quits?
> What if the user never runs the program again?
> What if this 'warning' comes up when the "user" is
> trying to enter a 100 million dollar order for a
> customer?
>
> What sort of software, and only software, do you
> think that you can write to solve the above
> solutions?
>
> My guess is none.
>
> So the solution is to gather requirements on the work
> flow of the "user" and determine exactly what it is
> that they need to do and can do. And you also need
> to determine what priority this has and how it
> escalates if it isn't fixed (or doesn't escalate as
> the case maybe.)
>
> Only once you have that do you try to create a design
> (and you do that before code) to solve the problem.
Actually, JSchell, BPM solutions can do any and all of the above (whether to do so is sensible in certain cases is another matter). As an example, wait states and long-lived transactions are fundamental to JBoss's jBPM.
http://labs.jboss.com/jbossjbpm
- Saish
# 7
> The "user"?
> And the "next time" the program is run?
>
> What if the user simply ignores whatever you do?
> What if the user takes a two week vacation? Or quits?
> What if the user never runs the program again?
> What if this 'warning' comes up when the "user" is trying to enter a 100
> million dollar order for a customer?
>
> What sort of software, and only software, do you think that you can write to
> solve the above solutions?
>
> My guess is none.
>
> So the solution is to gather requirements on the work flow of the "user"
> and determine exactly what it is that they need to do and can do. And you
> also need to determine what priority this has and how it escalates if it isn't
> fixed (or doesn't escalate as the case maybe.)
>
> Only once you have that do you try to create a design (and you do that
> before code) to solve the problem.
jschell, the code that I gave is a simple example that has almost nothing in common with my actual situation because it would be too complicated to explain in this forum. In my situation, the severity of the warning message is extremely low and the consequences of the user not taking action are also low. However, the user will want to correct the problem and he/she will not be checking log files to find out if something went wrong, so a message needs to be displayed on the screen as soon as the problem occurs or the user will never know about it.
Message was edited by:
kcraft4826
# 8
> Actually, JSchell, BPM solutions can do any and all
> of the above (whether to do so is sensible in certain
> cases is another matter). As an example, wait states
> and long-lived transactions are fundamental to
> JBoss's jBPM.
>
I am aware that work flow solutions exist.
Presumably you are aware that that doesn't mean that one doesn't need to collect requirements as to what users want to do - and that is what I was referring to.
# 9
>
> jschell, the code that I gave is a simple example
> that has almost nothing in common with my actual
> situation because it would be too complicated to
> explain in this forum. In my situation, the severity
> of the warning message is extremely low and the
> consequences of the user not taking action are also
> low. However, the user will want to correct the
> problem and he/she will not be checking log files to
> find out if something went wrong, so a message needs
> to be displayed on the screen as soon as the problem
> occurs or the user will never know about it.
>
Described like that and presuming there aren't really any other requirements then a simple event should be sufficient.
# 10
I don't know why I didn't think of this as an "event" before, but now that you mention it, that is exactly what it is. This can be handled with the observer pattern to avoid passing in a handler to the method:
public interface DataStoreListener {
public void nullData();
// other handlers
}
public class DataStore {
public void addDataStoreListener(DataStoreListener l) { ... }
public void store(List<String> data) throws StoreException {
for (String s : data) {
if (s == null) {
for (DataStoreListener l : listeners) {
l.nullData();
}
}
else {
try { ... } catch (Exception ex) { throw new StoreException(ex); }
}
}
}
}
Obviously the method names could be better, but you get the idea...