CPSC 225 | Intermediate Programming | Spring 2025 |
Together, this lab and lab 2 address ensuring program correctness. Lab 2 focused on testing code to uncover bugs. This lab focuses on two other aspects: support for reasoning about correctness (identifying and checking preconditions, postconditions, class invariants, and loop invariants) and preventing mistakes in the first place (defensive programming and clean code naming practices).
Labs are due at the start of lab on the date listed. It is OK to hand in your files at the very beginning of lab, but you should not be spending part or all of a lab period finishing up the previous week's lab.
To be eligible for revise-and-resubmit, you must turn in something by the due date. Late work and extensions are generally not accepted/granted; see the posted late policy for more information.
You may work with a partner if you wish on part 1 only. You must do part 2 yourself.
If you do part I with a partner, only one person needs to hand in RomanNumeral.java — be sure to include both teammates' names in the file!
Otherwise you may get help but you may not use other resources (friends, neighbors, websites, ChatGPT, etc) to produce answers. See the posted policy on academic integrity for more information.
To hand in your work:
Make sure that your name is at the beginning of each file (use the @author tag in Java files) and that all your Java files have been auto-formatted.
Copy the entire lab3 project directory from your workspace ~/cs225/workspace to your handin directory in /classes/cs225/handin.
Check that everything got handed in correctly — navigate to your handin directory and make sure it contains a directory lab3 with your code-review file and a subdirectory src containing RomanNumeral.
Create a new Java project called lab3 in Eclipse.
Import the two Java files from /classes/cs225/lab3 into your project. Make sure they end up in the src directory!
RomanNumeral.java contains an implementation of a class to represent Roman numerals. It may have bugs.
Your job in this part is to add information (comments) and checks relevant to correctness — things which should have been incorporated from the beginning as the code was being developed.
The public comments (for the class and its public methods) define what correct behavior is.
Add to the class comment, constructor comments, and public method comments to reflect that RomanNumeral is intended to support Roman numbers in standard form as described in the Wikipedia link above.
Add preconditions for the public constructors and methods as appropriate. Some preconditions may already have been identified in the previous step, but also be sure to consider limits on valid values for any parameters. Don't forget to consider values like null or an empty string. (Are those valid?) "Add" means to include a statement of the precondition in the relevant comment.
Add postconditions for the public methods as appropriate. "Add" means to include a statement of the postcondition in the relevant comment.
Check the preconditions identified — write code! For complex checks like "in standard form", first break the check down into simpler pieces — what properties does a Roman numeral in standard form have? (e.g. What symbols can appear? What order do they appear in? What rules are there for consecutive symbols?) Then write a descriptively-named private helper method to do each check. For now those helper methods can be placeholders and simply return true.
Class invariants, loop invariants, other invariants, and postconditions all address reasoning about correctness — writing them down makes you consciously think about how you can know that your code is working correctly, and checking them helps in testing and debugging. These should be written as you are developing the code in the first place.
Since RomanNumeral is intended to work with Roman numerals in standard form, it is reasonable that numeral_'s value should always be in standard form.
State this class invariant: add a comment to the declaration of numeral_ reflecting the intention (and assumption) that it is in standard form.
Check this invariant at the end of every constructor and public method — write code! As with the preconditions, break complex checks into simpler properties that can be checked and write descriptively-named private helper methods for each simpler properties. These helpers can be placeholders that simply return true.
Realistically, it's only necessary to check the class invariant at the end of methods that change numeral_ — those are the only ones that might break something. Balance not pasting an extra check with the risk that a future change to the method might change numeral_.
Algorithms involving converting to and from Roman numerals and doing arithmetic with Roman numerals can be complicated. Additional invariants (including pre- and postconditions for private helper methods) can help back up assumptions and verify reasoning about the correctness of those algorithms.
Add and check pre- and postconditions for the provided private helper methods (toAdditive, toSubtractive, merge, condense, getSymbolValue, toInt, and toRoman). Since these methods support the particular implementation of RomanNumeral's functionality (there's already thinking about algorithms that went into their creation), the pre- and postconditions have been identified for you — look for comments at the beginning and/or end of the bodies for each of those methods. "Add" here refers to adding information to the public comments for the method; "check" means writing code. As in earlier steps, break complex conditions down into simpler properties and write descriptively-named private helper methods to check (but you don't need to implement the bodies).
Loop invariants can be used to check intermediate stages of algorithms.
(advanced — optional!) merge, toInt, and toRoman contain loops. Understand what's going on the loops and identify and check appropriate loop invariants.
Enable assertions as discussed in class and run RomanMain to verify that you can run a program with assertions on.
Copy your main program from project 1A (the original version of flip) into your lab3 project. Make sure it ends up in the src directory!
Create a new text file code-review. Save it in your lab3 directory (not src). Be sure to put your name at the beginning of the file.
Review your flip code according to the defensive programming and clean code naming guidelines discussed in class and listed below. For each, evaluate how well your code adheres to the principle: fully (no exceptions), satisfactory (in most cases), unsatisfactory (often or mostly not), not applicable. Also write a paragraph or two explaining your evaluation for each — how your code demonstrates each principle or where it doesn't and how you would fix the issues. Put your answers in the code-review file.
Criteria to address:
Always use {} for loop and conditional bodies. (Not applicable if you don't have any with only one statement in the body.)
Don't rely on default values.
Don't rely on pass through behavior in conditionals.
Don't trust outside data.
Names should reveal intent.
Names should be informative so comments describing what the variable is for should generally not be necessary.
Avoid short variable names and long function names in large scopes.
Reduce vertical scope.
Avoid disinformation.
Make meaningful distinctions and pick one word per concept.