5

I have a class that transforms a complex model, for example an abstract syntax tree or intermediate model. The model can be either valid, invalid or partially invalid, i.e. it contains errors but some parts of it are valid and can be processed further.

The most simple error reporting is to use exceptions:

public class Transformer {
    public TargetModel transform(SourceModel model)
      throws InvalidModelException {
        // ...
    }
}

Obviously this doesn't allow to report multiple errors (at least if you don't attach further information to the exception) and exceptions should be for exceptional situations.

Martin Fowler addressed this problem in his article Replacing Throwing Exceptions with Notification in Validations. If you apply his method to the problem you have something like this:

public abstract class Problem {
    // ...
}

public final class ProblemA {
    // ...
}

public final class ProblemB {
    // ...
}

public class Transformer {
    public static final class TransformationResult {
        private Collection<Problem> problems;
        private Optional<TargetModel> targetModel;
        // ...
    }

    public TransformationResult transform(SourceModel model) {
        // ...
    }
}

You then either use the visitor pattern or instanceof checks to distinguish the errors. It's a trade-off between type-safety and verbosity.

Another possible solution would be to use the observer pattern:

public class Transformer {
    public interface ProblemListener {
        public void onProblemA(...);
        public void onProblemB(...);
        // ...
    }

    public void addProblemListener(ProblemListener listener) {
        // ...
    }

    public void removeProblemListener(ProblemListener listener) {
        // ...
    }

    public TargetModel transform(SourceModel model) {
        // ...
    }
}

Using the observer pattern has the advantage that you don't have to accumulate the errors in memory and that excessive instanceof checks or visitors are not needed. On the other hand it obscures the control flow even more than the visitor pattern.

I think all solutions do not lead to readable code. In a functional language and with enough memory I would use the second solution but in Java structural pattern matching either entails instanceof checks or use of visitor pattern which are both verbose. Is there anything that I have overlooked? Or implications that I did not consider? What is your experience with any of the solutions?

Christophe
  • 74,672
  • 10
  • 115
  • 187
user3998276
  • 187
  • 1
  • 2
  • 7

3 Answers3

1

You could describe Validation as a function, that takes data as input and gives you a tuple back

inputdata -> (outpudata, Errors) or maybe inputdata -> (outpudata, errors, warnings)

Where it will be obvious, that inputdata is the data, you want to fed in.

Depending on your usecase

  • outputdata is the same as inputdata

  • outputdata is some kind of ValidationResult where there is data which is your inputdata and maybe validatedData which represents the subset which is valid (in case, you want to accept partial results).

The point of Fowlers article is, that the naive approach of throwing exceptions has several downsides. An exception is some kind of early exit. Depending on your usecase, it doesn't reveal all, but only the first error occured. Say you are writing a complex forms app, you want the user to inform of all things where action is needed - unless you want to drive your users nuts. Second: invalid data is not really exceptionally, data is expected to be sometimes not quite right. Exceptions are a bit overkill in this scenario. Third: Exceptions could lead to some kind of goto-like programming structure, which could complicate the understanding of programming flow. Exceptions should not be used for this.

In Java you would make errors and warnings members of ValidationResult and would write hasErrors and hasWarnings and getErrors and getWarnings resp. addErrors and addWarnungs accordingly.

Your problem comes down to:

general validation function: inputdata -> (outpudata, errors, warnings)

calls a bunch of validation functions

single valiator: data -> (errors, warnings)

or using ValidationResult as the return type of a single validation

single valiator: inputdata -> (outpudata, errors, warnings)

after each call, you add all errors and warnings to the according collections of ValidationResult or implement a simple merge()-Method on ValidationResult.

In the end return the final ValidationResult

Done.

Thomas Junk
  • 9,405
  • 2
  • 22
  • 45
0

InvalidModelException should be abstract and subclassed by specialized implementation, which will hold specific details. Each problem can be handled in separate block:

TargetModel target;
try {
    target = transform(source);
} catch (ProblemA ex) {
    ex.getDetailsA();
} catch (ProblemB ex) {
    ex.getDetailsB();
} catch (InvalidModelException ex) {
    ex.getGenericDetails();
}

Alternatively return compound object:

public Result<TargetModel, Diagnostic> transform(SourceModel model);

if (result.isInvalid()) {
    if (result.getDiagnostic() instanceof ...) 
       ...
    // or
    switch (result.getProblemType()) { 
    case Problem.A:
        break;
    case Problem.B:
        break;
    default:
        break;
    }
    throw new Exception();
}
target = result.getTarget();
gavenkoa
  • 271
  • 2
  • 10
0

The most simple error reporting is to use exceptions [...] this doesn't allow to report multiple errors (at least if you don't attach further information to the exception)

Then... why not do that?

public class InvalidModelException extends Exception {
    private Collection<Problem> problems;
}
Michael Borgwardt
  • 51,037
  • 13
  • 124
  • 176