Wiki Spaces
Documentation
Projects
Resources
Get Help from Others
Q&A: Ask OpenMRS
Discussion: OpenMRS Talk
Real-Time: IRC Chat | Slack
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.
The following tools are used to ease code review, help new developers get used to our coding style and maintain code quality.
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
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 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
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 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
At the moment you cannot check the code against the FindBugs rules locally using maven as with checkstyle. We are working on it
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 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.
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:
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.
The following conventions are applied to code in openmrs-core but we encourage module authors to adopt them as well.
(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)
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:
if (!someBooleanFlag){ ...perform something with a one liner; }
equals()
and hashcode()
methods unless this is necessary for the semantics of the class. If you do override equals()
or hashcode()
, 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:
public boolean doSomething(boolean shouldDoSomething){ if (!shouldDoSomething){ return false; } else { //.... do something return true; } }
Write this:
public boolean doSomething(boolean shouldDoSomething){ if (!shouldDoSomething){ return false; } //.... do something return true; }
List<Concept>
over LinkedList<Concept>
. This way the implementation can be changed without affecting code that relies on the return type.@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 the readOnly
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:
myList.stream().map(item -> { return item.getCost() * 1.05; });
Write this:
myList.stream().map(item -> item.getCost() * 1.05);
Where possible, use method references instead of lambdas. That is, instead of this:
myList.stream().map(item -> item.toString());
Write this:
myList.stream().map(Object::toString);
Make sure you have followed the How-To Setup And Use Your IDE which will prevent you from running into the following issues.
Imports that are not used should be removed, remember that you introduce dependencies that are not needed!
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:
import java.util.ArrayList; import java.util.List; import java.util.Random;
Do not do the following:
import java.util.*;
Please check your IDE settings it is often the IDE which merges several imports into a wildcard import.
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
String a = b + c + d;
instead of
String a = new StringBuilder().append(b).append(c).append(d).toString();
For concatenation when logging, see How To Log.
if (s == null || s.equals("")) ...
". Instead do "if (StringUtils.isNotEmpty(s) ...
"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
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
If you want to log you should rely on the slf4j. The following shows a typical usage borrowed from their API documentation
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Wombat { private static final Logger log = LoggerFactory.getLogger(Wombat.class); Integer t; Integer oldT; public void setTemperature(Integer temperature) { oldT = t; t = temperature; log.debug("Temperature set to {}. Old temperature was {}.", t, oldT); if(temperature.intValue() > 50) { log.info("Temperature has risen above 50 degrees."); } } }
SLF4J 1.6.x is not supported more than two String arguments. In this case you can use
log.trace("Time taken to store {} objects of size {} is {} msecs", new Object[] { count, size, time });
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
if(log.isDebugEnabled()) { log.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }
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:
log.debug("The entry is {}.", entry);
Why? Read https://www.slf4j.org/faq.html#logging_performance for the answer.
Add the OpenMRS code templates to your IDE (Eclipse or IntelliJ) which will save you typing and help you stick to the above conventions.
System.out.
println() in your code; instead logex.
printStackTrace() in your code; instead logModifiers should follow the order suggested by the Java Language specification, sections 8.1.1, 8.3.1, 8.4.3 and 9.4:
We enforce this via Checkstyle rule ModifierOrder
Boolean
class; rather, use Boolean.TRUE
and/or Boolean.FALSE
.default
case if only to assert false
, since future coding changes may introduce unexpected cases.break
or return
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.true
or false
; rather, simplify the boolean expression by removing the comparison and, if necessary, adding a leading exclamation mark (!) if you are testing for false
expressions.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
, Throwable
, or RunTimeException
; rather, you should catch more specific exceptions. Catching these high level exceptions can mask errors.Throwable
, Error
, or RuntimeException
; rather, you should throw more specific 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:
org.openmrs.api.DuplicatePatientIdentifierException extends PatientIdentiferException
when we realize the webapp needs to distinguish duplicates from invalid identifier exceptions.
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
Example: instead of this:
<li>${identifier.identifier} ${identifier.identifierType.name}</li>
write this:
<li><c:out value="${identifier.identifier}" /> <c:out value="${identifier.identifierType.name}" /></li>
When using our custom "openmrs:message" or tag, make sure to use htmlEscape unless you really need html/js tags in the message:
<h2><openmrs:message code="Concept.view.title" htmlEscape="true" arguments="${command.concept.name}" /></h2>
Use String Builder (or StringBuffer) rather than the + Operator when concatenating String use this
StringBuilder x = new StringBuilder("a"); x.append("b");
instead of
String s = "a"; s = s.concat("b.");
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
Avoid Fetching all resources in a single method call eg avoid calls like
getAllPatients();
If a database has more than 10k patients, this will probably cause an Out of Memory Error
https://improvingsoftware.com/2011/06/27/5-best-practices-for-commenting-your-code/
5 Comments
Burke Mamlin
What are the conventions for naming of URLs and controllers?
Robert Day
Is the UI framework still in use? I can't seem to find any references to it when searching the code base.
Darius Jazayeri
Work on the Epic - Reference Application uses the UI Framework extensively. (But the existing web application that's under openmrs-core does not use it.)
Akash Agrawall
This post might explain why do we use <c:out value=${person.name}> instead of using directly ${person.name}
http://stackoverflow.com/questions/291031/jsp-cout-tag
Regards
Wyclif Luyima
I recall agreeing on using protected final and not static final for as modifiers for the loggers