10 tips to quality Java software (Part one)
I’m not going to recommend you read all the books you can get your hands on, though you could if you wanted to. These are the tips they don’t teach you in books or in school. From deleting code and refusing to comment, through to deliberately throwing exceptions, these tidbits of experience will make you a faster and smarter developer. And that’s what you want, right? Read on ..
Make it simple
Delete delete delete
If a method or chunk of code, or even a whole class is not used, don’t comment it out, just delete it, you won’t need it later. I’m not kidding. In the incredibly rare case you do, you will remember how it works but write it a little more simply. And if something went horribly wrong, you have version control. The improvements in readability and understanding, and reduced test cases for test code that isn’t called, more than saves the time you might lose in having to pull it out of your version control system. You are using version control, right?
Don’t document complex code. Write clear code.
Ironically, this is one of the things you hear a lot of – “make sure you document code that’s hard to understand”. Question: why write code that is hard to understand?
All of the time you spend on simplifying your code will equal the time you would spend on documentation – and reduce the time you spend relearning the code if you have to make changes later (which you will).
Use long variable and method names if you want to, and put your code chunks into very short methods. For instance, in a 50+ line method this was one of the small snippets of code that was really unrelated to a lot of other stuff that happened within the method.
// up the version
version = oldRecord.getVersion();
if (version == -1) {
newRecord.setVersion(1);
} else {
newRecord.setVersion(version + 1);
}
A poor developer might have to spend a more than a few minutes devising an intricate test data set to ensure this was working correctly given that it’s buried inside another method. The way we might do it, is to create a new method, with the snippet above, and having the below snippet instead:
updateVersionOnNewRecord(oldRecord, newRecord);
That will take you about 2-3 minutes to achieve, and you get code that is really easy to test, easy to understand, and what’s more is that you’ll find yourself reusing the method in other places.
Don’t write comments! Seriously! If you have to, write log entries ..
And if you have to use comments, instead of using comments like “uber algorithm below”, make it a log trace entry. Documentation is notorious for being neglected when code is being developed. It’s psychologically much more pertinent when developing code to keep the log trace entry in sync with the code, and it kills two birds with one stone (you get documentation AND you get logging!)
Use the Law of Demeter (for functions) to make your code simple
This law states that any method of an object should call only methods belonging to:
- itself,
- any parameters that were passed into the method,
- any objects created inside the method, and,
- anything directly held by the object.
Read the above again. I’m not saying it’s easy to follow or that you’ll automatically do it first time, though if you keep it in mind writing a few blocks of code, within a few days I know you will find yourself subconsciously writing better quality code.
Don’t Repeat Yourself (DRY)
Duplication is bad. If you have more than 4-5 lines of code that do the same thing in a single class, it is quite possible that you could refactor to remove duplication. It might seem hard to refactor with the code intertwined. Test cases & version control will save you from that fear. If you can see that you would be able to refactor but theres just one or two lines of code that is different in each method that causes you trouble, you may be able to benefit from reflection or use of the Strategy pattern. Don’t be afraid to ask for advice, chances are that it is possible and you can learn some neat new tricks in the process.
By the way, this applies not only to code, but to other resources, like images. For instance, keeping one master image, and automatically generating thumbnails from the master with a script is going to save a lot more time than manually opening your image editor.
No surprises please!
Deliberately break things that are partially completed
If you haven’t implemented a method on an interface, or haven’t completed a section of code, add an UnsupportedOperationException with a good description. Anyone who uses your code now will know exactly what the problem is, allowing them to continue work save time chasing up a random exception that doesn’t make sense. It also provides an easy marker for you to look through and find stuff you’ve missed.
Check your assumptions in code (fail-fast)
Put your hands up if you’ve ever written some code and wondered where the NullPointerException really came from? I think we all have.
Check your assumptions, and don’t be afraid of throwing IllegalArgumentException. For example, if you get passed an array, check that the array has how many elements you think it should. If it doesn’t, you should throw an exception with a clear description. It will take a few seconds to write but you can guarantee if a bug pops up you can much more easily identify the cause and location.
I hope you enjoyed this as much as I did
Leave your comments below; Part Two will be coming up shortly!
September 25th, 2007 at 12:08 pm
You only posted 7 tips, not 10
The Javalobby article was correct.
September 25th, 2007 at 1:29 pm
Thanks Gili — you’re correct, I intended to start with 10, then the list grew too long so I split it into a Part One and Part Two. Part Two is “in the works”
October 4th, 2007 at 2:13 pm
So, the Law of Demeter can be summed up as “no globals”?
October 4th, 2007 at 4:33 pm
No actually — it’s a little more complex than that. Open up a method of code, and check each line for each of those rules.
The code should *only* use:
- itself,
- any parameters that were passed into the method,
- any objects created inside the method, and,
- anything directly held by the object
So, take this line for example:
topObject.getChildObject().setAnswer(someOtherTopObject.getChildObject().getHelper().performCalculation());
Your code is now coupled to the implementations of: TopObject, ChildObject, and Helper. If any one of those changes, your code has to change! To decrease your coupling, follow the rules in the Laws Of Demeter for Fucntions:
For instance, this would become:
topObject.setAnswer(someOtherTopObject.performCalculation());
and then in TopObject you have
public void setAnswer(Calculation c) {
// pass it down the chain
childObject.setAnswer(c);
}
public Calculation performCalculation() {
return childObject.performCalculation();
}
… and so on down the chain. Now if any object changes, you *only* have to change the implementation of the layers that directly use the object.
This does of course mean you have extra methods that don’t “achieve” anything — what they are there for is to reduce coupling and improve maintainability. (Same as logging doesn’t “achieve” anything except improve the future).
Does that make sense?