How to return a flag plus an optional message in Java?
I want to write a method in Java that verifies that some conditions hold on some data, and acknowledges that the data is valid or produces an appropriate error message otherwise.
The problem is that we cannot return more than one thing from a method, so I'm wondering what the best solution is (in terms of readability and maintainability).
First solution. Easy, but we cannot know what exactly made the check fail:
boolean verifyLimits1(Set<Integer> values, i开发者_如何学编程nt maxValue) {
for (Integer value : values) {
if (value > maxValue) {
return false; // Out of limits
}
}
return true; // All values are OK
}
Second solution. We have the message, but we are using exceptions in a way that we shouldn't (besides, it should probably be a domain-specific checked exception, too much overhead IMO):
void verifyLimits2(Set<Integer> values, int maxValue) {
for (Integer value : values) {
if (value > maxValue) {
throw new IllegalArgumentException("The value " + value + " exceeds the maximum value");
}
}
}
Third solution. We have a detailed message, but the contract is not clean: we make the client check whether the String is empty (for which he needs to read the javadoc).
String verifyLimits3(Set<Integer> values, int maxValue) {
StringBuilder builder = new StringBuilder();
for (Integer value : values) {
if (value > maxValue) {
builder.append("The value " + value + " exceeds the maximum value/n");
}
}
return builder.toString();
}
Which solution would you recommend? Or is there a better one (hopefully!)?
(Note: I made up this little example, my real use case concerns complex conditions on heterogeneous data, so don't focus on this concrete example and propose Collections.max(values) > maxValue ? "Out of range." : "All fine."
:-).)
If you need more than a single value you should return a simple class instance instead. Here is an example of what we use in some cases:
public class Validation {
private String text = null;
private ValidationType type = ValidationType.OK;
public Validation(String text, ValidationType type) {
super();
this.text = text;
this.type = type;
}
public String getText() {
return text;
}
public ValidationType getType() {
return type;
}
}
This uses a simple Enumeration for the type:
public enum ValidationType {
OK, HINT, ERROR;
}
A validator method could look like this:
public Validation validateSomething() {
if (condition) {
return new Validation("msg.key", ValidationType.ERROR);
}
return new Validation(null, ValidationType.OK);
}
That's it.
The solution is simple: create a custom VerificationResult
class. It can have a boolean status
flag and a String message
field, among other things you may want to add. Instead of returning either a String
or a boolean
, return a VerificationResult
.
Also, depending on context, throwing an exception may actually end up being the right thing to do. This has to be considered on a case-by-case basis based on concrete scenarios, though.
Alternative solution: a last error query
Another option you can use is to have the verification return a boolean
, and have a separate method e.g. String whatWentWrongLastTime()
that a user can query in case false
is returned. You'd have to be very careful with any concurrency issues etc. that may overwrite the "last" verification error.
This is the approach taken by e.g. java.util.Scanner
, which does NOT throw any IOException
(except for the constructors). To query if something "went wrong", you can query its ioException()
method, which returns the last IOException
, or null
if there wasn't any.
IllegalArgumentException
is the way to go if it really means that: You make some demands to the caller of the method (the contract) but they are ignored. In this case an IAE is appropriate.
If that doesn't reflect your use case, I'd use one of the solutions of the others.
Another approach - use a Status object:
public class Status {
public final static Status OK = new Status("OK");
private String message;
public Status(String message) { this.message = message; }
public String getMessage() { return message; }
}
To Verify, either return Status.OK
if the input is valid or create a new Status message.
public Status validate(Integer input, int maxValue){
if (input > maxValue) {
return new Status(
String.format("%s value out of limits (maxValue=%s)", input, maxValue);
}
return Status.OK;
}
Using the verifier is simple as that:
Status status = validate(i, 512);
if (status != Status.OK) {
// handle the error
}
I think the best solution is to create your own exception that holds as much error description information as you want. It should not be a RuntimeException
subclass; you want callers to have to deal with a failure to validate, because too many programmers fail to put in error handling. By making failure a checked exception, you force them (you?) to put at least something in, and code review can relatively easily pick up if they're being stupid about it. I know it's bureaucratic, but it improves code quality in the long run.
Once you've done that, consider whether you need to return a value on successful validation or not. Only return a value if that value contains information other than “oh, I've got here now” (which is obvious from the program flow). If you do need to return a result, and it needs to be a complex result, by all means use a custom class instance to hold it! To not do that is just refusing to use the facilities that the language gives you.
In this case, the method returning 'false' looks like a business logic result rather than a real Exception. So verifyLimits should return a result anyway rather than throwing an Exception when 'false'.
class VerifyLimitsResult{
//Ignore get, set methods
Integer maxValue;
Integer value;
public VerifyLimitsResult(Integer maxValue, Integer value) {
this.maxValue = maxValue;
this.value = value;
}
public boolean isOK(){
return value==null;
}
public String getValidationInfo(){
if(isOK()){
return "Fine";
}else{
return "The value " + value + " exceeds the maximum value/n"
}
}
}
....
VerifyLimitsResult verifyLimits4(Set<Integer> values, int maxValue) {
for (Integer value : values) {
if (value > maxValue) {
return new VerifyLimitsResult(maxValue, value);
}
}
return new VerifyLimitsResult(maxValue, null);
}
If you check a reasonable amount of items and be concerned about the number of objects you create to return the result, there's an alternative with interface
.
First you create an interface
to be called whenever the limit is violated:
// A simple listener to be implemented by the calling method.
public interface OutOfLimitListener {
// Called whenever a limit is violated.
public void outOfLimit(int value, int maxValue);
// ... Add additional results as parameters
// ... Add additional states as methods
}
You can add parameters and/or methods. For example the actual position of the violating value could be a parameter. As antother example add a method that is called at the end of each test with parameters for the number of checks and the number of violates.
An implementation of this interface is passed as argument to your checking method. It calls the listener every time one of the limits is violated:
private boolean verifyLimits(Set<Integer> values, int maxValue, OutOfLimitListener listener) {
boolean result = true; // Assume all values are OK
for (Integer value : values) {
if (value > maxValue) {
listener.outOfLimit(value, maxValue);
result = false; // At least one was out of limits
}
}
return result;
}
And finally you use this method just by implementening the interface:
@Test
public final void test() throws IOException, InterruptedException {
// Make up a test set of random numbers
Set<Integer> testSet = new HashSet<Integer>();
for(int i=0; i<10; i++) testSet.add((int) (Math.random() * 100));
// Implement the interface once with appropriate reaction to an out-of-limit condition
OutOfLimitListener listener = new OutOfLimitListener() {
@Override
public void outOfLimit(int value, int maxValue) {
System.out.printf("The value %d exceeds the maximum value %d\n", value, maxValue);
}
};
// Call verification
verifyLimits(testSet, 50, listener);
}
Android and other GUI Interfaces use this pattern heavily. For me, it got the prefered method when the result contains more then one value.
Create your own custom unchecked exception that extends from RuntimeException.
You can use simple Key-Value, by using HashMap, of course with predefined keys. Return the HashMap for further processing.
I would vote for the second solution (either using IllegalArgumentException or defining a specific one).
Generally good practice is ensuring that any return value from a method can safely be ignored (because some day somebody will forget to check it anyway) and, in cases when ignoring a return value is unsafe, it's always better to throw/catch an exception.
You could return the flag as a boolean and log the results of tests that don't verify, you'll want to log them anyhow...
presuming you'll be checking millions of values.
精彩评论