-
Notifications
You must be signed in to change notification settings - Fork 5
Developer Guide
This project aims to produce high quality software. Therefore it is very important that the code is well designed, documented and tested. No sub-project should be released (as official version) if it has NOT been tested intensively (though it can obviously still have bugs).
The project is using maven as build- and management-tool. Therefore the maven conventions apply to this project. If you are NOT familiar with maven please read the following guides:
For version control we are using GIT. If you are using windows, download and install msysgit and tortoisegit. If you are having trouble that your gitconfig gets lost and git thinks your homedrive is on drive Z: then go to windows environment variables and set HOME to %USERPROFILE%. On linux just install via package-manager.
After installation check out the entire codebase from https://github.com/m-m-m/mmm.git
The recommended IDE is Eclipse. After installing (JEE edition) you need to install the following plugins:
- m2e http://download.eclipse.org/technology/m2e/releases/
- m2e-wtp http://download.eclipse.org/m2e-wtp/releases/
- m2e-tycho http://repository.tesla.io:8081/nexus/content/sites/m2e.extras/m2eclipse-tycho/0.6.0/N/0.6.0.201207302152/
- gwt http://dl.google.com/eclipse/plugin/4.2
- eclipse-cs http://eclipse-cs.sf.net/update/
- egit http://download.eclipse.org/egit/updates/
- templatevariables TODO
- optional: anyedit http://andrei.gmxhome.de/eclipse/
Go to the menu and choose File > Import. Then select General > Preferences and click Next. Click Browse and from the checked out codebase select mmm-setup/src/settings/eclipse/mmm.epf.
Go to the menu and choose File > Import. Then select Maven > Existing Maven Projects and click Next. Click Browse and select the checked out codebase. From the available projects deselect all via root node and only select all the leaf nodes. Click Finish and proceed.
Please study the clickable Architecture. It gives you an overview of the project and contains the most important classes that directly link to their JavaDoc.
The code produced by this project follows a common code-style. To check these rules, configurations for checkstyle and eclipse are provided (see Setup). Further there are additional rules to follow:
- Names for classes and interfaces are english words that describe the type briefly. They are in the typical caml case (e.g. AbstractWidget). Shortcuts and acronyms are NOT capitalized as part of names (e.g.
XmlUtil
and notXMLUtil). - There are no artificial conventions for names of interfaces (like
I
as prefix orIF
as suffix). The interfaces should have the nice names while implementations may be named artificial (*Impl
). - The code should be well documented using javadoc. It does NOT help just to satisfy checkstyle with some generated javadoc nonsense. Please help to maintain the javadoc of the code and make it precise. Make intensive use of the
{@link FooClass}
constructs to specify what you are talking about.
This project follows the principle of a component-oriented-design. So what is a component? First of all a common definition:
"A component is a reusable piece of software that offers its functionallity through a well-defined and self-contained API. Its implementation is therefore exchangeable and can be composed using other components."
While such definition is easily agreed by most developers it is still very unprecise. If you want to drag the term component down to the code, one might mean some specific interface another one thinks about the content of a java-package. The following section defines rules that help to track this down to the code and give it a common structure. However the most important part about the design is how functionallity is grouped and exposed in the API, what can never be expressed or checked by formal rules.
The projects package namespace is net.sf.mmm
. All packages should be sub-packages of this namespace. Each logical unit of code declares a specific package (say net.sf.mmm.foo
) and is devided into the following subpackages:
-
api
contains the API and consists of interfaces and exception classes. It may also contain simple container classes (e.g. event-classes or maybe java-beans). Once released, this API should be stable. It may be extended but existing types and methods should NOT be changed or removed. Theapi
code must NOT import classes frombase
orimpl
packages. -
base
contains the basic implementation of the API. It is typically the common bridge between the API and exchangeable implementations. Therefore it contains abstract classes that implement parts of the API that are NOT intended to be realized in a different way. If someone outside this project wants to write a custom implementation, he is supposed to extend these classes rather than directly implementing the API interfaces. If in some specific circumstance the API has to be extended, compatibility can be gained by covering this in the base implementation. Additionally base packages may contain non-abstract classes or simple implementations that can be instantiated by end-users directly. However they should be used via the API afterwards and such classes are potentially less stable than the API. Thebase
code must NOT import classes fromimpl
packages. -
impl
contains the implementation(s) of the API. It should NOT be used by the end-user (directly in the code). It may change internaly in subsequent releases. -
-
The package itself (example wasnet.sf.mmm.foo
) may also contain classes. This can be an NlsBundle that contains messages that can be localized. Such class should only be used by code below this package.
Each of these sub-packages may be devided into further sub-sub-packages (say net.sf.mmm.foo.api.bar
) if desireable according to size and complexity. The implementation (net.sf.mmm.foo.impl
) can be bundled in a seperate module. In such case the api
and base is typically bundled in an api module (mmm-foo-api
) while the impl
is in a separate module (mmm-foo-impl
). Within mmm-util
the impl
will NOT be separated while it should be separated in other modules. If there are multiple implementations for different aspects that each consist of various classes, they should be separated as well. This applies for java-packages (net.sf.mmm.foo.impl.aspect1
) as well as for modules (mmm-foo-impl-aspect1
).
Users should only use the API what can easily be checked via the import statements (should contain api
, should NOT contain base
and especially NOT impl
). This NOT only applies to external users but also to other code of this project, though NOT always possible. A container framework is (to be) used for creating instances of API interfaces. Test-cases or very simple applications may directly create and setup implementations (from an impl
) package if using such framework would be overdosed.
The following table illustrates which packages (first column) may reference (import) which other packages (top-row). With reference we are NOT talking about JavaDoc @link
tags.
reference -> | foo | foo.api | foo.base | foo.impl | bar.api | bar.base | bar.impl |
---|---|---|---|---|---|---|---|
foo | X | - | - | - | X | - | - |
foo.api | X | X | - | - | X | - | - |
foo.base | X | X | X | - | X | (X) | - |
foo.impl | X | X | X | X | X | (X) | - |
For utility functions it is quite common to write some class with lots of nice static methods. While this makes it easy for the user to access the functionality it prevents an extendable design. Some simple feature may end in a complex operation where a static method is NOT a suitable solution. Therefore utilities in this project are declared by an interface (FooUtil
) and a default implementation (FooUtilImpl
). To make it easy for the user the implementation offeres a static getInstance()
method that gets a singleton instance of the utility. However the suggested usage is to use an IoC-framework (e.g. springframework) and manage the utilities as regular components. This dual usage principle is maybe the best trade-off between ease-of-use and design for extension.
Java makes resource handling very easy. The trade-off is that there are many common mistakes developers do because they stop thinking about resource management.
If you deal with streams (InputStream
, OutputStream
, or related stuff) please be very careful to ensure they are closed properly. Whenever an API method takes a stream as parameter, the JavaDoc must specify whether the stream is closed or not. If you want to close a stream make proper use of finally statements. The following example does NOT always close streams properly:
FileInputStream sourceStream = new FileInputStream(source); FileOutputStream destinationStream = new FileOutputStream(destination); FileChannel sourceChannel = sourceStream.getChannel(); try { sourceChannel.transferTo(0, sourceChannel.size(), destinationStream.getChannel()); } finally { destinationStream.close(); sourceStream.close(); }
A correct version looks as following:
FileInputStream sourceStream = new FileInputStream(source); try { FileOutputStream destinationStream = new FileOutputStream(destination); try { FileChannel sourceChannel = sourceStream.getChannel(); sourceChannel.transferTo(0, sourceChannel.size(), destinationStream.getChannel()); } finally { destinationStream.close(); } } finally { sourceStream.close(); }
However, this is still problematic as an exception during transfer may be lost if close()
will also cause an exception.
This is a design flaw of java that has been somewhat corrected in java7. Until then we already support this with NlsThrowable
.
The above also applies to transactions of sessions that have to be closed. The difference is that you will get into trouble way faster if your code is wrong.
The garbage-collector is a fine thing, but anyways it does NOT prevent you from creating memory-holes in your software. Therefore you should carefully dereference objects that are no longer needed. This especially applies to objects stored in collections. If you use a map to cache objects you need to ensure that the cache size can only grow to a defined maximum and objects can still be dereferenced using weak- or soft-references.
If you want to load a resource from the classpath do NOT use the following code-style:
InputStream resourceStream = MyClass.class.getResourceAsStream("org/mypackage/my-resource.xml");
This will NOT work properly in every environment such as some application-servers or frameworks that deal with special class-loaders. Instead use net.sf.mmm.util.resource.ClasspathResource
as following:
DataResource resource = new ClasspathResource(org.mypackage.MyClass.class.getPackage(), "my-resource.xml"); InputStream resourceStream = resource.openStream();
In non-functional programming languages it is complicated to deal with concurrent access as you have shared resources. Please avoid static
without final
. If you design a class always consider if it is possible to do something in a thread-safe way. On the other hand never assume that some implementation is thread-safe if NOT explicitly specified in the JavaDoc or annotated via @Singleton
or @Stateless
.
For each sub-project module-tests should verify the integrity of the code. Therefore JUnit and its extensions are used. Maven (surefire) is used to verify the unit-tests during the build process. The test-results and test-coverage are generated as report per sub-project on the web-site. It is hard to reach a high coverage but this is a goal of the project to increase the coverage continuously.
This documentation is licensed under the Creative Commons License (Attribution 4.0 International).