Over a million developers have joined DZone.

Make or Break Your Code With Gradle

DZone 's Guide to

Make or Break Your Code With Gradle

Using tools which integrate with Gradle, you can improve your code quality and avoid your code becoming legacy even as you write it.

· DevOps Zone ·
Free Resource

Have you ever heard of the phrase Legacy Code?

Have you ever considered you may be producing Legacy code in real time? The feeling is horrible, right?

But is it true? Is your code “Legacy?”

I asked myself this question and decided to do some research on the subject. I tried to figure out how one applies the adjective “Legacy” to code? While searching, I found this definition: “There is a common, false perception that legacy code is old. Although some software developers look at legacy code as a poorly written program, legacy code actually describes a code base that is no longer engineered but continually patched. Over time, an unlimited number of modifications may be made to a code base based on customer demand, causing what was originally well-written code to evolve into a complex monster. A software developer will recognize legacy code when a feature cannot be added without breaking other logic or features. At this point, the developers may begin lobbying for a new system.” (Techopedia)

Sound familiar? So how do we fix this?

I’ve worked on Android a lot recently, so I will relate the discussion to this platform. I recently joined ContentSquare, and I was lucky enough have the ability to directly affect both mobile platforms. I will learn from my mistakes and never allow anyone to make them again!

I started looking into the tools of the trade, and also what I want to do, and eventually implement the same safety net on both platforms.

I started writing requirements for my safety net:

  • Apply code style checks.
  • Add code analyzers.
  • Add specific pattern detectors.
  • Require continuous code documentation.
  • Perform continuous discovery of new issues.
  • Stop the bleed.

Git and Friends

We're equipped with all the usual tools. We use GitHub, and we have a Jenkins server which runs our builds. Our usual process uses the feature branch approach. This means our branch process by default looks like this:

                                    Git feature branching model, courtesy of github.com.

We also decided to disable direct commits to master, meaning we only can push code to master through a branch merge with a pull request. Doing this on GitHub is super easy. Just go to your repo settings, and select "protect this branch."

                                                   Setting up branch protection on GitHub.

What we just did is disallow anyone to commit and push directly to master. From now on we have to go through a pull request which will be reviewed by at least one person. This helps on two fronts:

  1. With the pull request, we notify everyone of the change, allowing them to know the development of the code by review.
  2. Using the peer reviews, we get to reduce the amount of bugs as people notice when someone is taking a shortcut.

For the time being, our build loop is very simple:

./gradlew check // run unit tests 
./gradlew assemble // assemble all build flavors

OK, now off to finding the tools we’re going to use.

As a requirement, we decided to only use tools which integrate through Gradle. This would allow us to have completely seamless integration.


As is a common tool, I will not go into detail about it. Instead I will just show you how to enable it.

Lint is a part of the Android plugin, but by default, it’s not configured on new projects.
To enable it, add the following block to the android section of the build.gradle file.

lintOptions { 
   //lint rules of conduct
   warningsAsErrors true
   abortOnError true
   htmlReport true
   //locations for the rules and output
   lintConfig file("${rootDir}/config/lint/lint-config.xml")
   htmlOutput file("${buildDir}/reports/lint/lint.html")

In the above segment, the things to note are:

  1. warningsAsErrors = true — Consider all warnings as errors.
  2. abortOnError = true — Break the build on any Lint error.
  3. lintConfig — A file which provides input for Lint, with definitions per rule.

Now that we have Lint done, we need to actually run it.

check - Runs all checks.connectedAndroidTest - Installs and runs instrumentation tests for

You will run the check task on all submodules. And this task runs

  • All unit tests for debug/release flavor.
  • All UI tests for debug/release flavor.
  • Lint.

For the moment, this is all we need, and due to its nature, we will link all future checks to this task.

Code Analysis

Next, I was reading up on PMD and Findbugs and discovered Facebook’s Infer.

PMD is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. PMD works on source code and therefore finds problems like violation of naming conventions, lack of curly braces, misplaced null check, long parameter list, unnecessary constructor, missing break in switch, etc. PMD also tells you about the Cyclomatic complexity of your code, which I find very helpful.

To add PMD as an analyzer, we have to append to the build.gradle file. We can add the following definitions

apply plugin: 'pmd'
def configDir = "${project.rootDir}/config"
def reportsDir = "${project.buildDir}/reports"
check.dependsOn 'pmd'
task pmd(type: Pmd, dependsOn: "assembleDebug") {
   ignoreFailures = false
   ruleSetFiles = files("$configDir/pmd/pmd-ruleset.xml")
   ruleSets = []
   source 'src/main/java'
   include '**/*.java'
   exclude '**/gen/**'
   reports {
      xml.enabled = true
      html.enabled = true
      xml {
         destination "$reportsDir/pmd/pmd.xml"
      html {
         destination "$reportsDir/pmd/pmd.html"

In this script, the interesting things to note are:

  1. check.dependsOn ‘pmd’ — this line links the PMD task with check. Which means, when we call gradle check, it will call pmd as a dependency task. This way, the team can get used to just calling gradle check and know all relevant checks are done through this task.
  2. ruleSetFiles — defines the set of rules and specifics which are to be used in this installation.
  3. reports block — defines all the requirements in terms of what to scan, what to ignore, and where to report.

FindBugs is an analyzer which detects possible bugs in Java programs. Potential errors are classified in four ranks: (i) scariest, (ii) scary, (iii) troubling and (iv) of concern. This is a hint to the developer about their possible impact or severity. FindBugs operates on Java bytecode, rather than source code.

apply plugin: 'findbugs'
def configDir = "${project.rootDir}/config"
def reportsDir = "${project.buildDir}/reports"
check.dependsOn 'findbugs'
task findbugs(type: FindBugs, dependsOn: "assembleDebug") {
   ignoreFailures = false
   effort = "max"
   reportLevel = "high"
   excludeFilter = new File("$configDir/findbugs/findbugs-filter.xml")
   classes = files("${buildDir}/intermediates/classes")
   source 'src/main/java'
   include '**/*.java'
   exclude '**/gen/**'
   reports { 
      xml.enabled = true
      html.enabled = false
      xml {
         destination "$reportsDir/findbugs/findbugs.xml"
      html {
         destination "$reportsDir/findbugs/findbugs.html"
   classpath = files()

Things to note in this configuration are:

  1. check.dependsOn ‘findbugs’ — same as before, we link it to check.
  2. ignoreFailures = false — defines whether any discoveries are used as warnings or errors.
  3. reportLevel = “max” — It specifies the confidence/priority threshold for reporting issues. If set to “low”, confidence is not used to filter bugs. If set to “medium” (the default), low confidence issues are suppressed. If set to “high,” only high confidence bugs are reported.
  4. effort — Set the analysis effort level. Enable analyses which increase precision and find more bugs, but which may require more memory and take more time to complete.
  5. reports = the location where the reports will be saved.

Infer is a static analysis tool for Java, Objective-C, and C. What was nice about infer is the fact it double checks all `@Nullable` vs `@NonNull` annotated variables and has some Android specific checks which were of interest. Infer is a standalone tool, which means that by default it doesn’t integrate with Gradle, however, good guy Uber developed a Gradle plugin for Infer.
To add this analyzer to our build process, we again add to Gradle.

apply plugin: 'com.uber.infer.android'
check.dependsOn 'inferDebug' 
check.dependsOn 'eradicateDebug'
inferPlugin {
   infer {
      include = project.files("src/main")
      exclude = project.files("build/")
   eradicate {
   include = project.files("src/main")
   exclude = project.files("build/")

Adding this plugin is quite straightforward, we only define the sources which are to be included and excluded from the check.

Now that we have some analyzers, call ./gradlew check and see what happens. 
Within the huge log, you will see something similar to the following

Starting analysis...
  "F" analyzing a file
  "." analyzing a procedure
Found 12 source files in /Users/tancho/Development/repos/tests/make-or-break/mylibrary/build/infer-out
  No issues found
:mylibrary:findbugs UP-TO-DATE
Starting analysis...
  "F" analyzing a file
  "." analyzing a procedure

Found 12 source files in /Users/tancho/Development/repos/tests/make-or-break/mylibrary/build/infer-out
  No issues found
Ran lint on variant release: 0 issues found
Ran lint on variant debug: 0 issues found

However, defining the code style was a pain!

Google to the rescue! Google actually provides its code style publicly, and as it was already pretty close to the IntelliJ Idea defaults; I just modified the “code formatting template” in the studio and within 10–15 minutes, I was all set.

ProTip: if you want to constantly auto format your code, IntelliJ has you covered. You can easily record a macro, which will, rearrange code, re-order imports, remove unused imports, as well as do any other style related operations. When done put a “save all” at the end. Next, store the macro and assign it to ctrl+s. These settings, can be shared to the team, and it automagically works for everyone.

Generating Documentation

This is quite straightforward for Java; we need to generate a JavaDoc.

Step1: Require a JavaDoc comment on all public methods, through Checkstyle, already done in the default rules.

Step2: Implement the Gradle JavaDoc plugin.

task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    title = "Library SDK"
    classpath = files(project.android.getBootClasspath())
    destinationDir = file("${buildDir}/reports/javadoc/analytics-sdk/")
    options {
        links "http://docs.oracle.com/javase/7/docs/api/"
        linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
    exclude '**/BuildConfig.java'
    exclude '**/R.java'
afterEvaluate {
    // fixes issue where javadoc can't find android symbols ref: http://stackoverflow.com/a/34572606
    androidJavadocs.classpath += files(android.libraryVariants.collect { variant ->

Now, if you call ./gradlew javadoc in your output folder, `build/reports/javadoc` you will find the complete JavaDoc for your project

Code Coverage Reports

For this task, we’ll use Jacoco, a Java standard.

apply plugin: 'jacoco'
jacoco {
    toolVersion = ""
task coverage(type: JacocoReport, dependsOn: "testDebugUnitTest") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
        html.destination "${buildDir}/reports/codecoverage"
    def ignoredFilter = [
    def debugTree = fileTree(dir:"${project.buildDir}/intermediates/classes/debug", excludes: ignoredFilter)
    sourceDirectories = files(android.sourceSets.main.java.srcDirs)
    classDirectories = files([debugTree])
    additionalSourceDirs = files([
    executionData = fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])

So, similarly by calling ./gradlew coverage in `build/reports/coverage` you would get a very nice coverage report page.

An important thing, in order to reduce code smell, was to break if developers forget code, used for debug purposes or comment out code in hopes of future use.


//this code will be used sometime
// someImportantMethod()

There is a quick fix for this, just add these rules to your checkstyle rules set.

<module name="Regexp">
    <property name="format" value="System\.err\.print" />
    <property name="illegalPattern" value="true" />
    <property name="message" value="Bad Move, You should not use System.err.println" />
<module name="Regexp">
    <property name="format" value="\.printStacktrace" />
    <property name="illegalPattern" value="true" />
    <property name="message" value="Bad Move, You should not use System.err.println" />
<!--Check for commented out code-->
<module name="Regexp">
    <property name="format" value="^\s*//.*;\s*$" />
    <property name="illegalPattern" value="true" />
    <property name="message" value="Bad Move, Commented out code detected, it smells." />

At the end, our build loop has two extra lines:

./gradlew check // run unit tests
./gradlew javadoc // generate javadoc
./gradlew coverage // generate coverage reports
./gradlew assemble // assemble all build flavors

So now that we have our checks in place, at last, we need to set Github to disallow branch merges unless our Jenkins build passes. This is quite easy with the Github plugin. You can just add a post build step, and run it once to have it available on Github.

Add a Jenkins build step and add the corresponding status as a requirement on Github.
                                               Setting up a status requirement in GitHub.

Now, once a build finishes, if you have a PR that does not conform to the rules put in place, Jenkins fails the build and by this Github blocks the merge!

                                        Github blocks merges if Jenkins fails to build a plan.

You now have a mechanism which runs

  • Code style checks ✓
  • Static code analysis (Android specific and Java related) ✓
  • Bad practice pattern detectors ✓
  • Continuous documentation through JavaDoc ✓
  • Continuous discovery through the Jenkins loop ✓
  • Stopping the bleed through the master branch protection ✓

Nice, now all you’re left to do is focus on the architecture of the code and continue improving your system.

A sample code project implementing most of the above can be found on my GitHub repo.

Oh, and if you’re in Paris and interested, look us up.. 


android ,java ,code quality ,gradle ,code analysis ,devops

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}