Whitepapers
The KindSoftware Coding Standards
Table of Contents:
- Introduction
- Documentation
- Naming Conventions
- Semantic Properties
- Tools
- Code Examples
- References
- Specific Recommendations for:
- Java
- Eiffel
- Credits
Java Recommendations
The following are coding recommendations. It is not necessary to follow them to the letter, but we have found it helpful to always keep them in mind.
- Do not use the
*
form ofimport
. Be precise about what you are importing. Check that all declaredimports
are actually used. -
Rationale: Otherwise, readers of your code will have a hard time
understanding its context and dependencies. Some people even prefer
not using
import
at all (thus requiring that every class reference be fully dot-qualified), which avoids all possible ambiguity at the expense of requiring more source code changes if package names change. (We, however, aren't that freaky.) - When sensible, consider writing a
main()
method for the principal class in each program file. Themain()
method should provide a simple unit test or demo. For many distributed classes there isn't a means by which you can build self-contained testing, so don't go crazy trying to make amain()
method for everything. - Rationale: Forms a basis for testing. Also provides usage examples.
- For stand-alone application programs, the class with the
main()
method should be separate from those containing normal classes. - Rationale: Hard-wiring an application program in one of its component class files limits the potential for class reuse.
- Use our template files for the most common types of class files you create - applets, library classes, application classes, abstract classes, exceptions, etc.
- Rationale: Simplifies conformance to coding standards. See the Code Examples section for links to our template classes.
- If you can conceive of someone else implementing a class's functionality differently, define an interface rather than an abstract class. Generally, use abstract classes only when they are "partially abstract"; i.e., they implement some functionality that must be shared across all subclasses.
- Rationale: Interfaces are more flexible than abstract classes. They support multiple inheritance and can be used as "mixins" in otherwise unrelated classes.
- Consider carefully whether any class should implement the
Cloneable
and/orSerializable
interfaces. - Rationale: These are "magic" interfaces in Java, that automatically add possibly-needed functionality only if so requested.
- Declare a class as
final
only if it is a subclass or implementation of a class or interface declaring all of its non-implementation-specific methods (a similar principle applies to the declaration of final methods). - Rationale: Making a class final means that no one will ever get a chance to reimplement its functionality. Defining it instead to be a subclass of a base that is not final means that someone at least gets a chance to subclass the base with an alternate implementation, which will essentially always happen in the long run.
- Never declare instance fields as public.
- Rationale: The standard OO reasons. Making fields public gives up control over internal class structure. Also, if fields are public, methods cannot assume that they have valid values.
- Never rely on implicit initializers for instance
fields (such as the fact that reference variables are initialized
to
null
). - Rationale: Minimizes initialization errors.
- Minimize statics (except for static final constants).
- Rationale: Static fields act like globals in non-OO languages. They make methods more context-dependent, hide possible side-effects, sometimes present synchronized access problems. and are the source of fragile, non-extensible constructions. Also, neither static fields nor static methods are overridable in any useful sense in subclasses.
- Generally prefer
long
toint
, anddouble
tofloat
. But useint
for compatibility with standard Java constructs and classes (the primary example is array indexing and all of the things this implies about, for example, the maximum sizes of arrays). -
Rationale: Arithmetic overflow and underflow can be 4 billion
times less likely with
longs
than withints
; similarly, fewer precision problems occur withdoubles
than withfloats
. On the other hand, because of limitations in Java atomicity guarantees, use of longs and doubles must be synchronized in cases where use of ints and floats sometimes would not be. - Use
final
and/or comment conventions (see themodifies
andvalues
properties) to indicate whether instance fields that never have their values changed after construction are intended to be constant (immutable) for the lifetime of the object (as opposed to those that just happen not to get assigned in a class, but could in a subclass). - Rationale: Access to immutable instance fields generally does not require any synchronization control, but access to mutable fields generally does.
- Generally prefer
protected
toprivate
. -
Rationale: Unless you have good reason for sealing-in a
particular strategy for using a field or method, you might as well
plan for change via subclassing. On the other hand, this almost
always entails more work. Basing other code in a base class around
protected
fields and methods is harder, since you have to either loosen or check assumptions about their properties. (Note that in Java,protected
methods are also accessible from unrelated classes in the same package. There is hardly ever any reason to exploit this, though.) - Avoid unnecessary public instance field access and update
methods. Write public
get/set
-style methods only when they are intrinsic aspects of functionality. - Rationale: Most instance fields in most classes must maintain values that are dependent on those of other instance fields. Allowing them to be read or written in isolation makes it harder to ensure that consistent sets of values are always used.
- Minimize direct internal access to instance fields inside
methods. Use
protected
access and update methods instead (or sometimespublic
ones if they exist anyway). - Rationale: While inconvenient and sometimes overkill, this allows you to vary synchronization and notification policies associated with field access and change in the class and/or its subclasses, which is otherwise a serious impediment to extensiblity in concurrent OO programming.
- Avoid giving a field the same name as one in a superclass.
-
Rationale: This is usually an error. If not, there must be a very
good reason, which should always be explained with a
hides
property. - Declare arrays as
Type[] arrayName
rather thanType arrayName[]
. - Rationale: The second form exists solely for incorrigible C programmers, and makes code less easily readable. The use of "[]" is part of the type, so it should be syntactically adjacent to the rest of the type name.
- Ensure that non-private
static
fields have sensible values, and that non-privatestatic
methods can be executed sensibly, even if no instances are ever created. Use static intitializers (static { ... }
) if necessary. - Rationale: You cannot assume that non-private statics will be accessed only after instances are constructed.
- Write methods that only do "one thing". In particular,
separate out methods that change object state from those that just
rely upon it. A classic example: in a
Stack
class, prefer having two methodsObject top()
andvoid removeTop()
to having the single methodObject pop()
that does both. - Rationale: This simplifies (sometimes, makes even possible) concurrency control and subclass-based extensions.
- Define return types as
void
unless they return results that are not (easily) accessible otherwise. (i.e., refrain from ever writing "return this;
"). -
Rationale: While convenient, the resulting method cascades
(
a.meth1().meth2().meth3()
) can be sources of synchronization problems and other failed expectations about the states of target objects. - Avoid overloading methods on argument type (overriding on arity is
OK, as in having a one-argument version versus a two-argument
version). If you need to specialize behavior according to the class
of an argument, consider instead choosing a general type for the
nominal argument type (often
Object
,Serializable
orCloneable
) and using conditionals which checkinstanceof
(or, reflectively,isAssignableFrom()
). Alternatives include techniques such as double-dispatching or, often best, reformulating methods (and/or their arguments) to remove dependence on exact argument type. -
Rationale: Java method resolution is static, based on the listed
types rather than the actual argument types. This is compounded in
the case of non-
Object
types with coercion charts. In both cases, most programmers have not committed the matching rules to memory. The results can be counterintuitive; thus the source of subtle errors. For example, try to predict the output of this. Then compile and run.
class Classifier { String identify(Object x) { return "object"; } String identify(Integer x) { return "integer"; } } class Relay { String relay(Object obj) { return (new Classifier()).identify(obj); } } public class App { public static void main(String [] args) { Relay relayer = new Relay(); Integer i = new Integer(17); System.out.println(relayer.relay(i)); } }
- Declare
concurrency
property values for at least all public methods. Describe the assumed invocation context and/or rationale for existence or lack of synchronization. If you don't know the concurrency semantics of your method, make it synchronized. - Rationale: In the absence of planning out a set of concurrency control policies, declaring methods as synchronized at least guarantees safety (though not necessarily liveness) in concurrent contexts (every Java program is concurrent to at least some minimal extent). With full synchronization of all methods, the methods may lock up, but the object can never enter into randomly inconsistent states (and thus engage in stupidly or even dangerously wrong behavior) due to concurrency conflicts. If you are worried about efficiency problems due to synchronization, learn enough about concurrent OO programming to plan out more efficient and/or less deadlock-prone policies (i.e., read "Concurrent Programming in Java" by Doug Lea).
- Prefer
synchronized
methods tosynchronized
blocks. - Rationale: Better encsapsulation; less prone to subclassing snags; can be more efficient.
- If you override
Object.equals()
, also overrideObject.hashCode()
, and vice-versa. - Rationale: Essentially all containers and other utilities that group or compare objects in ways depending on equality rely on hashcodes to indicate possible equality. For further guidance see Taligent's Java Cookbook
- Override
readObject()
andwriteObject()
if aSerializable
class relies on any state that could differ across processes, including, in particular,hashCodes
and transient fields. - Rationale: Otherwise, objects of the class will not transport properly.
- If you think that
clone()
may be called in a class you write, then explicitly define it (and declare the class toimplement Cloneable
). -
Rationale: The default shallow-copy version of
clone()
might not do what you want (and, in fact, does not in most cases). - Always document the fact that a method invokes
wait()
. - Rationale: Clients may need to take special actions to avoid nested monitor calls.
- Initialize all (reasonable) fields in all constructors, or do not initialize any fields in any constructors (rely on explicit or implicit initialization in field declarations) and always explicitly call the superclass constructor.
- If you don't initialize it, you don't know what value it really holds. Consistency is the key in this rule so that a reviewer/user/tool doesn't have to search around for every initialization.
- Whenever reasonable, define a default (no-argument) constructor so
objects can be created via
Class.newInstance()
. - Rationale: This allows classes of types unknown at compile time to be dynamically loaded and instantiated (as is done for example when loading unknown Applets from html pages). In Java 1.1 and later, reflection alleviates the need for no-argument constructors somewhat, but many classes which dynamically instantiate other classes at runtime still depend on their presence.
- Prefer
abstract
methods in base classes to those with default no-op implementations. Also, if there is a common default implementation, consider instead writing it as aprotected
method so that subclass authors can just write a one-line implementation to call the default. -
Rationale: The Java compiler will force subclass authors to
implement
abstract
methods, avoiding problems which occur when they forget to do so but should have. - Always use method
equals
instead of operator==
when comparing objects. In particular, do not use==
to compareStrings
. -
Rationale: If someone defined an
equals
method to compare objects, then they want you to use it. Otherwise, the default implementation ofObject.equals()
is just to use==
. - Never use
suspend()/resume()
pairs to implement synchronization with threads. -
Rationale: The
suspend()
andresume()
are inherently fragile, and have even been deprecated by JavaSoft as of JDK 1.2. - Always embed
wait()
calls inwhile
loops which re-wait if the condition being waited for does not hold. -
Rationale: When a
wait()
wakes up, there is no guarantee that the condition it is waiting for has become true. - Use
notifyAll()
instead ofnotify()
. -
Rationale: Classes that use only
notify()
can normally only support at most one kind of wait condition across all methods in the class and all possible subclasses. - Declare a local variable only at that point in the code where you know what its initial value should be.
- Rationale: Minimizes bad assumptions about values of variables. Of course, you should know about the initial value of most local variables at the beginning of the method. Only one-off (temporary, loop, etc.) variables should be declared within the body of a method.
- Name temporary and loop variables appropriately.
- Rationale: Lets the reader immediately know that the variable in question doesn't have a legitimate value unless it is within the code block of its declaration.
- Declare and initialize a new local variable rather than reusing (reassigning) an existing one whose value happens to no longer be used at that program point.
- Rationale: Minimizes bad assumptions about values of variables.
- Assign
null
to any reference variable which is no longer being used. This includes, especially, elements of arrays. - Rationale: Enables the Java garbage collector to do its job.
- Avoid assignments ("
=
") insideif
andwhile
conditions. -
Rationale: These are almost always typos. The Java compiler
catches cases where "
=
" should have been "==
", except when the variable is aboolean
. - Document cases where the return value of a called method is ignored.
-
Rationale: These are usually errors. If it is intentional,
make the intent clear. A simple way to do this is:
int unused = obj.methodReturningInt (args);
- Ensure that there is ultimately a
catch
for all unchecked exceptions that can be dealt with. -
Rationale: Java allows you to not bother declaring or catching
some common easily-handlable exceptions, for example
java.util.NoSuchElementException
. Declare and catch them anyway. It'll make your code more robust. - Embed casts in conditionals. For example:
-
C cx = null; if (x instanceof C) cx = (C) x; else evasiveAction();
Rationale: This forces you to consider what to do if the object is not an instance of the intended class, rather than just generating aClassCastException
. - Document fragile constructions used solely for the sake of optimization.
- Rationale: See Jonathan Hardwick's Java Optimization pages.
- Do not require 100% conformance to rules of thumb such as the ones listed here!
- Rationale: Java allows you program in ways that do not conform to these rules for good reason. Sometimes they provide the only reasonable ways to implement things. And some of these rules make programs less efficient than they might otherwise be, so they are meant to be conscientiously broken when performance is an issue.