Language
The are currently no plans to internationalise BEAM. The default language to be used throughout the BEAM user interfaces and documentation is British English.
Java Language
BEAM 4.x uses Java 1.6. The Java language specification level is 5.0 (including enum keyword, autoboxing, foreach statement, generics, annotations).
Java Annotation Standard
Configure the intention/quickfix settings of your IDE to match the following standard
- @Override is mandatory if methods are overridden and is suggested if methods are implemented.
- @Deprecated is suggested for deprecated elements
Java API
How to tell API from Non-API
Any sub-package named internal and all of its sub-packages are considered to contain implementation details and are definitely not public API, thus no API documentation will be generated for them.
Although BEAM comprises many public Java interfaces and classes, the actual BEAM API to be used by clients is defined by the following packages:
- org.esa.beam.framework with all sub-packages
- org.esa.beam.util with all sub-packages
- org.esa.beam.visat without all sub-packages
All other packages are considered to contain interfaces which may change at any time.
API Documentation Conventions
General
Class documentation should provide the user with the following informations:
- What is it, what does it represent?
- What does it (... in the system, ... for clients)
- What is its purpose, how, when and why is it used?
- Optional usage (source code) examples
Java File Templates
Use the following file template for classes, interfaces and enums:
/** * todo - add API doc * * @author Norman Fomferra * @version $Revision$ $Date$ * @since BEAM 4.2 */ public interface Makeable { }
In order to activate the SVN keyword replacement, set svn:keywords=Revision Date using the SVN command line or your IDE. In IDEA:
- Select your project directory
- In the main menu, goto {Version Control/Subverseion/Set Property
- In the property list, select name svn:keywords
- Enter value Revision Date
Classes and Interfaces:
Make use of the following API doc snippets:
/** * <p>Classes that implement this interface must provide a constructor with no arguments.</p> * <p>This class can be instantiated by a \{@link XXXFactory\}.</p> * <p>This class can be used without \{@link XXX\} running.</p> * <p>This class can not be extended by clients.</p> * <p>This class can not be extended or instantiated by clients.</p> * <p>This interface is intended to be implemented by clients.</p> * <p>This interface is not intended to be implemented by clients.</p> * <p>This interface is not intended to be implemented or extended by clients.</p> * <p>Clients shall not implement or extend the interface <code>XXX</code> directly. Instead they should derive from \{@link AbstractXXX\}.</p> * <p>This class/interface is for debugging purposes only.</p> * <p>For internal use by the \{@link XXX\} only.</p> * <p>This class/interface is public for the benefit of the implementation of another (internal) class and its API may change in future releases of the software.</p> * <p><b>Note:</b> This class/interface is part of an interim API that is still under development and expected to change significantly before reaching stability. It is being made available at this early stage to solicit feedback from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken (repeatedly) as the API evolves.</p> * @author author * @version $Revision$ $Date$ * (non-Javadoc) */ public interface Makeable { }
Methods:
Make use of the following API doc snippets:
public interface Makeable { /** * <p>This method must complete and return to its caller in a timely manner.</p> * <p>For internal use by the \{@link XXX\} only.</p> * <p>The default implementation does nothing.</p> * <p>Clients must not call this method directly (although super calls are okay).</p> * <p>Subclasses may override.</p> * <p>It is generally a bad idea to override with an empty method.</p> * <p>It is okay to call \{@link XXX.xxx()\} from this method.</p> * <p>Subclasses may override.</p> * <p>This method is for debugging purposes only.</p> * <p>This method is public for the benefit of the implementation of another (internal) * class and its API may change in future releases of the software.</p> * (non-Javadoc) * @return <code>true</code> if XXX, <code>false</code> otherwise */ void makeIt(); }
Type, Method and Variable Names
- Always use a single and unique naming sheme within a given context. Consider to use one of the following naming schemes and use it consistently:
- Camel-case, glued (e.g. Java, C++, C#): AbstractProgressEmitter, delegateList
- Lower-case, underscrore separated (e.g. C, IDL): abstract_progress_change_emitter, delegate_list
- Lower-case, minus separated (e.g. XML elements): abstract-progress-change-emitter, delegate-list
- Upper-case, underscrore separated (e.g. constant definitions): ABSTRACT_PROGRESS_CHANGE_EMITTER, DELEGATE_LIST
- If you add new names to an existing context, always use the existing naming convention, if any.
- Consider to handle external configuration parameters to be read in by a software as if they where class fields (properties) of objects internally used by the software. Use the same naming convention for both the external and internal names.
- Carefully keep an eye on lower/upper case letters, especially in case sensitive contexts.
- Adhere to the following method naming rules, especially for public APIs:
- PropertyType getPropertyName();
- boolean isPropertyName();
- void setPropertyName(PropertyType propertyName);
- Type createElementName(Parameters...);
- void addElementName(ElementType elementName);
- void removeElementName(ElementType elementName);
- int getElementNameCount();
- int getNumElementNames();
- ElementType getElementName(int index);
- ElementType getElementName(KeyType key);
- void setElementName(int index, ElementType elementName);
- Method names should always start with a verb. Exceptions are event listener methods such as mouseMoved.
- Use meaningful method names which directly describe the actual action performed.
- The method's API documentation shall not differ from the action expressed by the method's name and the things it actually does.
- Adhere to the following variable naming rules:
- Use meaningful method names which clearly describe a property, state or an other thing. Exceptions from this rule are shortcuts for very frequently used variables such as i for index, it for iterator, e for exception or error, etc.
- Instance fields, especially property fields, shall be named in a way that prepending the verbs get, is (for boolean types) and set yield meaningfull method names.
- Name collection types as follows: Type[[] elementNames; Map elementNameMap; List elementNameList; etc. For example: double[[] weights; Color[[] colors; List layerList; Map tableInfoMap;
Object Oriented Design
Concepts
- Abstraction
- Encapsulation
- Polymorphism
- Inheritance
Principles
- Encapsulate what varies
- Favour composition over inheritance
- Program to interfaces, not implementations
And furthermore
- Loose coupling principle - Strive for loosely coupled designs between objects that interact.
- Open-closed principle- Classes should be open for extension but closed for modification.
- Dependency inversion principle - Depend upon abstractions, do not depend upon concrete classes.
- Least knowledge principle - Only talk to friends.
- Hollywood principle - Don't call us, we'll call you.
- Single responsibility principle - A class should only have one reason to change.
Design
- Prefer well-known design patterns. Always search for existing solutions for a given design problem. Study JDK implementation by Sun or inspect source code of very commonly used environments and libraries such as Apache, Eclipse, etc.
- Don't design a framework before you have at least a couple of independently implemented use cases.
- Don't publish interfaces, classes, methods etc. before you are absolutely sure, that this is indispensable. With other words, keep things private (private, making things protected also means publishing them) as long as possible. Note, it is always possible to publish items later, but not the other way round.
- Keep interfaces and classes simple as possible. Only add essential methods, don't pollute interfaces with utility methods, which could be obviously implemented by using the interface's fundamental methods.
- Try to replace inheritance by delegation whenever you can.
- A 'has-a' relationship is more flexible than a 'is-a' relationship and keeps the actual interface of the owner small.
- Inherited code is difficult to test
- Inherited classes inherit also more dependencies
- In situations where inheritance is mandatory, avoid deriving your class from a concrete super-class. Try to implement an interface or at least from an abstract base class. You can use a concrete class for the actual implementation.
- Minimize number of if-statements.
- When designing a new object, clearly define the valid internal states of objects. Which fields can be null, which shall never be null? Which fields can change during lifetime of the object, which not? If you get this things clear, you can easily set aside recurring if (variable != null)}}s and {{if (variable == null)}}s. This will make you code much easier to to read and understand. You can also declare variables using the {{final (Java) or const (C++) keywords in order to express their inmutuability.
- You can try to replace conditional statements by abstract strategies. (A 100% object-oriented design uses if-statements exclusively in factory methods.)
- Make use of UML or UML-like diagrams in order to present and discuss architectural designs to and with your colleagues. The most important diagram types for that purpose are class, sequence and action diagrams. UML is well understood, don't use your own diagramming style which you must explain to the team again and again.
- Avoid returning the instance of an array or collection property from a method. Since the array or collection may have it's contents modified by the calling method, this construct may result in an object having it's state modified unexpectedly. While occasionally useful for performance reasons, this construct is inherently bug-prone.
- Use assertions to ensure valid state before executing blocks of code. In this way, it can be avoided to have recurring conditional statements checking the value of fields.
- In the API documentations of public and protected methods, always state which parameters can be null and if return values can be null. Alternatively lay down a general design contract for you API, e.g. NullPointerExceptions are always thrown if null arguments are passed.
- Avoid redundant multiple and two-way dependencies especially if they are used just for convenience in situations where delegations can be used. Use multiple delegations instead in order to derive property values of associated objects.
- Prefer to use general I/O streams instead of files, URLs or path strings. This is valid for both class fields and method parameters and return values. Streams are more general and can be substituted in UnitTests and enable pipelining results as input to different processes without forcing slow disk I/O.
- Keep separated levels of concern in separated packages. E.g. keep GUI code (and dependency upon UI-components), technical code in separate packages. Never have classes being dependent upon UI components as well as database backend code. Hide low level dependencies (java.sql, java.swing etc.) behind facades.
- Minimize dependencies between classes and packages and avoid mutual dependencies between packages.
Coding
- Write human-readable code. Readability is much more important than dubious but optimized code. Let the compiler do the optimization job.
- Don't use inexpressive names such as fRet, strRet, pDesc etc. for variables.
- The same variable shall not be used for different purposes, instaed declare a new one with a name that clearly signals its usage in the current context or code block. Single and very rare exception: extreme performance requirements for very frequently processed code lines such as image pixel processing.
- Extract private methods with meaningful method names instead of having longish method implementations containg multiple annotated code blocks.
- Take the time to write complete numbers. So write 0.24 instead of .24, 12.0 instead of 12., 2.0e-4 instead of 2.e-4
- In methods, local variables shall not be declared before they are used.
- Use the final (Java) or const (C++) keyword in order express that the initial value of a local variable will not change within a method. This makes it easier to understand the code.
- Avoid setting multiple properties in a single setter in order to avoid side effects. Don't let getters (get_PropertyName_, is_PropertyName_) directly modify the object's state, if not avoidable use delegation to e.g. register new instances in an associated map.
- Generally try to avoid side-effects in public and protected methods which return something.
- Always try to avoid to let a private method return something and additionally modify the object's state. Ideally, methods should either return values or modify the object. This code often emerges from careless application of the refactoring extract method when large methods are broken down into parts.
- Public and protected factory methods (create_Thing_) should always have a non-void return type.
- Don't out-comment code without leaving a note what the intention was. Others will meditate and speculate what your intention was. Prefer to implement code that you might need later in a private, annotated method so that it gets compiled too.
- Don't declare and use field variables when you can use getters and setters instead. Make an exception only for extreme performance requirements when you are not sure whether or not the compiler will perform an inlining or the VM a hot-spot compilation.
- Internationalize text in your application using external text files (Java: resource bundles) for each country. If you don't plan to internationalize the code now, mark each string constant which is natural language, for example add /I18N/ close to the string, so that it can be found and externalized later.
- try to avoid the Java instanceof operator or equivalent operators in other languages whenever you can
- these code patterns help to avoid monster classes:
- Often you need a "utility" method in a derived class. Because you feel, the method is very useful also for other people, you define it in the base class or interface. Consider putting it into a helper class which uses the interface. Typically such methods are static.
- Some utility methods don't alter the objects state at all or perform just on a small subset of instance varibles. Consider defining a job-class just for the purpose of executing the utility function. Think in terms of configuring the job-class with your actual class or interface. This is absolutely the right pattern if you already have instance variables, which are just supporting a certain kind of operation within your class and don't implement a declared interface, e.g. a simple Java Bean providing control parameters for the job. Note that a job-class can perform some kind of function more efficiently due to a thread-safe implementation and by maintaining job local resources.
Exception Handling
- Clearly differenciate between runtime exceptions and exceptions whose causes lie outside the responsibility of the software developer, such as I/O errors or missing or invalid external configuration parameters.
- Don't confuse returning null with throwing an exception. It shall be clearly stated in the interface documentation whether or not a method can return null or throws an exception in the case that a resource can not be retrieved. It should also be made clear whether returning null is an error condition or not.
- Avoid catching any type of {{RuntimeException}}s. Note that this rule is not limited to the Java language. {{RuntimeException}}s shall only be catched if an appropriate handler exists (logging is not enough in this case).
- In general, don't catch any exceptions without handling them. In rare situtations it can be applicable to convert a high level exception into a RuntimeException. Source: Exception handling confusion in BEAM processor framework and processor implementations. Not all fixed: refer to org.esa.beam.processor.binning.database.QuadTreeDatabase and its interface.
- Don't abuse RuntimeException}}s in order to test validity of an object, e.g. {{java.lang.NumberFormatException. Write appropriate code in this case, e.g. public static boolean isValidIntegerFormat(String s).
- In the case of illegal null arguments, always throw NullPointerException and not an IllegalArgumentException. Reasons:
- null is not an Object and thus not an expected argument instance
- illegal means not legal but not missing
- if the argument is not checked for null before it is used, an NullPointerException would be the result in most cases
- Whether a method should throw a checked or unchecked exception is often hard to decide. An often discussed example is the case of a singular matrix. Shall Matrix.solve() throw a runtime exception or a checked SingularMatrixException? Solution: if there is an API which lets a client query the state of an object before the call of the relevant method would lead to an error, throw a runtime exception otherwise declare a checked exception. With other words, API users shall not be forced to catch any runtime exceptions when using the API. Relating to the example above, if we have a Matrix.isSingular() method then Matrix.solve() can throw a runtime exception, otherwise we should declare a checked SingularMatrixException.
Code Optimizations
This section compiles some ruels of thumb which should be applied in any case. These rules will not affect the code readability.
- generally implement a non-optimized version first and make it run
- if the optimization is expensive, don't implement it before you have developed
- a set of unit level tests which extensively test the interface parts which should behave identically after optimization
- one ore more spikes that show that the optimization will significantly increase performance
- consider using the Strategy pattern in order to support both the non-optimized and optimized algorithm.
- consider using the Flight-Weight pattern if many equal instances are owned by an object at the same time
- consider using the "create resource not before used" code pattern to safe memory. Examples for a resource are an open file, a listener list or an UI component. Use this code pattern if
- it is normal that a resource will never be used within an object
- the resource is expensive and/or many resource owners can exists at the same time (see also Flight-Weight pattern)
- For multiple nested loops, try always to position the loop with the most number of iterations inside the others.
- Keep in mind: function calls, primitive type arithmetic and primitive type comparision, stack pushs/pops cost nothing, object and array allocations, I/O stream handling are generally expensive
- Java: Use a single, local StringBuffer instance to assemble big strings in frequently called methods instead of using + or += operators.
- Java: Make use of the final and private keywords where applicable
Java Unit Level Testing
- Don't ever check in un-tested code.
- Don't test human readable text constants, since they are subject to change and should actually be kept in resource bundles. It is OK to test the invariant key names which are used to adress the message texts.
- Don't catch unexpected exceptions in a test. Instead, add related exceptions to the throws clause of the test method in order to treat them as errors and not as test failures.
- Don't out-comment test code because it does not run green. Instead make it run.
User Interface Design & Layout
- Before you start to develop a complex UI, familiarise yourself with UI design in general, e.g. take a look into Java LAF Design Guidelines.
- Use consistent text labels
- Use ellipsis after text '...' for buttons and menu items which open modal dialogs, e.g. "Edit Properties..."
- Texts of buttons, menu items, tabs, groups borders and table headers:* Capital first letter of each substantive in a sentence
- Texts of labels, checkboxes and radio buttons:* Capital first letter of first word in sentence. You should use a colon ':' for text of a label which is annotating an input field directly afer the last word of the text. But if you use a colon in such cases, use it always or never.
- Toolbar buttons: Use similar icon art, use similar color schemes, same sizes