β

拯救Java Code Style强迫症

程序师 10 阅读

这篇文章缘起于上一个持续交付的咨询项目,当时正在指导客户团队的 Java 工程师做 Code Review,发现一个很有意思的现象:有一位工程师对 Code Style 特别在意,所以在 Code Review 的大部分时间中都是该工程师在指出哪里哪里的格式不对,但是团队并没有找到改进方法,每次的结论都是“下次我注意一点。”我挺欣赏这位工程师对 Code Style 的认真态度,所以就萌生了“怎么拯救 Code Style 强迫症”的想法。

要点

Code Style 是一项工程实践

我是右侧风格的忠实拥趸,如果让我在工作的项目中看到左侧风格的代码,你猜猜我的反应是什么。

嗯,可能我对代码风格确实有些强迫症,但事实上,Code Style 并不仅仅是代码是否好看那么简单,如果没有按照惯例来编写代码,甚至会让阅读者产生疑惑。

private Listener listener = new Listener () 
// So Listener looks like a class? {}; 
// Oops, it is an interface

如果代码可读性还不足以打动你,那么想象一下这个场景,你的同事说他修复了两个空指针问题,请你帮忙
Code
Review,你查看了这个文件的修订历史,乍看之下有许多改动,看来是个大动作。然而事实上,绝大部分改动是代码格式调整,只有两处改动与需要
Review 的问题相关。

(看来这位同事的 IDE 使用了不同的自动缩进设置,导致所有行都产生了缩进)

之所以会产生以上这些影响工作效率的问题,是因为团队没有重视 Code Style,没有把它当做一项工程实践,既没有对其达成一致,也没有正确地使用工具帮助实施。

那就按照工程实践的标准来实施 Code Style

本文将重点介绍 Java 项目中 Code Style 的工具支持,但在此之前,你的团队需要一起做一些决定:

  1. 使用哪种 Code Style? 每个人可能都有偏好的 style,但在团队协作面前,需要一定的妥协。有些公司或组织有着统一的 Code Style 指导标准,萧规曹随是个不错的选择(但是要确保这类统一指导标准在制定时参考了开发人员的意见,是切实可行的),你的团队也可以自己裁剪,但至少要保证项目(Repository)级别上使用同一种 Style。
  2. 如何处理不符合 Code Style 的提交? 大家往往懈怠于事后补救的方式,我的建议是不要让不符合约定的代码流入代码库。对于遗留项目,尤其是大型项目,可以选择一部分代码作为实施范围,集中修复 Style 问题后严格实施,切忌操之过急,最后团队疲惫不堪只得放弃。

我们都知道人工监督检查的方式是不可持续和不可靠的,来看看有哪些工具可以提供帮助吧。

懒惰是第一生产力

工程实践不能没有自动化工具支持,在 Java 生态圈中,Code Style 工具最出名的应该是 Checkstyle 了,它可以通过 XML 形式的 外部 DSL 来定义 Code Style 的检查风格,比如你可以从这里找到 Google 的 Java Checkstyle 配置文件 。这里我不会详细介绍 Checkstyle 本身,相反,我会更多地探讨如何工程化地使用 Checkstyle,在交付代码的各个活动中,我们都可以用到 Checkstyle,进行 360°无死角的检查。

(和 Code Style 相关的代码交付生命周期)

守住提交的质量关口

为了贯彻不让不符合约定的代码流入代码库的决定,可以优先在服务端设置 Code Style 的检查关卡。

(优先守住代码提交时的服务端检查,可以考虑使用 CI 服务器来实现)

从实现层面上说,有两种方式:

一是在 SCM(Source Control Management,例如 Git/SVN)服务端设置检查项,如果不达标则拒绝提交,但这种方式相对不容易实现,而且一般 SCM 服务端也不由开发团队管理,设置起来不灵活也不方便。

二是利用 持续集成 服务器,开发团队的每一次提交都会触发一次构建,我们可以在构建脚本中加入 Checkstyle 检查,如果有不达标的代码则让构建失败,以便告诉提交者立即修复 Style 问题。我更推荐这个方案,因为相关的工具支持都很成熟,实现简单,而且构建过程可以在开发者的本地环境复制,以便在后续改进中将 Checkstyle 检查前移,提供更快的反馈。如果团队使用 Maven/Gradle 等构建工具,可以用插件的方式实现 Checkstyle 检查并嵌入到整个构建过程中。这样 CI 服务器只要调用构建脚本就行了。

在开发者本地验证 Style

(在开发者本地实现验证,反馈关口前移)

在实现了 CI 验证后,就可以着手实现开发者本地验证了,这样开发者就不用等到提交代码到服务端后才会获得反馈了。由于之前采用的是构建工具的插件方案,所以开发者在本地运行构建就能实现验证了。比如 Gradle 提供了 Checkstyle 插件支持,你可以在这里找到 Gradle Checkstyle Plugin 的详细配置文档,如果你使用 Maven,则可以参考这里。现在只需要一条命令,开发者久能在本地验证 Code style 了。

# build.gradle 
# omitted plugins 
apply plugin: 'checkstyle'  
checkstyle {     
    configFile = file ("config/checkstyle.xml") //指定 checkstyle 配置文件     
    toolVersion = "7.4" //指定 checkstyle 工具的版本,部分 style 规则有版本要求 
}  
checkstyleTest.exclude "**/ContractVerifierTest**" // 忽略检查生成代码,这个锅我们不背  

// 如果出现 checkstyle warning 也使构建失败,插件默认只支持 checkstyle error 失败 
// Fail build on Checkstyle Warning Violation · Issue #881 
tasks.withType (Checkstyle) .each { checkstyleTask ->     
    checkstyleTask.doLast {         
        reports.all { report ->             
            def outputFile = report.destination             
            if (outputFile.exists () && outputFile.text.contains ("<error ")) {    
                throw new GradleException ("There were checkstyle warnings! For more info check $outputFile")             
            }         
        }     
    }
}

现在只需要一条命令,每个开发者就能在本地验证 Code Style 了。你可以在 这里 找到 Gradle Checkstyle Plugin 的详细配置文档,如果你使用 Maven,则可以参考 这里

➜  court-booking-backend (master) ✗ ./gradlew check 
Starting a Gradle Daemon (subsequent builds will be faster) 
:compileJava 
:processResources UP-TO-DATE 
:classes 
:checkstyleMain [ant:checkstyle] 
    [WARN] /Users/twer/Workspace/restbucks/court-booking-backend/src/main/java/com/restbucks/courtbooking/http/CourtRestController.java:16: 
    'method def' child have incorrect indentation level 4, expected level should be 8. [Indentation] :checkstyleMain FAILED  
FAILURE: Build failed with an exception.

本地验证很不错,但我有时候会忘记执行

(让机器代劳琐事)

有时候,开发者修改了代码后会忘记执行本地检查就提交代码了,最好能够在提交代码前自动执行检查。如果你使用 Git 的话,可能会想到 Git commit hook,比如这是我常用的 pre-commit hook

#!/bin/sh
# From gist at https://gist.github.com/chadmaughan/5889802

# stash any unstaged changes
git stash -q --keep-index

# run the tests with the gradle wrapper
./gradlew clean build

# store the last exit code in a variable
RESULT=$?

# unstash the unstashed changes
git stash pop -q

# return the './gradlew build' exit code
exit $RESULT

将该脚本拷贝到 .git/hooks/ 下,在执行 git commit 的时候就会自动触发检查了,如果检查失败则提交失败。但问题是 .git 并不能提交到远程代码仓库,那么除了人工分发和拷贝外,有没有更好的方式在团队中共享这个机制呢?

可以曲线救国!把 pre-commit 纳入版本控制(如下面的 config/pre-commit ),再使用构建工具的扩展机制来自动完成拷贝工作,这样可以间接实现 git hooks 的团队间共享。

# build.gradle

task installGitHooks (type: Copy) { //将 pre-commit 拷贝到指定位置
    from new File (rootProject.rootDir, 'config/pre-commit')
    into {
        new File (rootProject.rootDir, '.git/hooks')
    }
    fileMode 0755
}

build.dependsOn installGitHooks //设置执行 build 任务时会自动触发 installGitHooks 任务

关闭包围圈,编辑时反馈

(实时反馈)

之前基于构建工具的方案都很好,但是对于开发者来说,最好能将反馈前移到编辑时,并且可视化。所幸的是,Checkstyle 的生态系统非常成熟,各主流 IDE 都有插件支持,以 Intellij Idea 为例,可以使用 checkstyle-idea 插件,让团队成员手工设置插件,使用项目的 checkstyle 配置文件即可(我目前还没有找到自动化配置的方式,或许 gradle idea 插件可以?)

(checkstyle-idea 插件配置和效果)

有了自动实时检查,最好还能将 IDE 的自动格式化与 Checkstyle 配置文件挂钩,否则自动格式化反倒给你添麻烦了。

(为 IDE 导入 checkstyle 配置文件作为自动格式化的依据)

如果你连自动格式化都懒得按,那可以试试 Save Actions 插件,它可以在 Intellij 保存文件时自动执行代码格式化等动作。

(这个插件目前对部分文件有些问题,可以通过 File path exclusion 忽略)

总结

  1. Code Style 影响工作效率,团队应将其当做工程实践予以重视。
  2. Code Style 不能靠人工监督和检查,应该提供端到端的工具支持
  3. 服务端检查(推荐集成到 CI 的构建步骤中)
  4. 开发环境检查(使用各构建工具的 Checkstyle 插件)
  5. 自动提交检查(git pre-commit hook 与共享)
  6. IDE 增强(checkstyle 插件实时可视化反馈/自动的自动格式化!)
  7. 以上的工具都要依据为同一份 Checkstyle 配置文件,并纳入版本控制

希望以上这些招数可以解救 Java Code Style 强迫症 :)

作者:程序师
用程序师的眼光看世界
原文地址:拯救Java Code Style强迫症, 感谢原作者分享。

发表评论