Unmaintainable Java Technique
9 PM
August 1, 2003
Inspired by Cedric Beust’s thoughts on coding conventions, I present my contribution to the discipline of writing unmaintainable Java code. These hints and tips have been collected from a variety of Java projects, over many years.1 In the hands-on spirit of industry-standard, panic-driven development, this list concerns itself purely with low-level coding and cowboy-style pseudo-design techniques.
In no particular order:
- Declare classes and methods ‘final’, for no good reason. If you are have trouble coming up with some bad reasons, here are two: performance, and distrust of the abilities of the developer who comes after you.
- Initialise class members where they are declared, and then initialise them again in the constructor. This practice is without any defensible rationale.
- Initialise class members with zero, false and null. The maintenance difficulties created are slight but gratuitous.
- When coding with arrays, use
null rather than a zero length array. This causes every for loop iterating over the array to need wrapping with if (array != null). A similar effect can be achieved when coding with collection classes.
- Separate function from the data it operates on. Insist all data be stuffed into logic-free Java beans, and all logic into data-free utility classes. Have the courage to repudiate the OO-paradigm.
- Design in such a way that programmers need to write code in the form of
object.getX().getX(). For bonus unmaintenance points, ensure that getX() and getX() return different types.
- Write 9 line if statements.
if (something1()
&& something2()
&& something3()
&& something4()
&& something5()
&& something6()
&& something7()
&& something8()
&& something9()) {
...
- Here is a trick for storing numeric values in database rows. Allow only positive values to be stored into the row. If the value is allowed to be negative at runtime, have the programmer indicate the sign of the value with a flag stored elsewhere in the database row.
- Use as many EJBs as possible, wherever possible. Some ideas to get you started: each table in your database schema can support at least one CMP EJB; a stateless session bean makes an ideal interface, so use one to front each CMP EJB; finally, tie together your entire system with a further layer (or two!) of EJBs.
- When coding
long constants, use a lower-case ‘l’ in preference to upper-case ‘L’. Even with plain font such as Courier, 1l can cause all kinds of havoc. And with proportional san-serif fonts being supported by more and more IDEs, things can only get less clear and more unmaintainable.
- Never use a raw
boolean values in a condition. The classic example is ”if (number.isEven() == true) {…”, a trifle which is easily ignored by a maintenance programmer. However, given the right opportunity and a small amount of thoughtlessness, this simple device can finesse one line of code into five:
if (number.isEven() == true) {
return true;
} else {
return false;
}
- It is a little known fact that many commercial J2EE servers are quite resilient to references to unused or non-existent classes and methods in configuration files. Recently, while working on an EJB with three finder methods, I was pleasantly surprised to find five finder method definitions amongst the ejb-jar file extensions.2 Experiment to see what kind of cruft your app server allows.
- Unusable programmatic interfaces should be natural consequences, not brute-force design decisions. When developing library code, concentrate on least-effort implementation, performance, or—if all else fails—internal elegance.
- “Ctrl-C, Ctrl-V” is your friend.
- And finally, make sure that at least one of the applications classes exceeds five thousand lines. This is especially important if the IDE in use cannot handle such large files gracefully.
PS: Most of these are not fatal—they are simple to correct, and would be caught in even a cursory code review. Even so, I am thankful that I haven’t come across a single project that has put all of these into practice.
1 It’s just a bunch of notes really, but there would be too much irony in any more formal presentation.
2 I couldn’t be bothered to work out which two were not being used, so I left them all there while I added a new one.
Comments
In in doubt, mark methods 'private'. If you aren't *sure* that users of your API will need access to a method, then they don't need it, right?
Use post-compilation bytecode processing of your classes. With OOP (polymorphism etc.) you never know what code will be really executed, so why to give any hope to guess it?
Design in such a way that programmers need to write code in the form of object.getX().getX(). For bonus unmaintenance points, ensure that getX() and getX() return different types.
I find this one particularly funny because it's built into the Java API. In Java1.3 (which I'm still forced to use) there is a Calendar.getTimeInMillis() method but it is protected so I can't get at it unless I want to subclass it and wrap it. Thus I'm forced to use Calendar.getTime(), which returns a Date, and then Date.getTime() to return time in millis, thus producing calendar.getTime().getTime() where X and Y are two different return types :).
(This condition has apparently been fixed in Java1.4 by removing the protected restriction from Calendar.getTimeInMillis).
Alan, for a more comprehensive list of techniques, check out
http://mindprod.com/unmain.html
> if (number.isEven() == true) {
> return true;
> } else {
> return false;
> }
I find this so annoying that I wrote a PMD rule for it - see SimplifyBooleanReturnsRule here:
http://pmd.sourceforge.net/rules/design.html
Tom
... and those
1)Always create a java.lang.String as
String something = new String("something");
2)Do not bother creating object when you can pass just a list of parameters to a method:
void someMethod(int aParam1, String aParam2, byte[] aParam3, Object aParam4,String aParam5,String aParam6) {...}
3) Circular dependancies among packages will add more fun to your development.
4) Do not forget to add public abstract to all methods declared in an interface.
5) Test every parameter for null. Who knows what idiot will call your method ?
6) Make sure that at least several classes have public inner interfaces. See Netbeans code for references of how to do it.
7) If you make every method and variable public, you will not have any problem accessing what you want.
ResultSet res = prepStmt.executeQuery();
if (res != null && res.next()) {
...
In both "Practical Java" and "Effective Java" it's suggested to return zero-length arrays and collections instead of returning a null pointer. This avoids the "if (arr == null)" and makes code shorter and easier to read.
Use constructor as a function!
For instance, if you are required to:
1) build an object, initialise data,
2) invoke functions to log text files, calculate whatever
Do not bother to do it all step by step! Simply declare functions as static(even if they are using non static data) and invoke them in the constructor body. Add them all, the more the merrier!
This is an ingenious way to relieve the burden from your code. Instead of creating an object and then invoking functions(what a tedious routine), simply instantiate an object and let its constructor do all the work in the background.
Sounds like fun, doesnt it?
Language == C#
.ToString() pitfall
Use object .ToString() function or System.Convert.ToString()?
That is the question!
I recommend using .ToString() function as often as possible. This is an inherited function, defined in the object class, root class of all objects. Thats why all objects must implement it by overriding it, or simply inheriting it.
Sadly, if .ToString() of an uninitialised object(value == null) is invoked, the program crashes. Thats OK, the object does not exist, thus its ToString function does not exist its not declared static. To compensate use:
if(obj1 != null){
str_target = obj1.ToString();
}
else{
str_target = ;
}
It would be really stupid to use System.Convert.ToString() and avoid the problem. It would make ones life as a programmer quite dull.
Use less specific types for internal fields and for getter methods, even when there is no doubt about the specific type. If anyone complains about all the casts, argue that this is the price to pay for collecting common code in an abstract super class.
In the following example, Tiger extends Cat. Note how elegantly TigerCage exposes its contents as merely a cat. This will add fun, casts, and checks for future generations of developers.
---
public abstract class CatCage { private final Cat cat; public CatCage(Cat cat) { this.cat = cat; } public Cat getCat() { return cat; } }public class TigerCage extends CatCage { public TigerCage(Tiger tiger) { super(cat); } public void Foo() { if ((Tiger)getCat()).isBengal() { .. } } }---
Casting Cat to Tiger in the TigerCage class is a nuisance but at least it is done in some kind of context. The real impact lies in code like the following:
public class TigerHandler { public void handleCage(TigerCage tigerCage) { Cat cat = tigerCage.getCat(); if (cat instanceof Tiger) { // this should always be the case Tiger tiger = (Tiger)cat; } else { // does this ever happen? Should ask the // previous developer but he was sacked for // some reason. Should I throw a // FelineException? Log an error? Quit? } } }---
PS. Bonus points awarded for initializing important stuff in the block where Cat is cast to Tiger. After all, we all know that the Cat in a TigerCage is always a Tiger so what could probably go wrong?