查看原文
其他

如何产出规范、安全、高质量的代码?

少个分号 Thoughtworks洞见 2022-03-15

对于一个软件开发团队,可以通过哪些代码质量指标和扫描方法让团队产出规范、安全、高质量的代码?让开发团队运行的安全、透明、可靠?本文总结了其中一些实践和工具,包含常见代码质量扫描工具、代码质量指标、第三方依赖管理、安全运维等几个方面,主要适用于 Java/JavaScript 技术栈的 web 项目,希望对于想要规范化自己的项目的 Tech Lead 有所帮助。


对于一个软件开发团队,可以通过哪些代码质量指标和扫描方法让团队产出规范、安全、高质量的代码?让开发团队运行的安全、透明、可靠?
本文总结了其中一些实践和工具,包含常见代码质量扫描工具、代码质量指标、第三方依赖管理、安全运维等几个方面,主要适用于 Java/JavaScript 技术栈的 web 项目,希望对于想要规范化自己的项目的 Tech Lead 有所帮助。

代码扫描和常见质量指标

“祸患常积于忽微”,往往一些奇怪的 bug 都是一些不规范的小问题造成的。德国飞机涡轮机的发明者帕布斯·海恩提出的一个在航空界关于飞行安全的法则,法则指出: 每一起严重事故的背后,必然有 29 次轻微事故和 300 起未遂先兆以及 1000 起事故隐患。应用于软件开发中,如果项目中代码混乱不堪,必然会在某个时候最终爆发大量的问题。

这里整理了一些常见的扫描工具和代码质量指标,可以在搭建项目基础设施时引入,用于自动化的检查代码中潜在的问题,达到控制代码产出质量的目的。

扫描工具

  • checkstyle

checkstyle 是常用于 java 项目的扫描工具,检查源代码是否与代码规范相符,检查项目主要包括:Javadoc 注释、imports、过长的类和方法、空格、重复文件、圈复杂度等,默认使用 sun 的代码规则,也可以配置自定义的代码规则,例如阿里就发布了相应的检查规则。
  • findbugs

通过 Bug Patterns 的概念,寻找代码中可能出现的 bug,检查项目主要包括:不良编程习惯导致的问题、性能问题、安全问题、线程问题等。例如,应使用 equals 判断相等,而不是 “ =” 操作符、流需要关闭、线程资源需要释放等问题。findbugs 的模式库对编程经验也有较好的提升作用。还可以导入和编写自己的 Bug Patterns 完善检查机制。

  • simian
simian 是一个用于检查重复和相似代码的工具,它的重复检查类似于论文查重,会提示一定的相似度。可以单独运行,也可以作为 checkstyle 插件来使用,相对来来说比较小众。
  • pmd

pmd 是一款跨语言的通用静态扫描工具,具备一部分 checkstyle、findbugs 的功能,不再赘述。

  • ESlint/TSlint

前端界的 checkstyle , TSlint 设计用来做 TypeScript 类型检查,ESlint 作为代码风格检查工具。不过现在 ESlint 也提供了TypeScript 类型检查功能,基本上 ESlint 能整合这两个功能。由于性能问题, TypeScript 也采用了 ESLint 作为 TSlint替代的检查工具。

  • SonarQube

SonarQube 是一款用于代码质量管理的开源工具,它主要用于管理源代码的质量。SonarQube 和上面的工具不太一样,SonarQube 设计目的是提供一个平台,通过插件的方式提供对各个语言进行支持,也可以和 checkstyle、pmd、simian 等工具进行集成。SonarQube 一般需要单独部署成一个服务,提供数据库,可以记录扫描结果等信息。

  • npm audit

npm audit 是 npm 6 之后的版本 自带的一个前端安全扫描工具,可以扫描 npm 依赖中的潜在的漏洞威胁。这些引入的漏洞可能威胁用户开发的机,另外也可能被带入 bundle 文件发布到线上,带来安全问题。目前 npm audit 会在 npm install 完成后自动执行,需要留意安全威胁报告。

  • Fortify SCA

Fortify SCA(Source Code Analyzer) 是一款非常优秀的代码安全扫描工具,用于分析代码中潜在的安全问题。通过调用语言的编译器或者解释器把代码(Java、C、C++等源代码)转换成一种中间媒体文件 NST(Normal Syntax Trcc),然后通过模式匹配相关的方式抓取存在于漏洞库中的漏洞。例如,上传的文件没有做检查等 XSS 攻击。

  • OWASP Dependency-Track

开放式 Web 应用程序安全项目(OWASP)是一个非营利组织,提供了很多安全标准、数据库、社区和培训。其中一个工具就是 OWASP Dependency-Track,可以对第三方依赖包中的知名漏洞进行检查,扫描结果受到漏洞数据库的更新影响。

  • archunit 架构规范检查

前面的检查是代码层面,archunit 可以用于代码架构检查,可以定义规则检查每个包中的实现是否符合规范。例如,controller 包中的类不能实现 service 的接口,repository 下的类必须实现 Repository 接口。通过 archunit 可以减少 codereview 的工作量,避免项目的结构被破坏。

统计工具

sloccount、sourcemointor 这两个工具可以用于统计代码数量,包括行数、文件数、注释等。除了在项目中扫描 bug 之外,配置代码统计工具可以对项目有一个整体的认知。其他的扫描工具还很多,例如 coverity、codemars、binscope、synk、appscan、retire.js 等工具,不再一一列举。

最佳搭配

这几款工具之间的功能有所重叠,在实际工作中,我们可以根据上面推荐的关注的点,重点清除这些问题。这些扫描工具全部用上除了会带来团队压力和维护成本之外,代码质量不会随着引入的插件增多。除开有质量团队的大厂提供这些扫描平台外,敏捷团队往往不会太大,团队持续关注一个精简的扫描组合更好。

Java 后端

  1. checkstyle Java 代码风格守护,Java 项目至少应该配置一个默认的 checkstyle 规则。至少让项目干净,没有无用、重复的代码,以及超大的类和方法。建议做到每次提交代码前检查。

  2. findbugs 常见不规范的代码检查,一些空指针、equals 检查非常有用,而且 IDE 的插件也很好用。

前端

  1. eslint 守护 JavaScript 代码风格,eslint 搭配一个 .editorconfig ,可以方便的让编辑器保持同 eslint 一致的代码风格。
  2. npm audit 项目中第三方包的威胁扫描,npm 自带无需额外安装,npm 6 以后自运行,需要关注并修复报出的安全问题。

安全

  1. fortify 扫描代码中的漏洞,用它检查出来的大部分安全问题都是注入攻击、XSS 等攻击,这些问题明显可以在开发过程中避免。可以作为 Jenkins 插件配置,和单元测试作为同一阶段运行。
  2. OWASP 插件 用来扫描第三方依赖漏洞,因为项目中的依赖不会像源代码一样频繁变化,推荐使用 Jekins 插件,定期执行即可。
为什么不用 SonarQube 呢,SonarQube 是一个非常优秀的代码质量开放平台,需要单独的配置安装,需要花费额外的时间维护,对于小团队来说成本较高,如果有专门的质量团队可以考虑维护一套。

常用代码质量指标参考

  1. 编译告警数大部分程序员基本上忽略 warning,但是编译器出现了告警是一种不好的体现,意味着软件可能工作,但是存在不好的实践,而这种不确定性,会带来不确定的 bug 最终让人一头雾水。编译过程中的告警,尽量消除掉,编译告警的值推荐消除到 0。
  2. 平均函数代码行数,过大的函数会导致阅读困难,而且往往过大的函数职责不够单一,一般将一个方法代码行数控制到 30 - 50 行。
  3. 平均文件代码行和平均函数代码行一样,过长的文件一样难以维护,一般一个文件10多个方法,因此文件的代码行数一般控制到 300 - 500 行。
  4. 冗余代码有时候我们代码中可能存在未使用的方法、变量等代码,这让维护者一头雾水,通常需要清零。
  5. 总文件重复率出现重复文件的次数。除了编写单元测试的情况下,业务代码不应该出现重复代码,推荐值为 0。
  6. 总代码重复度代码的重复度检查,限于扫描工具的识别模式,需要有一定的容忍度,推荐值在 5% - 10%
  7. 平均函数圈复杂度圈复杂度用来衡量一个模块判定结构的复杂程度。如果一个方法内部有大量的 if 语句嵌套,意味着这个方法的实现质量低下,且程序复杂度高不利于维护,推荐值小于 5%。
  8. 安全告警如果配置了安全扫描工具,例如 Fortify,安全威胁应该被清零。
  9. 代码缺陷如果配置了缺陷扫描工具,例如 Findbugs,需要清零。


第三方依赖规范化

软件开发过程中,不可避免的需要引入第三方或者开源软件包作为库或者框架引入。“第三方” 其实不是一个软件工程术语,现今在软件行业里面的理解是:第一方为自研的软件,第二方为内部发布的软件,第三方为从社区或者外部商业途径引入的软件包。
对于个人开发者而言,面向“搜索引擎”编程往往将来源不明的代码片段和程序包引入到项目中。对于企业来说,考虑到的不仅仅是功能是否能实现,还要考虑引入时带来的成本和问题,例如是否需要授权、开源协议是否合理、是否会带来安全威胁。
企业对于第三方依赖的引入分为几种情况:
  1. 作为开发工具引入,例如 gcc、Jenkins,基本没有开源协议问题,但是需要注意开发机、CI 会有安全风险。Jenkins 曾出现过漏洞,CI 服务器被当做远程矿机使用。

  2. 作为服务部署使用(SaaS,部分开源协议会限制这种使用方式,第三方依赖的安全问题会威胁服务器。

  3. 通过软件包再发布,大部分开源软件对这种使用方式有较多要求,例如 GPL 开源协议具有传染性,要求使用了 GPL 的项目也要开源。

  4. 拷贝源代码引入项目,非常不推荐这种方式,尽量通过包管理的方式引入。

引入第三方依赖需要充分考虑,尽可能最小成本的引入。在一个 React 的前端项目中,有不熟悉的工程师,为了使用一个简单的手风琴效果,引入了整套 bootstrap。不仅破坏了使用 React 的最佳实践,而且让输出的 bundle 文件大小激增数倍,造成首屏加载的性能问题。

常见商业友好的开源协议

商业用户常用的开源协议实际上只有6种左右,即 LGPL、Mozilla、GPL、BSD、MIT、Apache,另外还有极其宽松的 The Unlicense,但采用的开源软件不多。
GitHub 提供了一个 license 清单的列表 https://choosealicense.com/licenses/,我根据开源协议的宽松程度,整理了一个列表,方便查看:
几乎所有的开源协议有一个共同的注意事项:采用该开源协议的软件项目,不提供任何责任转移和质量保证。也就是说采用开源软件造成的法律问题和开源项目无关,另外需要使用者承担因质量问题造成的所有后果。另外,除了引入的程序包之外,字体、图片、特效音、手册等媒体资源也算广义上的“软件”需要考虑开源协议和使用场景。

第三方依赖管理

对项目中出现的任何第三方依赖有效的管理有非常重要的意义,通过扫描工具,识别出项目中是否有源码、jar包、二进制文件是否来源于某个开源项目。
任何的第三方软件需要申请入库管理(内部其他团队申请通过可以直接使用),质量团队对申请的软件进行评估:
  • 是否有开源义务需要履行
  • 引入的第三方依赖是否有 CVEs等漏洞
  • 第三方开源软件是否仍然在维护
质量团队根据上面的一些条件,决定出申请的软件能否在项目中使用,允许被采用的软件会定义出优选级别,优先推荐团队使用较为优选的软件,并对项目整体的优选率有一定要求。如果项目中出现了无法识别的二进制文件、非约定目录下的代码片段,需要报备。通过良好的依赖管理和规范化,能减少不良第三方依赖的引入,让软件项目透明、可信。
一些商业公司提供这些完整的服务,例如 fossid、blackduck、code-climate 等。

运维安全

大的软件公司,往往有一堆流程和要求。虽然一线开发对堡垒机、防火墙、各种安全规范显得不耐烦,但这些安全措施也在保护开发者。

防火墙用于环境隔离

往往开发者理解的防火墙用于防止网络入侵、审计、入侵检测等功能,除此之外,防火墙还可以用于各个环境的隔离。一般来说,企业对于生产环境的数据控制比较严格,不会将生产环境的权限交给团队所有开发者,但网络连接有可能疏漏。
曾经出现过一次线上事故,由于配置文件错误,将原本应该连接到测试的数据库连接到了生产环境,造成大量脏数据写入。如果通过防火墙规则对各个环境进行隔离,这类问题将不会出现。
另外也可以设计 DMZ 区,将面向用户侧的网关部署到 DMZ 区,仅仅开放必要的端口给网关,实现内外网的物理隔离。同时,对整个系统的防火墙策略应该清晰地记录,否则在做大的基础设施更新时,梳理出所有的防火墙策略,是一件比较困难的事情。

凭据管理

项目中会用到大量的凭据,例如数据库、第三方系统对接的 key,使用明文不是一件好事。理想的情况下,对项目中所有的密码信息进行掩盖(mask),避免 CI、日志中敏感信息的泄露。
有很多种方法可以掩盖项目中的密码信息:
  1. 使用环境变量对密码信息进行覆盖。

  2. 使用Spring boot 的项目可以配置 jasypt,使用 jasypt 将密码加密,将生成的加密串配置 ENC(加密串) 到工程的配置文件中。加密过程可以加盐作为解密的凭据,“盐” 可以不存放到工程中,在工程部署的时候注入即可。

  3. 如果使用 Jenkins 等 CI/CD 工具,可以使用构建平台提供的凭证管理工具。

  4. 如果使用 Spring cloud,可以使用 spring cloud vault 组件部署一个凭证管理服务

另外,建议不要用任何个人凭据用作系统对接,应该使用一个公共的应用凭据。

堡垒机

一般来说我们管理服务器,所有的运维操作需要通过堡垒机进行操作。开放 22 等高危端口,允许开发者直接登录到服务器是一种不安全的做法。
堡垒机,通俗的来说是跳板机 + 监控。最初使用的跳板机配置了两张网卡,用于连接开发环境和生产环境,并没有监控功能。在此基础上,堡垒机增加了统一运维管理的功能,往往需要两步验证(SMS 或 Email),并对所有的操作进行记录和监控。
在需要团队参与运维工作的场景中,非常有必要部署一套堡垒机服务,并使用 LDAP 对接到团队成员的 ID 上,便于集中运维管理。

定期对系统软件扫描

Linux 系统往往有云厂商推送安全补丁和风险提示,但是安装到服务器上的软件,例如 JDK、nodejs,需要自己检查安全问题。因此需要在系统中安装并定期运行 CVEs 检查并及时更新。有一款 cvechecker 可以帮助运维人员,编写一个脚本定期运行 cvechecker 检查系统中已知的软件是否存在 CVEs 漏洞,并提醒开发者及时更新。

写在后面

刚开始工作时候,喜欢动态的、灵活的编程语言,讨厌的死板的、套路化的编程语言,然而需要很长一段时间,才能意识到 “约束是程序员的朋友”。对一些安全知识了解的来源大多来自修复 SonarQube 的经历,使用 findbugs 也让我对 Java 基础认识的更加深刻。
类似的,在使用一些框架、平台的时候往往存在大量的限制,有时候开发者难以意识到 “限制” 正是框架、平台的作者 “保护” 应用开发者的一种方式。有一些开发者以 Hack 框架、平台为乐,但是这样会带来潜在的隐患,在用户量上来之后负面效应表现的尤为明显。
项目的规范化对于 Tech Lead来说可以减少程序的运行事故和 codereview 时间,对于团队来说也许可以少加班吧。

- 相关阅读 -
API设计的几条原则
标准化技术下的软件开发


点击【阅读原文】可至洞见网站查看原文&绿色字体部分的相关链接。

本文版权属ThoughtWorks公司所有,如需转载请在后台留言联系。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存