Lab 3
Style and Maintainability
There are two important concepts to learn about software engineering:
- Writing code that solves useful problems, and
- Writing code that can be easily maintained.
Too many people focus on #1 at the expense of #2.
Why writing maintainable code is important
There are several reasons why code has to be maintained. The first is bug-fixes. Assume that you write a useful program that lots of people like. The users will eventually find small errors that effect the program's value. If you want people to keep using it, then they need to be fixed.
The second is allowing for growth. Your users will frequently request additional features to make your program easier to use or more useful. Think of the growth in MIDS - the database has a lot of uses, and new features are added every year.
The third is to respond to changes in the environment. Operating Systems get updated constantly, and some of the changes may eventually not be reverse-compatible with your program. Your program will have to be updated and recompiled to keep it working. This happens frequently with Java. The JVM (Java Virtual Machine) changes several times a year. Every time the JVM changes, your program needs to be tested against the new version and any new problems fixed.
We generally spend more money maintaining a program than writing it in the first place. A team of 5 programmers may build a new application in a year, and then a single programmer may maintain it for the next 10 years. That is a 2:1 ratio of maintenance to development costs over the life of the program. We can keep those costs down by writing code that is easily maintainable.
What does maintainable code look like?
That is a tough question. At a minimum, it should:
- have a consistent style throughout the code
- look neat
- be well-organized
- use meaningful variable and class names
- have meaningful file names
- include useful comments
- use common programming conventions that other programmers will easily recognize
- check for invalid data entries before processing them
That last item (checking for invalid data entries) is important. We are going to implement input-checking in today's lab.
What does non-maintainable code look like?
This is an easier question to answer, if only because there are so many good examples.
Here a programmer describes some code a co-worker had written that will be expensive to maintain and can be expected to break frequently (source: http://googlesystem.blogspot.com/2006/07/collection-of-funny-source-code.html):
Once I had a junior programmer writing VB code that needed to know the day of the week. He decided to write his own code, and read something like: if (day = 1) or (day = 8) or (day = 15) or (day = 22) or (day = 29) then weekday = "Monday" elseif (day = 2) or (day = 9) or (day = 16) or (day = 23) or (day = 30) then weekday = "Tuesday" ... Trying to be educational I asked him for his opinion about his own code, and he said that he would simply modify it every month.
There are annual contests to develop "Obfuscated Code". These are computer programs that are nearly impossible to read. The winners of one such contest are here: IOCCC. Take a look at the *.c files on this page for some good negative examples.
Non-maintainable code is easy to spot when you see it, but has many different causes. Here are some clues to help you spot it in the wild:
- You stare at a function for over 5 minutes and still have no idea what it does.
- A function has one or more 'side-effects'. This means that a function is intended to change one variable, but it also changes a second value somewhere in memory, and this is not documented.
- Any file whose author insists on programming without using spaces or tabs.
- A function has more than 4 if() statements.
- The variable names are too short (int p)
- The variable names are too long (int thisIsTheVariableWhereIStoreMyAccountNumber)
- The variable names read like an eye-chart (int xfshortUnlded)
- One class has become a 'junk-drawer' that contains dozens of completely unrelated functions.
- 'Clever' code that does something complex in only 1-2 lines, but is difficult to understand or modify.
- The code does not indicate a legal set of input data. It might assume that the user knows the data limits and will simply not enter anything outside of that range.
Code style
You will see lots of attempts at defining a code-style for your organization. There is no one best style for formatting neat code, but it is important that all members of an organization use the same style as each other. For this purposes of this lab, your code must meet the following style:
Class Names have a first letter that is upper-case. If the name is a compound word, then each subsequent word is capitalized as well. The rest of the word is lower-case.
- Good: Mid, BankTeller, PiecesOfEight
- Bad: MID, mid, Bankteller, bankTeller, PiecesofEight
Variable Names have a first letter that is lower-case. If the name is a compound word, then each subsequent word is capitalized. The rest of the word is lower-case.
- Good: mid, bankTeller, piecesOfEight
- Bad: MID, Mid, mID, bankteller, piecesofEight
Function Names follow the same rules as variable names.
Constant Names are always all caps.
- Good: PI, PLANCK,
- Bad: pi, Planck
Whitespace Every time you nest a new set of brackets {}, the code beneath must be indented exactly 4 spaces. Tabs may not be used for indenting, since different editors will indent tabs different amounts. Below is a good example.
public static class Mid {
public int alpha;
public String firstName;
public Mid(int a) {
alpha = a;
}
}
Brackets Brackets {} indicate function and class boundaries. The open bracket { should be placed on the same line as the function or class name, with one space before it. The closing bracket } should line up under the start of the class/function name. Similar styles should be used for if(), etc. The else {} statement must be lined up under the if {}, and not not be on the same line as the if's closing bracket. See below:
public static class Mid { //GOOD
public Mid(int a){ // BAD
alpha = a;
} // GOOD
public Mid(int a)
{ // BAD
alpha = a;
} // BAD
public Mid(int a){alpha=a;} // VERY BAD
public void test() { // GOOD
if(alpha) { // GOOD
...
} else { // BAD
...
} // GOOD
else { // GOOD
}
} // GOOD
Lab Assignment
In this lab, we are going to implement a fairly simple program that uses the tools you have learned so far in class. The key element that we want you to think about maintainability. Your code must meet all of the design requirements given below as well as the style guide given above.
Your code is going to read four float values from the command line. These values are the X and Y coordinates of two points. Your program is going to calculate the length of the line segment that connects those points.
Our classes
Point.java will hold the values of the vertices.
Line.java will hold the two Points and will calculate the length between them.
Lab03.java will read in the floats from the command line, verify that they are proper input values, pass them the Line's constructor, and ask the Line for its length. It will output an error and quit if it detects invalid command-line input.
The assignment
Here are the program Requirements:
- The main program must be named Lab03.java. You must have two importable class files named Point.java and Line.java.
- The main program must read four floats from the command line arguments. The first two values are the X and Y coordinates of point1, and the last two values are the X and Y coordinates of point2.
- The X and Y coordinates must be in the range (-1..1). If any of them are outside this range then the program should output an error message and exit.
- If the number of arguments on the command-line is not exactly four, then the program should output an error message and exit.
- Error messages should begin with the text "ERROR:" and then provide a helpful sentence explaining the problem that caused the program to quit.
- The Point class contains two floats that represent its X and Y coordinates. It should have a constructor that initializes these values when it is created. The coordinate variables should be left public, with no get() or set() functions required.
- The Line class contains two Point objects. It initializes them both during the Line() constructor. The Line constructor must be passed four floats.
- The Line class contains one member function called getLength() that returns the length of the line-segment between the two Points.
- The Lab03 class cannot directly interact with the Points in any way. It can only interact with the Line class.
- You code must be able to run for any legal input value. Think through your line-length algorithm and make sure it will. HINT - is it possible to enter values that cause a div by zero? If so, you need to re-think your algorithm.
- Example runs are shown below. Your code should match this functionality:
~/ic211/Lab03$ java Lab03 1 1 -1 -1 Line length: 2.8284 ~/ic211/Lab03$ java Lab03 1 1 -1 -1 0 ERROR: Too many command-line variables. Four required. ~/ic211/Lab03$ java Lab03 1 1 -1 ERROR: Too few command-line variables. Four required. ~/ic211/Lab03$ java Lab03 1 1 -1 2 ERROR: Values must be in range -1 <= n <= 1.
Extra Credit
Here is one extra credit item. Your program should additionally check whether the four items passed on the command-line are numbers at all. Doing so requires using Exceptions, which we have not covered yet. Basically, an Exception is Java's way of saying that it encountered an unexpected error and does not know how to proceed. An example of an Exception is trying to covert the word "Potato" into a float. This type of error happens frequently when we parse user-input.
- If any of the command-line arguments cannot be interpreted as floats, then the program should output an error message and exit.
Exceptions use a try/catch statment that is similar to an if/else statement. The catch{} block runs only if the try{} block encounters an error.
This program iterates over the command-line and checks whether the input strings can be converted to floats:
public class ExceptTest {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
Float f = Float.parseFloat(args[i]);
System.out.println("FLOAT: " + f);
}
catch (NumberFormatException nfe) {
System.out.printf("ERROR: cannot convert '%s' to a float.\n", args[i]);
}
}
}
}
~/ic211/Lab03$ java ExceptTest 1 1.2 1.2.3 Potato 4
FLOAT: 1.0
FLOAT: 1.2
ERROR: cannot convert '1.2.3' to a float.
ERROR: cannot convert 'Potato' to a float.
FLOAT: 4.0
If you run the above input without using a try{} block, you will get the following error:
Exception in thread "main" java.lang.NumberFormatException:
What to hand in
You need to hand in the three files that you created:
- Lab03.java
- Line.java
- Point.java