How to not duplicate validation code in both object and user input

Viewed 165

I am a beginner in Java and I am writing my first application in JavaFX.

I am accepting input from a user and then creating an object if the user's input is valid.

Here's a simplified code explaining what I am currently doing:

public class Visitor {

    private String id;

    public enum InvalidIdType {
        EMPTY,
        NOT_ALPHANUMERIC,
        NOT_5_CHARACTERS;
    }

    public Visitor(String id) {
        Objects.requireNonNull(id);

        checkId(id);

        this.id = id;
    }

    private void checkId(String id) {
        if (validateId(id).isPresent()) {
            throw new IllegalArgumentException(validateId(id).get.toString());
        }
    }

    public static Optional<InvalidIdType> validateId(String id) {
        if (id.isEmpty()) return Optional.of(InvalidIdType.EMPTY);

        if (id.length != 5) return Optional.of(InvalidIdType.NOT_5_CHARACTERS);

        if (...) return Optional.of(InvalidIdType.NOT_ALPHANUMERIC);

        return Optional.empty(); 
    }
}

So that in the code elsewhere handling the user input, I can do something like:

public class Main {
    public static void main(String[] args) {
        ...
    
        if (Visitor.validateId(userinput).isPresent()) {
            System.out.println(Visitor.validateId(userinput).get().toString())
            // Tells user what is wrong. I switch-case through the different enum values.
        }
    }
}

So that I don't have to validate the code twice in Main.java and in Visitor.java.

But I feel like this creates unnecessarily long code in Visitor.java and is not a common way to validate inputs in Java.

What would be the best practice to validate a field in both the object's constructor and other code which handles the user input, but not duplicate my validation code.

1 Answers

By putting the validation code inside the Visitor class, Visitor owns the responsibility of validation. This is probably not what you want. Typically, you would have an interface for a validator:

@FunctionalInterface
public interface Validator<T> {
    ValidationResult validate(T input);
}

And then you have a chain of Validators, which you can run the input through:

List<Validator<? super String> idValidators = new ArrayList<>();
idValidators.add(id -> id.isEmpty ? new Result(id, EMPTY) : Result.OK);
...
idValidators.stream().allMatch(v -> v.validate(input) == OK); //or check the result one by one for the error

I am not sure if I should expand this approach further because it is quite robust and prepared to do a lot of validation of different kind. Is this what you want? Maybe localized validation is simpler and more concise for you.

Do you really need to validate the input inside the constructor and also outside? If you validate outside, the constructor is initialized with valid data. If you validate inside, you can throw and don't need to validate outside.


Anyway, in your main method you called the validation twice, which is redundant. Use the Optional's methods ifPresent or map if you want to process the values further


Another advice: you wanted to switch over the enum to report the errors. This is not quite a good design, you should invert the control, the ValidationResult should contain the message and you just get it and print it. The message could be provided when creating the Validator or if you want to use enums, predefined in the enum's field.

enum ... {
EMPTY("Empty msg..."),
TOO_SHORT("Less than 5 chars"),
...
}

Validation libraries for JavaFX after a quick Google: https://dzone.com/articles/validation-javafx https://github.com/rrohm/fx-validation I do not know these, you need to do research. However, what I had in mind was creating your own very little validation framework.

Related