Quality policies and enforcement

Revision as of 17:09, 21 May 2019 by WikiFreak (talk | contribs) (Created page with "Category:Development Category:Resources This page does not talk about design but '''implementation and code delivery'''. This page describes '''how code quality is...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


This page does not talk about design but implementation and code delivery. This page describes how code quality is enforced.


Targets

The objectives are:

  • Ensure the maintainability of the code
  • Be sure that LuxTrust follow best practices and common design patterns. It will be easier to get inside the code by someone else later on
  • Be sure that separation of concerns is well done. This will avoid cyclic dependencies, mix of data / business code at the same place, etc.
  • Run OWASP dependencies check and be inform in case of security issues
  • Enforce coding rules and formats (ex: tab /vs/ spaces, brackets, etc.)
  • Control test coverage
  • Enforce governance and internal policies


Means of success

To reach the targets, different means have been setup:

  1. Coding awareness == IDEs plugins
  2. Support of each other == generate project javadoc and publish source code of internal projects. If you do not trust the company members, how can you trust a 3rd party dependency ??
  3. Compilation safeties == maven plugins
  4. Build on push ; ALL TESTS MUST ALWAYS PASS == Jenkins
  5. Build enforcement / notifications == jenkins will run additional rules (ex: OWASP checks) and inform developers in case of issues
  6. SonarQube == static code analysis and quality control
  7. Code review / peer programming = to ensure code is really as good as it should be



KEY POINTS

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

— John F. Woods


Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

— Martin Fowler


The best programs are written so that computing machines can perform them quickly and so that human beings can understand them clearly

— Donald E. Knuth


It’s OK to figure out murder mysteries, but you shouldn’t need to figure out code. You should be able to read it.

— Steve McConnell


Without requirements or design, programming is the art of adding bugs to an empty text file.

— Louis Srygley


DO NOT LET "CLICHES" BE RIGHT

If McDonalds were run like a software company, one out of every hundred Big Macs would give you food poisoning, and the response would be, "We’re sorry, here’s a coupon for two more."

— Mark Minasi


1. Coding awareness

> The best to code efficiently is to be inform right away inside our IDEs. Many plugins are available for Eclipse and IntelliJ.


IntelliJ

You should manually install the following plugins:

  • Code quality (inspection)
    • CodeMetrics : to know the cyclomatic complexity of each method. A must have
    • Infinitest: to run unit tests automatically on method change. A must have
    • SonarLint + SonarQube Community plugin : to see the alerts inside IntelliJ
    • Code smell detector : to have alerts about bad / suspicious practices
    • Checkstyle-IDEA : to have the Checkstyle rules
    • Findbugs-IDEA : to have the FindBugs rules
    • PMDPlugin : to have the PMD rules
  • Utilities
    • Maven helper : to view the maven dependencies graph


Some plugins must be enabled.

  • Go to File > Settings > Other settings
    • SonarLint General Settings
      • Register sonarQube server. (i) to generate a Token go to "SonarQube" > login > My account > security
    • SonarLint Project settings
      • Select the new server
      • Select the SonarQube project
    • SonarQube
      • Add Sonar server


Eclipse

Install the following plugins:

  • Checkstyle
  • PMD
  • Findbugs
  • EmmaCodeCoverage (include in Eclipse since 2018)
  • SonarQube
  • Infinitest


2. Support of each other

To help each other, please enable the javadoc generation and source code publication if your artefact is not confidential / something related to security. You do not want your colleague to harass you... You want them to challenge your code and design. :)


In your maven POMs, please enable something like that (it can be done in the parent POM only):

    <properties>
        <maven.source.version>3.0.1</maven.source.version>
        <javadoc.version>3.1.0</javadoc.version>
    </properties>
  
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
               <version>${maven.source.version}</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
 
            <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-javadoc-plugin</artifactId>
               <version>${javadoc.version}</version>
               <configuration>
                    <quiet>true</quiet>
                    <failOnWarnings>false</failOnWarnings>
                    <!-- add this to disable checking and ensure it will not failed on Java 8 -->
                    <doclint>none</doclint>
                </configuration>
             </plugin>
        </plugins>
    </build>


3. Compilation safeties

> Do not forget to compile your code BEFORE commit and push

Running Maven from the command line - without the black magic of the IDE - will give you a lot of warranty about your code.


Maven code quality principles

Maven relies on plugins to check the code quality.

  • Some plugins always run: they are in the build / Plugins section
  • Some plugins only run in particular cases: specific profiles or they are related to reports execution. These are in build / pluginsManagement section
  • Some plugins are only executed on demand : they are in the reporting section


BUILD configuration

These are the main plugins. You should add them to your parent pom.


    <properties>
        <!-- Set the reporting settings -->
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- Plugin versions -->
        <surefire.version>2.22.2</surefire.version>
        <dependency-plugin.version>3.1.1</dependency-plugin.version>
        <jacoco.version>0.8.3</jacoco.version>
    </properties>   
     
    <build>
        <!-- Global configuration. It will be used on request -->
        <pluginManagement>
            <plugins>
                <!-- ==== packaging checks ==== -->
                <!-- to check if some dependencies are unused or missing -->
                <!-- Note that the dependency:analyze-only goal is used in preference to dependency:analyze since it doesn't force a further compilation of the project, but uses the compiled classes produced from the earlier compilation -->
                <!-- usage: "mvn org.apache.maven.plugins:maven-dependency-plugin:analyze-only" -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>${dependency-plugin.version}</version>
                    <executions>
                        <execution>
                            <id>analyze</id>
                            <goals>
                                <goal>analyze-only</goal>
                            </goals>
                            <configuration>
                                <failOnWarning>true</failOnWarning>
                                <outputXML>true</outputXML>
                                <ignoreNonCompile>true</ignoreNonCompile>
                                <ignoredDependencies>
                                    <dependency>org.hamcrest:hamcrest-core</dependency>
                                    <dependency>org.springframework</dependency>
                                    <dependency>org.springframework.boot</dependency>
                                </ignoredDependencies>
                                <ignoredUsedUndeclaredDependencies>
                                    <dependency>org.hibernate.javax.persistence</dependency>
                                    <dependency>org.springframework.batch</dependency>
                                    <dependency>org.springframework.ws</dependency>
                                    <dependency>org.springframework.boot</dependency>
                                    <dependency>org.hibernate</dependency>
                                    <dependency>xml-apis</dependency>
                                </ignoredUsedUndeclaredDependencies>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
 
                <!-- ==== Security ==== -->
                <!-- To check for vulnerabilities against the OWASP databases -->
                <!-- usage: "mvn org.owasp:dependency-check-maven:check" -->
                <!--   to aggregate all reports at once: "mvn org.owasp:dependency-check-maven:aggregate" -->
                <plugin>
                    <groupId>org.owasp</groupId>
                    <artifactId>dependency-check-maven</artifactId>
                    <version>5.0.0-M3</version>
                    <configuration>
                        <failOnError>false</failOnError>
                        <formats>HTML,XML,JUNIT,JSON</formats>
                    </configuration>
                </plugin>
 
                <!-- ==== Reports ==== -->
                <!-- To aggregate all reports into 1 website. This is mandatory to activate the "REPORTING" sections of the POM -->
                <!-- This will offer a global view and navigation for (none exhaustive list): -->
                <!-- dependency checks, OWASP checks, Jacoco reports (surefire unit tests + failsafe integration tests), etc. -->
                <!-- This is only used when requested -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                    <configuration>
                        <locales>en</locales>
                        <!-- locales>en,fr</locales -->
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
 
  
        <!-- Plugins that will ALWAYS run -->
        <plugins>
            <plugin>
                <!-- Always check that all dependencies are correctly set: no unused / no missing -->
                <!-- Configuration of that plugin is externalized in the "pluginsManagement" section -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
            </plugin>
 
            <!-- To run UNIT tests and generate execution reports. -->
            <!-- reports are within each project/target/surefire-reports. Do not hesitate to consult them -->
            <!-- These reports are required for SonarQube -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire.version}</version>
                <configuration>
                    <excludes>
                        <exclude>**/*TestAPI</exclude>
                        <exclude>**/*IntegrationTest</exclude>
                    </excludes>
                </configuration>
            </plugin>
 
            <!-- To run INTEGRATION tests and generate execution reports. -->
            <!-- (i) Failsafe and Surefire must always share the same version to ensure reports are compatible -->
            <!-- reports are within each project/target/failsafe-reports. Do not hesitate to consult them -->
            <!-- These reports are required for SonarQube -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${surefire.version}</version>
                <configuration>
                    <includes>
                        <include>**/*TestAPI</include>
                        <include>**/*IntegrationTest</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>default-integration-test</id>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
 
            <!-- JACOCO test coverage plugin.
                 Use it to compile SUREFIRE (unit tests) and FAILSAFE (integration tests) reports for SonarQube
                 (i) attach that plugin to Maven TEST phase
                 Reports are generated in "${project.build.directory}/site/jacoco/*" by default
                 Good documentations:
                    https://wiki.onap.org/display/DW/Implementing+Code+Coverage
                    https://www.devcon5.ch/en/blog/2015/05/29/multi-module-integration-test-coverage-sonar-jacoco/
                    https://www.eclemma.org/jacoco/trunk/doc/maven.html
                 -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <configuration>
                    <append>true</append>
                    <!-- Use offline bytecode (with powermock changes) -->
                    <excludes>
                        <exclude>*</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <!-- Support for PowerMock tests -->
                    <!-- See https://www.igorkromin.net/index.php/2018/03/06/quick-look-at-jacoco-vs-cobertura-performance-and-coverage-results/ -->
                    <execution>
                        <id>jacoco-instrument</id>
                        <goals>
                            <goal>instrument</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>jacoco-restore-instrumented-classes</id>
                        <goals>
                            <goal>restore-instrumented-classes</goal>
                        </goals>
                    </execution>
					
                    <!-- ## UNIT TESTS ## -->
                    <!-- Configure JaCoCo runtime agent. It is passed as VM argument when Maven SUREFIRE plugin is executed. -->
                    <execution>
                        <id>pre-unit-tests</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <!-- Create reports -->
                    <execution>
                        <id>report-unit-tests</id>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                    <!-- ## INTEGRATION TESTS ## -->
                    <!-- Configure JaCoCo runtime agent. It is passed as VM argument when Maven FAILSAFE plugin is executed. -->
                    <execution>
                        <id>pre-integration-tests</id>
                        <goals>
                            <goal>prepare-agent-integration</goal>
                        </goals>
                    </execution>
                    <!-- Create reports -->
                    <execution>
                        <id>report-integration-tests</id>
                        <goals>
                            <goal>report-integration</goal>
                        </goals>
                    </execution>
                    <!-- ## MERGE ALL TESTS reports ## -->
                    <execution>
                        <id>merge</id>
                        <goals>
                            <goal>merge</goal>
                        </goals>
                        <configuration>
                            <!-- Only 1 destination file to aggregate ALL integration tests reports -->
                            <!-- the "session.executionRootDirectory" = parent folder that is being build by Jenkins -->
                            <destFile>${session.executionRootDirectory}/target/jacoco-it.exec</destFile>
                            <fileSets>
                                <fileSet implementation="org.apache.maven.shared.model.fileset.FileSet">
                                    <directory>${project.build.directory}</directory>
                                    <includes>
                                        <include>**/*.exec</include>
                                    </includes>
                                </fileSet>
                            </fileSets>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
 
            <!-- SonarQube engine -->
            <!-- required for JENKINS to inject settings in the build phase -->
            <plugin>
                <groupId>org.sonarsource.scanner.maven</groupId>
                <artifactId>sonar-maven-plugin</artifactId>
                <version>3.6.0.1398</version>
            </plugin>
        </plugins>


REPORTING configuration

Configuration below is optional. It is only useful if you plan to run mvn site

(red star) To generate the mvn site might be very long..


	<reporting>
		<plugins>
			<!-- ==== Maven site === -->
			<!-- Requirement for mvn site to display reports and aggregate content, since v3 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-project-info-reports-plugin</artifactId>
				<version>3.0.0</version>
				<reportSets>
					<!-- a) generate UNIT and INTEGRATION tests reports by modules -->
					<reportSet>
						<id>global-info</id>
						<inherited>true</inherited>
						<reports>
							<!-- full list available at: https://maven.apache.org/plugins/maven-project-info-reports-plugin/plugin-info.html -->
							<report>summary</report>
							<report>modules</report>
							<!-- List of dependencies and plugins -->
							<report>dependencies</report>
							<report>dependency-management</report>
							<report>plugins</report>
							<report>plugin-management</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
	 
			<!-- ==== unit tests reports ==== -->
			<!-- To aggregate Unit tests reports from Surefire -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-report-plugin</artifactId>
				<version>${surefire.version}</version>
				<configuration>
					<linkXRef>false</linkXRef>
				</configuration>
				<reportSets>
					<!-- a) generate UNIT and INTEGRATION tests reports by modules -->
					<reportSet>
						<id>surefire-failsafe</id>
						<inherited>true</inherited>
						<configuration>
							<aggregate>false</aggregate>
						</configuration>
						<reports>
							<!-- unit tests -->
							<report>report-only</report>
							<!-- integration tests -->
							<report>failsafe-report-only</report>
						</reports>
					</reportSet>
					<!-- b) AGGREGATE all reports under PARENT project -->
					<!-- ==  do NOT generate reports on each sub-module: set "inherited" to "false" -->
					<reportSet>
						<id>surefire-failsafe-aggregate</id>
						<inherited>false</inherited>
						<configuration>
							<aggregate>true</aggregate>
						</configuration>
						<reports>
							<!-- unit tests -->
							<report>report-only</report>
							<!-- integration tests -->
							<report>failsafe-report-only</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
	 
			<!-- ==== JACOCO test coverage ==== -->
			<plugin>
				<groupId>org.jacoco</groupId>
				<artifactId>jacoco-maven-plugin</artifactId>
				<version>${jacoco.version}</version>
				<configuration>
					<append>true</append>
				</configuration>
				<reportSets>
					<!-- ## UNIT TESTS ## reports by module -->
					<reportSet>
						<id>unit-tests-reports</id>
						<inherited>true</inherited>
						<reports>
							<report>report</report>
						</reports>
					</reportSet>
					<!-- ## INTEGRATION TESTS ## reports by module -->
					<reportSet>
						<id>integration-tests-reports</id>
						<inherited>true</inherited>
						<reports>
							<report>report-integration</report>
						</reports>
					</reportSet>
					<!-- ## AGGREGATE TESTS ## report for all -->
					<reportSet>
						<id>all-tests-reports</id>
						<inherited>false</inherited>
						<reports>
							<report>report-aggregate</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
	 
			<!-- ===== Javadoc ===== -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-javadoc-plugin</artifactId>
				<version>${javadoc.version}</version>
				<configuration>
					<quiet>true</quiet>
					<failOnError>false</failOnError>
				</configuration>
				<reportSets>
					<!-- Generate modules reporting -->
					<reportSet>
						<id>javadoc</id>
						<inherited>true</inherited>
						<reports>
							<report>javadoc-no-fork</report>
						</reports>
					</reportSet>
					<!-- Aggregate javadoc -->
					<reportSet>
						<id>javadoc-aggregate</id>
						<inherited>false</inherited>
						<reports>
							<report>aggregate-no-fork</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
	 
			<!-- ===== Checkstyle static analysis report ===== -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-checkstyle-plugin</artifactId>
				<version>3.0.0</version>
				<configuration>
					<!-- There are 2 predefined rulesets included in Maven Checkstyle Plugin:
						 * sun_checks.xml: Sun Checks
						 * google_checks.xml: Google Checks
					-->
					<configLocation>google_checks.xml</configLocation>
					<linkXRef>false</linkXRef>
				</configuration>
				<reportSets>
					<!-- Generate modules reporting -->
					<reportSet>
						<id>checkstyle</id>
						<inherited>true</inherited>
						<reports>
							<report>checkstyle</report>
						</reports>
					</reportSet>
					<!-- Aggregate all reports at parent level -->
					<reportSet>
						<id>checkstyle-aggregate</id>
						<inherited>false</inherited>
						<reports>
							<report>checkstyle-aggregate</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
	 
			<!-- ===== PMD static analysis report ===== -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-pmd-plugin</artifactId>
				<version>3.12.0</version>
				<configuration>
					<linkXRef>false</linkXRef>
				</configuration>
				<reportSets>
					<!-- Generate reports -->
					<reportSet>
						<reports>
							<report>pmd</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
		</plugins>
	</reporting>


Local build=

Before commit, please run mvn clean install locally. It should always go smoothly.

(warning) Do NOT skip tests locally. Tests must run on the local workstation


4. Jenkins build

A jenkins build should always be successful. If not, you must IMMEDIATELY attend to it: fix compilation errors, tests issues.


5. Build enforcement

This is related to Jenkins build.

(red star) Make sure to run install goal, not "package", because integration tests are only run at "install" phase.

(warning) Before you setup SonarQube, ensure that you are running Surefire & Failsafe always. If so, you should find "surefire-reports" / "failsafe-reports" folders + "jacoco.exec" files after compilation.



You must update your JenkinsFile to run the dependency-checks and sonarqube reporting tool. Here is an example:


        stage('Build') {
            when {
                // condition
            }
            steps {
                withMaven(
                        maven: 'Maven 3.5.2',
                        mavenLocalRepo: '.repository'
                ) {
                    // Build
                    echo "Build"
                    sh "mvn clean install"

                    // MVN dependencies check
                    echo "MVN dependencies checks [unused/missing/outdated/etc]"
                    sh "mvn org.apache.maven.plugins:maven-dependency-plugin:analyze-report"

                    // Publish coverage reports
                    jacoco(
                          changeBuildStatus: false,
                          execPattern: '**/*.exec',
                          sourcePattern: 'src/main/java',
                          classPattern: 'target/classes',
                          exclusionPattern: '**/*Exception*,**/*Configuration*,**/ApiApplication*'
                          )


					// OWASP dependencies checks
					echo "OWASP dependencies checks"
                    sh "mvn org.owasp:dependency-check-maven:check"
                    // sh "mvn org.owasp:dependency-check-maven:aggregate"
                    dependencyCheckPublisher failedTotalHigh: '0', unstableTotalHigh: '1', failedTotalNormal: '2', unstableTotalNormal: '5'

                    // SonarQube
                    echo "Checking code quality"
                    withSonarQubeEnv('sonarqube') {
						// Mind the version over there - it must match Jenkins
                        sh "mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.6.0.1398:sonar -Dsonar.projectName=\"PROJECT ($BRANCH_NAME)\" -Dsonar.projectKey=PROJECT:$BRANCH_NAME"
                    }
                }
            }
        }


6. SonarQube

By default there will be 1 sonarqube' entry for every Jenkins artifactId that is build (parent name).