Recently, I was given a task of re-writing a really old piece of software. The whole software itself is well written, except for the one thing that worries me, classes containing a huge amount of code. A lot of that is nothing but really really big validation chains that go like:
if (!isFooValid(dataObject.getFoo()))
throw new FooException(...);
if (!isBarValid(dataObject.getBar()))
throw new BarException(...);
And that inspired me to write an API that can help in decoupling the validation chains.
As far as my understanding goes, validation is generally a Composite Pattern, and if we break it down and separate the what we want to from the how we want to do it, we get:
If foo is valid then do something.
And we got an abstraction: is valid.
So I went up on decoupling how do we get the abstraction is valid, I came up with a design idea.
I can have a Result
object, that contains the message about validation with a simple true/false to check whether it was successful or not.
public interface Result {
// StandardResult is the default implementation of Result.
// Using the default constructor gives an instance that indicates
// "SUCCESS" state of validation.
public static final Result OK = new StandardResult();
public Throwable getError();
public boolean isOk();
public String getMessage();
}
I can have a Validator<T>
object, that has the validation logic, and then returns a Result
object that contains information about what happened.
public interface Validator<T> {
public Result validate(T target);
}
This enables me to do stuff like:
Result r = new SomeStringValidator().validate("This String");
And similarly, I can do the validation chains using a Chain of Responsibility pattern.
public class ChainValidator<T> implements Validator<T> {
// This list contains all the validators that are in the chain.
private final List<Validator<T>> validators = new ArrayList<Validator<T>>();
public CompositeResult<T> validate(T target) {
CompositeResult<T> result = new CompositeResult<T>();
for (Validator<T> v : validators) {
Result validationResult = null;
try {
validationResult = v.validate(target);
} catch (Exception ex) {
// Creating it with StandardResult(Throwable) would give
// an instance that indicates "FAILED" state.
validationResult = new StandardResult(ex);
}
result.put(v, validationResult);
}
return result;
}
private final class CompositeResult<T> implements Result {
private final Map<Validator<T>, Result> delegate = new HashMap<Validator<T>, Result>();
private Throwable failCause;
public boolean isOk() {
for (Result r : delegate.values()) {
if (!r.isOk()) {
failCause = r.getError();
return false;
}
}
return true;
}
public String getMessage() {
return delegate.toString();
}
public void put(Validator<T> validator, Result resut) {
delegate.put(validator, resut);
}
public Throwable getError() {
return failCause;
}
}
}
This works but it leaves me with a few questions:
The very first guideline to API designing says Do not Return Null to Indicate the Absence of a Value, but here I am returning a
null
object inResult#getError()
method.Does the use of generics on my example sound like code smell?
This one is going to be opinion based
- Is it worthy of making it into an API instead of just 4 classes?
Source code could be found here.