Java Conventions
OpenMRS tries to follow a common coding style in order to make our code base robust: more readable and more reliable (if the same code is created by three different developers, it should look and read very similar). Our style conventions are designed to promote consistency and good coding practices, while reducing common errors. If you feel that our coding styles could be improved (by changing, adding, or removing something), please bring your comments to the Developers Mailing List.
We use a combination of tools (checkstyle, PMD, FindBugs, ...) to write down our coding conventions in a set of rules. These rules are picked up by services (provided by our own infrastructure like SonarQube or cloud providers like codacy) so that any addition of new code (via a commit or pull request on Github) is checked for violations of these rules. Having the rules in simple config files together with the source code enable any developer to run the same checks locally.
- 1 Tools
- 1.1 Checkstyle
- 1.1.1 Manual Run
- 1.2 PMD
- 1.2.1 Manual Run
- 1.3 FindBugs
- 1.3.1 Manual Run
- 1.1 Checkstyle
- 2 Automation
- 3 How-To Write Code According to the Conventions
- 3.1 Unit Tests
- 4 Conventions
- 4.1 File template
- 4.2 Attribution
- 4.3 Code Style
- 4.4 Imports
- 4.4.1 Remove unused imports
- 4.4.2 Do not use wildcard imports
- 4.5 Naming Conventions
- 4.6 Strings
- 4.6.1 Concatenation
- 4.6.2 Use of StringUtils
- 4.7 Do not comment out code
- 4.8 Logging
- 4.8.1 Background
- 4.8.2 How To Log
- 4.8.3 How To (Not) Log
- 4.8.4 Code Templates
- 4.9 More Conventions
- 4.9.1 Do not print out
- 4.9.2 Modifier order
- 4.9.3 Others
- 4.10 Documentation
- 4.11 Exception Handling
- 4.11.1 General
- 4.11.2 Creating Custom Exceptions
- 4.12 Patching Third Party Libraries
- 4.13 New Public Methods and Classes
- 4.14 Deprecation
- 4.15 Security
- 4.15.1 Avoiding XSS scripting
- 4.16 Perfomance Optimization
- 4.16.1 Java API level
- 4.16.2 Openmrs API Level
- 4.17 Resources
Tools
The following tools are used to ease code review, help new developers get used to our coding style and maintain code quality.
Checkstyle
We use checkstyle for static code analysis. checkstyle defines rules http://checkstyle.sourceforge.net/checks.html which when violated in the code will result in errors or warnings. The checkstyle rules we want our code to adhere to are defined in the openmrs-core repostitory's checkstyle.xml
Manual Run
You can run checkstyle locally using the maven checkstyle plugin with the following command
mvn clean compile jxr:aggregate checkstyle:checkstyle-aggregate
This will generate an html report at
target/site/checkstyle-aggregate.html
(the path is relative to the root of the openmrs-core repository)
The report will show you all the violations for each java class with their severity, the name of the rule and the location (the jxr portion in the above command enables you to navigate to the location of the violation in the java class). If you want to know more about a specific rule go to http://checkstyle.sourceforge.net/checks.html
PMD
PMD is used to check for issues like unused, unnecessary or error prone code. The PMD rules we want our code to adhere to are defined in the openmrs-core repostitory's ruleset.xml
Manual Run
Install PMD locally and try
./pmd-bin-5.5.4/bin/run.sh pmd -d openmrs/openmrs-core/api/src/main/java -f text -R openmrs/openmrs-core/ruleset.xml -version 1.8 -language java
Adjust the version and location of the PMD you installed and refer to the PDM documentation to see the available command line options.
FindBugs
FindBugs is another tool we use to check the code for issues. The FindBugs rules we want our code to adhere to are defined in the openmrs-core repository's findbugs-include.xml
Manual Run
At the moment you cannot check the code against the FindBugs rules locally using maven as with checkstyle. We are working on it
Automation
Codacy & Github Integration
We have set up integration with codacy so that our rules will be checked on every pull request. Whenever you make a pull request your code will be commented by codacy and if its clean the check will pass and if not show you what parts of your code violated which rules. You can see the current status of the openmrs-core at https://www.codacy.com/app/openmrs/openmrs-core/dashboard
SonarQube
SonarQube checks the openmrs-core code once a day. You can find the issues it found here
Read FindBugs to see where the rules come from.
Limitations & Work In Progress
We aim to bring all our rules (FindBugs, ESLint) into the openmrs-core repository and to let codacy use both set of rules to check the code. Codacy is currently configured to read our checkstyle and PMD rules from the repository to check Java code that goes into it.
Other languages such as javascript, ruby, ... might be checked as well but we do not yet have formalized these rules into config files like ESLint or JSHint. If you want to do that go ahead!
FindBugs rules will not be checked by codacy since that is a feature only available to enterprise users. If you know another cloud provider that satisfies our needs and supports FindBugs please tell us!
The topic is discussed here:
How-To Write Code According to the Conventions
Make sure you have followed the How-To Setup And Use Your IDE so that your code changes will automatically be formatted according to the OpenMRS style.
Unit Tests
Add the OpenMRS code templates to your IDE (Eclipse or IntelliJ) which will make it easy for you to insert tests according to our conventions
Read and follow Unit Testing Conventions
Conventions
The following conventions are applied to code in openmrs-core but we encourage module authors to adopt them as well.
File template
OpenMRS License Header. All code should start with the OpenMRS license header which can be found OpenMRS core repository.
File Length. Files should not exceed 2500 lines. If your class exceeds this length, consider refactoring your code to break it into more reasonably sized files. Overly large classes are harder to read.
One class per file. Do not put more than one class in your .java file(s).
Attribution
Attribution is to be done within commit comments according to the the comment conventions.
Attribution (either author or contributing authors) should not be placed into source code. When encountered, such attribution should be removed by (or with permission from) the original author(s). Contributions that require attribution (i.e., author(s) demanding attribution within the code contributions) will be graciously refused or removed from the repository.
(NOTE: this attribution-free coding policy took effect after version 1.1, so you may see some attribution in the existing code; we are gradually removing attribution from our code base)
Code Style
Include curly braces around
if
clauses, and loops even when there is only a single statement inside, that is, instead of this:if (!someBooleanFlag) ...perform something with a one liner;
Write this:
For classes that extend BaseOpenmrsObject, do not override the
equals()
andhashcode()
methods unless this is necessary for the semantics of the class. If you do overrideequals()
orhashcode()
, be sure that you override both of them.Avoid unnecessary else statements. normally you should be able to configure your IDE to catch this and warn you about it. That is instead of this:
Write this:
Favouring return types that refer to interfaces rather than concrete implementations where possible. This is especially true for objects from the Java Collections API. For example, favour returning
List<Concept>
overLinkedList<Concept>
. This way the implementation can be changed without affecting code that relies on the return type.When writing services, the
@Transactional
annotation should be added to the implementing class at the class level and not to the interface. All methods that don't write to the database, e.g getXXX methods, should have their own@Transactional
annotation with thereadOnly
attribute set to true.Java 8 lambdas should only be used for short, easily understood blocks of code and should not be wrapped in curly braces unless absolutely necessary. That is, instead of this:
Write this:
Where possible, use method references instead of lambdas. That is, instead of this:
Write this:
Imports
Make sure you have followed the How-To Setup And Use Your IDE which will prevent you from running into the following issues.
Remove unused imports
Imports that are not used should be removed, remember that you introduce dependencies that are not needed!
Do not use wildcard imports
If you are importing multiple classes from a package, list them all individually, like in the following code block, since this makes it clear exactly what imported classes are in use in any file:
Do not do the following:
Please check your IDE settings it is often the IDE which merges several imports into a wildcard import.
Naming Conventions
Package names should be lowercase without punctuation, following Java specs.
Class and interface names should be capitalized (using CamelCase), begin with a letter, and contain only letters and numbers.
Method names should follow Java specs.
Parameter names should follow Java specs.
Class and instance variables should follow Java specs. Any constants should be written in uppercase letters.
Strings
Concatenation
Since Java 6 String concatenation is implemented through the StringBuilder
(or StringBuffer
) class and its append
method (source: String javadoc). Commonly String concatenation is more readable than StringBuilder
(or StringBuffer
), because takes up less space.
It is means that in most cases we should use
instead of
For concatenation when logging, see How To Log.
Use of StringUtils
Do not do this: "
if (s == null || s.equals("")) ...
". Instead do "if (StringUtils.isNotEmpty(s) ...
"We have both the apache commons StringUtils and springframework StringUtils library. If possible, use the apache commons StringUtils method. However, if the spring StringUtils is already imported, use that one.
Do not comment out code
Do not leave commented out code in the source files. Just remove it. Others will think this has been left there for a purpose and thus will probably not dare to delete it which will leave this commented out code hanging around forever. There are some exceptions of course, such as: "When I'm pretty sure it needs re-enabled at some point, and to make sure I don't forget why that is, I leave a clear TODO comment, e.g. "TODO this needs re-enabled when TRUNK-XXXX is fixed". Most IDEs are good at parsing out all TODO statements." @Rowan Seymour
Logging
Background
OpenMRS uses the logging facade https://www.slf4j.org/ which in turn delegates to the log4j as the logging implementation. If you want some background info on these libraries and their relation to Apache commons-logging and spring read this http://docs.spring.io/spring/docs/4.1.4.RELEASE/spring-framework-reference/htmlsingle/#overview-not-using-commons-logging
How To Log
If you want to log you should rely on the slf4j. The following shows a typical usage borrowed from their API documentation
SLF4J 1.6.x is not supported more than two String arguments. In this case you can use
How To (Not) Log
You might need to construct a message for logging and want to prevent this construction from happening if the debug log level is not enabled. You could then come up with a pattern like this
which you should not use even if you see it in existing code (if you see this clean it up).
Instead you should use parametrized messages:
Why? Read https://www.slf4j.org/faq.html#logging_performance for the answer.
Code Templates
Add the OpenMRS code templates to your IDE (Eclipse or IntelliJ) which will save you typing and help you stick to the above conventions.
More Conventions
Do not print out
System.out.println(). Do not use
System.out.
println() in your code; instead logex.printStackTrace(). Do not use
ex.
printStackTrace() in your code; instead log
Modifier order
Modifiers should follow the order suggested by the Java Language specification, sections 8.1.1, 8.3.1, 8.4.3 and 9.4:
public
protected
private
abstract
default
static
final
transient
volatile
synchronized
native
strictfp
We enforce this via Checkstyle rule ModifierOrder
Others
Long method signatures. Method signatures should be kept under 400 characters in length.
Too many parameters. Method signatures should have less than 20 parameters.
new Boolean(). You should not instantiate the
Boolean
class; rather, useBoolean.TRUE
and/orBoolean.FALSE
.foo = bar = 0. Avoid inline assignments; rather, break them into separate lines of code. Inline assignments can reduce code readability and may represent an error: equality (h1. ) accidentally entered as assignment (=).
Long lines. Lines should not exceed 125 characters.
Switch default case. All switch statements should have a
default
case if only toassert false
, since future coding changes may introduce unexpected cases.Switch fall through. All switch cases containing any code should end with a
break
orreturn
statement to avoid accidentally falling through to the next case. In the rare case that you desire a fall through, the next case should be preceded with/* fall through */
to indicate your intention.Magic Numbers. Literal numbers should only exist within constants, rather than being introduced directly in the code.
Literal Long Numbers. Always use a capital L for long numbers, since a lowercase L (l) is easily confused with a one (1).
equals() without hashCode(). Classes that override equals() should also override hashCode()'.
Nested If Statements. If statements should not be nested deeper than eight (8) levels.
Nested Try Statements. Try statements should not be nested deeper than three (3) levels.
No clone(). Classes should not override the clone() method.
(expression false). Do not test equality with
true
orfalse
; rather, simplify the boolean expression by removing the comparison and, if necessary, adding a leading exclamation mark (!) if you are testing forfalse
expressions.if Statements. If statements should always be enclosed in braces. Always write "if (expr) {doSomething();}", never write "if (expr) doSomething();"
Loop Braces. There should be braces for the case of any loop even there is only one statement. Always write "while (condition) {doSomething();}", never write "while (condition) doSomething();"
Documentation
All classes and interfaces should, at a minimum, have a Javadoc comment at the class level
Public methods should have a Javadoc comment
Code that performs any non-obvious task should be documented for clarification
Specify the return value of every method. Return values that should be documented are: null values, empty collections (whether an empty sets or empty list is always returned, etc)
Test this return value assumption and annotate them with @should as described by Unit Tests
Please note that the commenting style from most textbooks is only a good practice when the comments are intended for a student learning to program. It is not intended for professional programmers. Take a look at these best practices for commenting your code.
Exception Handling
General
catch (Exception e) {}. Do not catch
Exception
,Throwable
, orRunTimeException
; rather, you should catch more specific exceptions. Catching these high level exceptions can mask errors.throw Throwable. Do not throw
Throwable
,Error
, orRuntimeException
; rather, you should throw more specific exceptions.
Creating Custom Exceptions
Create an intuitive hierarchy of exceptions within the package they belong, only creating new Exception classes for when the need to branch on different types of exception within code/webapp are needed (add as needed instead of making extra classes "just in case" for every possible variation).
For example:
org.openmrs.api.APIException
org.openmrs.api.PatientIdentifierException extends APIException
org.openmrs.api.MissingPatientIdentifierException extends PatientIdentifierException
and later add:
when we realize the webapp needs to distinguish duplicates from invalid identifier exceptions.
Patching Third Party Libraries
In general, this should be avoided. If you feel it is needed, please discuss with the dev community!
Conventions within Patching Third Party Libraries
New Public Methods and Classes
All new public methods and classes should have a @since annotation for the version when they were introduced. If the class is new, then it is enough to just put it at the class level instead of each method. Something like: @since 2.0
Deprecation
We deprecate methods that are part of our external public interface, instead of changing/deleting them in order to preserve backwards compatibility
External, public methods include:
service methods
public interface methods
domain object methods
Internal methods are not considered public, and therefore do not have to go through a deprecation cycle, and can be changed/deleted outright. These include:
DAO methods
We will delete all deprecated methods when we make a new version (e.g from 1.x to 2.0)
Use both the @Deprecated annotation and the @deprecated javadoc comment
The @deprecated javadoc annotation should point to the new method that is replacing the current one
The @deprecated javadoc annotation should also mention the version when the deprecation happened. e.g: @deprecated As of 2.0, replaced by {@link #getEncounters(EncounterSearchCriteria)}
Security
Avoiding XSS scripting
In JSPs, use a core.OutTag for every string type attribute (or even for every variable)
Example: instead of this:
write this:
When using our custom "openmrs:message" or tag, make sure to use htmlEscape unless you really need html/js tags in the message:
StringEscapeUtils.escapeJavaScript() and StringEscapeUtils.escapeHtml() to escape any user-generated data in pages.
StringEscapeUtils is an Apache class and its java doc can be found here
In the reference application (with the UI framework), use ui.escapeJs(), ui.escapeHtml(), and ui.escapeAttribute()
Contextual Encoding is introduced for platform 2.0 and above.
Instead of StringEscapeUtils.escapeJavaScript() use WebUtil.encodeForJavaScript()
Instead of StringEscapeUtils.escapeHtml() use WebUtil.escapeHTML() (This method signature is different from other signatures to preserve backward compatibility)
If the variable is used for an html attribute use WebUtil.encodeForHtmlAttribute()
You can find out the WebUtil class from here.
OWASP Encoder java doc can be found here.
Perfomance Optimization
Java API level
Use String Builder (or StringBuffer) rather than the + Operator when concatenating String use this
instead of
This saves memory space by avoiding creating new unnecesary objects hence reducing on extra pressure put on the GC. see above concatenating Strings
Avoid BigInteger and BigDecimal BigInteger and BigDecimal require much more memory than a simple long or double and slow down all calculations dramatically.
Use primitives where possible Rather than Wrapper classes Another quick and easy way to avoid any overhead and improve the performance of your application is to use primitive types instead of their wrapper classes. So, it’s better to use an int instead of an Integer, or a double instead of a Double. That allows your JVM to store the value in the stack instead of the heap to reduce memory consumption and overall handle it more efficiently.
Make use of Use Apache Commons String Uitils for String operations as much as possible This library contains more efficient string operations functionalities which may greatly optimize Application Performance
Cache expensive resources Caching is a popular solution to avoid the repeated execution of expensive or frequently used code snippets. The general idea is simple: Reusing such resources is cheaper than creating a new one over and over again. A typical example is caching database connections in a pool. The creation of a new connection takes time, which you can avoid if you reuse an existing connection. see Caching in openmrs
Openmrs API Level
Avoid Fetching all resources in a single method call eg avoid calls like
If a database has more than 10k patients, this will probably cause an Out of Memory Error
Resources
https://improvingsoftware.com/2011/06/27/5-best-practices-for-commenting-your-code/