Suppose we have a Student class with the following constructor:
public Student(int matrNr, String firstName) { if (matrNr < 10000 || matrNr > 99999 || !firstName.matches("[^\\s]{3,}")) throw new IllegalArgumentException("Pre-conditions not fulfilled");
Correct me if I am wrong, but I think that in this example I followed the design according to the contract paradigm, simply indicating (rather static) restrictions on the possible input values and raising a general, unchecked exception if this is not fulfilled.
Now there is a backend class that manages the list of students, indexed by their number of enrollments. It saves a Map<Integer, Student> to save this mapping and provides access to it using the addStudent method:
public void addStudent(Student student) { students.put(student.getMatrNr(), student); }
Now suppose that there is a restriction on this method, for example: "a student with the same enrollment number should not already exist in the database ."
I see two options for how this can be implemented:
Option A
Define a custom UniquenessException class that raises addStudent if the student is of the same name. number already exists. The code call would look something like this:
try { campus.addStudent(new Student(...)); catch (UniquenessError) { printError("student already existing."); }
Option B
Record the requirement as a prerequisite and simply raise the IAE if it is not met. Also, provide the canAddStudent(Student stud) method, which checks in advance whether addStudent will complete. Then the call code looks something like this:
Student stud = new Student(...); if (campus.canAddStudent(stud)) campus.addStudent(stud); else printError("student already existing.");
I feel that option A is much cleaner from a software point of view, at least for the following reason:
- It's easy to make it thread safe without changing the call code (thanks Voo for pointing to TOCTTOU , which seems to describe this exact problem)
So I wonder:
- Is there another third option?
- Does option B have an advantage that I have not thought about?
- Is it possible to resolve option B from the point of view of the contract from the point of view of use and define uniqueness as a precondition of the
addStudent method? - Is there a rule to define preconditions and just raise the
IAE and when to use the “right” exceptions? I think that “making this a precondition if it does not depend on the current state of the system” may be such a rule. Is there any better?
UPDATE: There seems to be another good option that the public boolean tryAddStudent(...) method should provide, which does not throw an exception but instead signals an error / error using the return value.