What prerequisites are reasonable in a contract project?

Suppose we have a Student class with the following constructor:

 /** Initializes a student instance. * @param matrNr matriculation number (allowed range: 10000 to 99999) * @param firstName first name (at least 3 characters, no whitespace) */ public Student(int matrNr, String firstName) { if (matrNr < 10000 || matrNr > 99999 || !firstName.matches("[^\\s]{3,}")) throw new IllegalArgumentException("Pre-conditions not fulfilled"); // we're safe at this point. } 

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.

+4
source share
2 answers

(comment too long)

In your option B, I will not use Map <Integer, Student>, and then do:

 if (campus.hasStudent(12000)) printError("student already existing."); else campus.addStudent(new Student(...)); 

Map abstraction is not practical enough for your use case (you mention concurrency issues), I would instead use ConcurrentMap <Integer, Student> and do something like this:

 final Student candidate = new Student(...); final Student res = putIfAbsent(student.getMatrNr(), candidate) if ( res != null ) { throw new IllegalStateException("Class contract violation: \"student already exists!\", please read the doc"); } 
+2
source

I do not believe that the base class manages the list of students, will be related to the contract - that is, that it contains Map<Integer, Student> , will not be part of the contract. So casting the enrollment number to the contract in hasStudent(int matrNr) seems a bit evil.

I would suggest that the campus probably should have a Boolean hasStudent(Student student) method that will check if there is a student on the campus based on any condition. If a contract requires uniqueness and is truly exceptional, you should use a contract check:

  Student student= new Student(int matrNbr, String name); if (campus.hasStudent(student) { throw new UniquenessException(); } else { campus.add(student); } 

Selected exceptions must match the contract as arguments and return values

UPDATE

If an addition should simply fail, if uniqueness fails and is not exceptional, then do not throw an exception. Instead, make a successful addition of the return value (as in java.util.HashSet.add ()). Thus, campus.add(Student) will return true if the student was added.

+2
source

Source: https://habr.com/ru/post/1396238/


All Articles