Home Commenting preconditions and postconditions
Preconditions and Postconditions
When writing a method header comment, focus on describing the preconditions and postconditions for that method. You are required to do this for both public and private methods.
As mentioned earlier, the method header comment is NOT to describe how that particular method works, but rather to describe how to use it.
More specifically, what you are required to comment on is preconditions and postconditions for using that method.
Preconditions are any conditions that the caller, or client of the method must fulfill in order to successfully use your method. So, if an int parameter must be greater then five, or if the caller must call some other method first before using yours, you would qualify all those as preconditions.
Postconditions are any conditions that you, the writer of the method, will guarantee to be true after the method has finished running. So, if your method always prints something, or always returns some double between 0 and 1, you would qualify those all as postconditions.
Together, preconditions and postconditions fulfill a sort of "contract" or "promise" between you (the implementor) and whoever is using your method or class (the client). The client agrees to fulfill the specified preconditions before calling your method, and you promise to satisfy the listed postconditions in exchange.
(And when that doesn't take place, well, that's where bugs come from)
Sometimes, your methods may not have preconditions. It may be that a client does not need to do or know anything at all to successfully call your method. In those cases, it's ok to not mention preconditions at all.
However, every method should have a postcondition. If it doesn't, it means that method is doing absolutely nothing the client can detect, in which case that method is useless and should be deleted. We will never ask you to write a method that is completely useless in this way.
Here is an example of what good comment discussing pre and post conditions looks like:
// Counts the number of occurrences of the provided name // in the given list of names. // // Pre: // - The list of names must not be null, otherwise an IllegalArgumentException // will be thrown. // - The target name must not be null, otherwise an IllegalArgumentException // will be thrown. // // Post: // - Returns the number of times targetName appears in names as a a positive int. public static int numberOfOccurrences(List<String> names, String targetName) { int count = 0; for (String name : names) { if (name.equals(targetName)) } count += 1; } } return count; }
However, you are not required to explicitly mark preconditions and postconditions so long as you mention that information in some way. For example, the following would also be acceptable:
// Counts and returns the number of occurrences of the provided name // in the given list of names as a positive int. Throws an IllegalArgumentException // if either the list of names or the target name is null. public static int numberOfOccurrences(List<String> names; String targetName) { // ... }
You are free to present the information in any format you wish, as long as the content we're looking for is there.
Implementation detail
Your method header comments should never include implementation detail – information about how exactly the method or class works.
One useful way of determining if something would count as implementation detail is to ask yourself if your code would survive a total internal rewrite. If it doesn't, odds are it contains implementation detail.
When commenting, you should try and write comments that are robust and focus on describing the contract or promise between the caller and the implementor: preconditions and postconditions. They should not mention how exactly that contract is fulfilled (that way, the implementor is free to change their mind about how exactly to go about writing a method without ever bothering the client).
A useful rule of thumb is to ask yourself if your comments would survive a total internal rewrite. More specifically, imagine that somebody took the homework spec, and produced a program that has full external correctness, but decided to make the class internally completely crazy. They used advanced material, crazy data structures, a completely different programming language, horrible style, etc...
Because both the twisted version and the normal version of the assignment both have full external correctness, and behave completely identically from the perspective of the client, you should be able to take your comments, copy-and-paste them into the twisted program, and still have the comments perfectly and accurately describe the behavior of the twisted program. Their internal behavior might be completely different, but they're identical externally, so your comments should apply to both versions.
As an example an example of how this process might work, let's say that we have a method
which we're adding to our ArrayIntList
class where we find the sum of all the
numbers. Well, this might be one valid way of writing the method:
public int sum() { int output = 0; for (int i = 0; i < this.size; i++) { output += this.elementData[i]; } return output; }
Now, let's say that we added the following (bad) method header comment:
// Uses a for loop to iterate through this.elementData up to the `size`-th // element and adds together and returns each number. public int sum() { int output = 0; for (int i = 0; i < this.size; i++) { output += this.elementData[i]; } return output; }
This is an example of a very bad comment – it exposes far too
much implementation detail. We don't care about exactly how the method works, all we care
about is what it's meant to do. It also wouldn't survive a complete internal
rewrite. For example, what if we were to change the name of our fields to data
and length
?
// Uses a for loop to iterate through this.elementData up to the `size`-th // element and adds together and returns each number. public int sum() { int output = 0; for (int i = 0; i < this.length; i++) { output += this.data[i]; } return output; }
Well, our comment is now suddenly outdated – we're no longer using those specific fields! We can edit our comment to remove all reference to fields to something like so:
// Uses a for loop to iterate through the internal array and adds up each // element together and returns the final sum.
Well, we're still not done. For example, let's say that we decided to rewrite the code to use a while loop:
// Uses a for loop to iterate through the internal array and adds up each // element together and returns the final sum. public int sum() { int output = 0; int i = 0; while (i < this.length) { output += this.data[i]; i += 1; } return output; }
Again, our comment is now suddenly outdated. We're no longer using a for loop! Well, we can try and patch this up by editing our comment to remove all references to a for loop:
// Uses a loop to iterate through the internal array and adds up each // element together and returns the final sum.
But even this isn't great – what if instead, for whatever reason, we decided to
use an ArrayList
to store our data instead of an array? (And if you're
wondering why you'd use an ArrayList
to represent an
ArrayIntList
, well, who knows? We did say our hypothetical
implementor was crazy, after all).
import java.util.*; // ...snip... // Uses a loop to iterate through the internal array and adds up each // element together and returns the final sum. public int sum() { int output = 0; for (int num : this.data) { output += num; } return output; }
Again, our comment did not survive an internal rewrite. In particular, we aren't even using an array. Again, we rewrite:
// Uses a loop to iterate through the list and adds up each // element together and returns the final sum.
Still not good enough. What if we decided to completely abandon CSE 14x and use something that's hugely and blatantly advanced material?
import java.util.*; import java.util.stream.*; // ...snip... // Uses a loop to iterate through the list and adds up each // element together and returns the final sum. public int sum() { return this.data.stream().limit(this.size).sum(); }
Well, crap – what even is that? We've blown way past 142 and 143 material, and it doesn't look like we're even using a loop in our method – our comments still did not survive the internal rewrite.
Well, let's try again. We might not be using a loop, but we're still iterating in some way, right?
// Iterates through the list and adds up each // element together and returns the final sum.
Still not good enough. What if instead of iterating over it, we stored the sum as a
separate field, and added and subtracted from it whenever we added or removed a number
from our ArrayIntList
?
// Iterates through the list and adds up each // element together and returns the final sum. public int sum() { return this.totalSum; }
Failure again. Our comment still hasn't survived an internal rewrite. Well, fine, we can rewrite to the following:
// Returns the sum of all numbers in the ArrayIntList. public int sum() { return this.totalSum; }
And finally, for the very first time, we've hit upon a correct comment with absolutely no implementation detail. No matter how we chose to write our method, this comment will still stay perfectly accurate and describe every aspect of our method in perfect detail:
// Returns the sum of all numbers in the ArrayIntList. public int sum() { int output = 0; for (int i = 0; i < this.size; i++) { output += this.elementData[i]; } return output; } // Returns the sum of all numbers in the ArrayIntList. public int sum() { int output = 0; for (int i = 0; i < this.length; i++) { output += this.data[i]; } return output; } // Returns the sum of all numbers in the ArrayIntList. public int sum() { int output = 0; int i = 0; while (i < this.length) { output += this.data[i]; i += 1; } return output; } // Returns the sum of all numbers in the ArrayIntList. public int sum() { int output = 0; for (int num : this.data) { output += num; } return output; } // Returns the sum of all numbers in the ArrayIntList. public int sum() { return this.data.stream().limit(this.size).sum(); } // Returns the sum of all numbers in the ArrayIntList. public int sum() { return this.totalSum; }
Notice how our comment works perfectly no matter how our method is implemented. Our comment can survive a complete internal rewrite and therefore contains no implementation detail.
You will still have to spend some time making sure you've covered all the preconditions and postconditions, but at least you know that there's no material that you should be actively deleting.
Assumptions about the client
Your comments should never make any assumptions about the behavior of the client.
This principle is the inverse of implementation detail: your comments should not expose internal detail, and should not speculate on external detail. That is because your class should be flexible enough to be reused by a theoretically infinite number of clients, and it's silly to bind your class to a specific client.
Again, comments document the boundary between client and implementation. You should never mention either of the two, and restrict yourself to only talking about that boundary and interface between the two.
For example, the following would be an example of a bad comment – it assumes that the Scanner object is reading from a file:
// Reads in a sequence of lines from a file, and stores them in a list public List<String> readFile(Scanner input) { List<String> output = new LinkedList<String>(); while (input.hasNextLine()) { output.add(input.nextLine()); } return output; }
This would be another example of a bad comment, since it mentions a specific client by name:
// Represents a chunk of arbitrary text as a sequence of characters for a guessing game // program public class String { // ..snip.. }
You should assume that a potentially infinite number of clients may choose to use your particular object for any arbitrary reason. Consequently, you should not "hard-code" in references to specific clients into your comments.
Retain only useful information
We do not enforce any sort of length requirement or restriction for comments. You are free to make your comments as long or short as you'd like, so long as don't omit any important information and all the information included is actually useful.
This will mean that your method header comments may sometimes be only single sentence long, while others will be several paragraphs.
For example, here is an example of poor commenting:
// This method takes in three parameters, the first of which is a // variable of type int named 'times', the second of which is a variable // of type String named 'a', and the third of which is _also_ a variable of // type String named 'b'. This method will return a string where a and // b are added together and repeated the number of times the int // parameter tells us to. // The int param must not be a negative number. public void alternate(int times, String a, String b) { // Adds together the strings outside the loop instead of // inside to make the code more efficient String combined = a + b; // Start of cumulative sum! String output = ""; // Start repeating and looping for (int i = 0; i < times; i++) { output += combined; } // Returns the output return output; }
While the comment technically doesn't really contain implementation detail, and while all of the comments are technically accurate, it also isn't very useful. A lot of the information was just repeating the method signature, which is a bit redundant since the client can see that information anyway. The inline comments are also not very useful – they often comment on things that are obvious or just repeat what the code is saying.
Here's a more concise version:
// Returns a string where 'a' and 'b' are alternating the given // number of 'times'. Example: // // alternate(3, "--", "*") // "--*--*--*" // // pre: 'times' must not be negative. public void alternate(int times, String a, String b) { // combines strings outside loop for efficiency String combined = a + b; String output = ""; for (int i = 0; i < times; i++) { output += combined; } return output; }
This is much more concise. It conveys the same amount of information, but requires less words to do so.
Formatting precondition/postcondition comments
We are not picky about how exactly you document your preconditions and postconditions as long as the information we're looking for is there. You could either use the style we've used in this style guide, use Javadocs format, or use your own conventions.
See formatting your comments for more details.