(This article originally appeared in the June 2013 issue of GroovyMag)
This article provides examples of using CodeNarc’s “generic” rules to enforce your own custom best practices.
CodeNarc is a static analysis tool for Groovy source code, enabling monitoring and enforcement of many coding standards and best practices. It contains a set of rules, each of which can be individually enabled and configured.
In addition to the more obvious and straightforward rules for naming, size/complexity, unused code, etc., CodeNarc also contains a set of generic rules that require more specific configuration and customization in order to be useful (and enabled). In this article, we will look at six of these generic rules and how they can be used to enforce your organization’s coding and design standards and best practices.
The StatelessClass rule checks for non-final fields on a class. The intent of this rule is to check a configured set of classes that should remain “stateless” and reentrant. This rule ignores final fields (either instance or static), and all fields annotated with the @Inject annotation, as well as all classes annotated with the @Immutable transformation.
One example where this can be useful is DAO classes, if you define them as singletons (e.g., Spring beans). In that case, they need to be reentrant, and the only fields on the classes should be for configuration or injecting dependencies.
You can configure this rule to ignore certain fields either by name (ignoreFieldNames) or by type (ignoreFieldTypes). This can be used to ignore fields that hold references to (static) dependencies (such as DataSources) or static configuration.
See Listing 1 for an example StatelessClass rule configuration. Note that we override the rule name property (from its default “StatelessClass”), to give it a name more reflective of its customized purpose. That is usually a good idea for any of the CodeNarc generic rules. The rule is configured to ignore any fields named “dataSource” or fields with names ending in “Dao”. We also give it a custom description, which will be shown in the rule descriptions section of the HTML or XML reports.
name = 'StatelessDao'
priority = 2
ignoreFieldNames = 'dataSource, *Dao'
applyToClassNames = '*Dao'
description = 'DAO classes should be stateless and reentrant. (Custom)'
Listing 1: StatelessClass rule configuration for DAOs
Note that this rule will not catch violations of true statelessness/reentrancy if you define a final field whose value is itself mutable, for example a final HashMap.
The IllegalPackageReference rule checks for references to any of the packages configured in its packageNames property. This rule can be useful for governance and enforcement of architectural layering. Listing 2 contains an example IllegalPackageReference rule configuration that checks for references to any JDBC-specific packages (groovy.sql, java.sql or javax.sql) from non-DAO classes.
name = 'UseJdbcOnlyInDaoClasses'
priority = 2
packageNames = 'groovy.sql, java.sql, javax.sql'
doNotApplyToClassNames = '*Dao*, com.example.dao.*'
description = 'Reference to JDBC packages should be restricted to DAO classes. (Custom)'
Listing 2: IllegalPackageReference rule configuration
The IllegalClassReference rule checks for reference to any of the classes configured in its classNames property. Like the IllegalPackageReference rule, it can be used for governance and enforcement of architectural layering. Listing 3 contains an example IllegalClassReference rule configuration that checks for illegal DAO references from within model classes.
name = 'DoNotReferenceDaoFromModelClasses'
priority = 2
classNames = '*Dao'
applyToClassNames = 'com.example.model.*'
description = 'Do not reference DAOs from model classes.'
Listing 3: IllegalClassReference rule configuration: Do not reference DAOs from model classes
The IllegalClassReference rule can also be used to flag references to obsolete or deprecated classes. Listing 4 contains an example rule configuration that checks for references to an old XmlUtil class that was used in our Java code to parse XML, but that is now obsolete because of the XML parsing provided by Groovy’s wonderful XmlSlurper (or XmlParser).
name = 'DoNotUseXmlUtil'
priority = 2
classNames = 'com.example.xml.XmlUtil'
applyToClassNames = '*'
description = 'Do not use the XmlUtil class to parse XML. Use the Groovy XmlSlurper class instead. (Custom)'
Listing 4: IllegalClassReference rule configuration: Do not use obsolete class
The RequiredRegex rule checks for a specified regular expression that must exist within the source code. Listing 5 contains an example rule configuration that verifies that each source file contains a javadoc comment containing @version … $Revision … @author … $Author … $Date. The (?s) prefix within the regular expression string enables dotall mode, which means that the expression . matches any character, including a line terminator, allowing the pattern to span multiple lines.
name = 'ClassJavadocTags'
priority = 2
regex = '(?s)\\/\\*\\*.*\\@version.*\\$Revision.*\\$.*\\@author.*\\$Author.*\\$.*\\$Date.*\\$'
description = 'Class javadoc must include $Revision, $Author and $Date tags. (Custom)'
Listing 5: RequiredRegex rule configuration
The IllegalRegex rule checks for a specified illegal regular expression within a source file. Listing 6 contains an example rule configuration that checks for empty @see tags within javadoc. We had an old class javadoc template in place for a few years that included an empty @see tag, so our codebase was littered with them. Perhaps you have similar junk scattered around your codebase? The (?m) prefix within the regular expression string enables multiline mode, which means that the $ expression matches the end of the line.
name = 'EmptySeeTag'
priority = 2
regex = /(?m)\@see\s*$/
description = 'Class javadoc must not include empty @see tags. (Custom)'
Listing 6: IllegalRegex rule configuration
The RequiredString rule checks for a specified text string that must exist within the source files. Listing 7 contains an example rule configuration that checks that each source file contains the Apache 2.0 License header. Note that, as configured, it only checks for a single line of the license. That should be good enough to ensure that you don’t forget to include the license within your source files. You can change the configuration to check for the exact, entire license, if you wish, but you must configure the whitespace exactly, and the copyright year must be correct and consistent across all source files.
name = 'ApacheLicenseRequired'
string = 'Licensed under the Apache License, Version 2.0 (the "License");'
violationMessage = 'The Apache License 2.0 comment is missing from the source file'
description = 'Checks that all source files contain the Apache License 2.0 comment'
Listing 7: RequiredString rule configuration