文档结构  
翻译进度:37%     翻译赏金:0 元 (?)    ¥ 我要打赏

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)

第 1 段(可获 2.11 积分)

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.
第 2 段(可获 1.44 积分)

Git和支持者

我们配备了所有常用工具。 我们使用GitHub,我们有一个运行我们的构建的Jenkins服务器。 我们通常的分支过程使用特征分支的方法。 这意味着我们的分支过程默认情况下如下所示:

                                   
Git功能分支模式,由github.com提供。

我们还决定禁用直接提交到master分支,这意味着我们只能通过拉请求的分支合并将代码推送到master分支。 在GitHub上这样做是非常简单的。 只需去你的仓库设置,然后选择“保护这个分支”。

第 3 段(可获 1.16 积分)

                                                   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:

第 4 段(可获 1.21 积分)
./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.

Lint

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.

第 5 段(可获 1.15 积分)
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.

第 6 段(可获 0.61 积分)
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.

第 7 段(可获 1.7 积分)

添加PMD作为分析器,我们必须添加到build.gradle文件。 我们可以添加以下定义

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"
      }
   }
}
第 8 段(可获 0.26 积分)

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.

第 9 段(可获 1.78 积分)

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.
第 10 段(可获 1.36 积分)

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/")
   }
}
第 11 段(可获 0.96 积分)

添加这个插件是非常简单的,我们只需要定义从检查中包含和排除的源。

现在我们有一些分析仪,调用./gradlew check 看看会发生什么。
在大量的日志中,您将看到类似于以下的内容

:mylibrary:inferCheckForCommand
:mylibrary:inferPrepareDebug
:mylibrary:eradicateDebug
Starting analysis...
 
legend:
  "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
:mylibrary:inferDebug
Starting analysis...
 
legend:
  "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:deleteInferConfig
:mylibrary:lint
Ran lint on variant release: 0 issues found
Ran lint on variant debug: 0 issues found
:mylibrary:pmd
第 12 段(可获 0.58 积分)

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.

第 13 段(可获 1.51 积分)

生成文档

这对Java来说非常简单; 我们需要生成一个JavaDoc。

Step1:需要通过Checkstyle对已经在默认规则中完成的所有公共方法的JavaDoc注释。

Step2:实现Gradle JavaDoc插件。

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 ->
        variant.javaCompile.classpath.files
    })
}
第 14 段(可获 0.46 积分)

现在,如果您在输出文件夹中调用./gradlew javadoc,那么可以在`build/reports/javadoc`中找到您的项目的完整的JavaDoc

代码覆盖率报告

对于这个任务,我们将使用Jacoco,一款标准的Java代码覆盖率工具。

apply plugin: 'jacoco'
jacoco {
    toolVersion = "0.7.5.201505241946"
}
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 = [
            '**/R.class',
            '**/R$*.class',
            '**/BuildConfig.*',
            '**/Manifest*.*',
            'android/**/*.*',
            'com.android/**/*.*',
            'com.google/**/*.*'
    ]
    def debugTree = fileTree(dir:"${project.buildDir}/intermediates/classes/debug", excludes: ignoredFilter)
    sourceDirectories = files(android.sourceSets.main.java.srcDirs)
    classDirectories = files([debugTree])
    additionalSourceDirs = files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData = fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
}
第 15 段(可获 0.44 积分)

所以,同样地,通过调用./gradlew coverage,您将在`build/reports/coverage`中得到一个非常好的覆盖报告页面。

为了减少代码异味,如果开发人员忘记编写代码,用于调试目的或注释掉代码以备将来使用,那么重要的就是中断代码。

e.printStacktrace();
 
System.out.println();

//this code will be used sometime
//if(contition){
// someImportantMethod()
//}

有一个快速的解决办法,只需将这些规则添加到您的checkstyle规则集。

<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>
<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" />
</module>
<!--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." />
</module>
第 16 段(可获 0.8 积分)

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.

第 17 段(可获 0.95 积分)

                                               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 ✓
第 18 段(可获 1.1 积分)

很好,现在剩下要做的就是关注代码的架构,并继续改进你的系统。

一个实现了上述大部分内容的简单的示例代码项目,可以在我的GitHub仓库中找到。

哦,如果你在巴黎并且对此有兴趣,可以来找我们。.

http://www.welcometothejungle.co/companies/contentsquare

第 19 段(可获 0.69 积分)

文章评论