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


对于一个软件开发团队 , 可以通过哪些代码质量指标和扫描方法让团队产出规范、安全、高质量的代码?让开发团队运行的安全、透明、可靠?本文总结了其中一些实践和工具 , 包含常见代码质量扫描工具、代码质量指标、第三方依赖管理、安全运维等几个方面 , 主要适用于 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 时间 , 对于团队来说也许可以少加班吧 。
【|如何产出规范、安全、高质量的代码?】文/ThoughtWorks少个分号


    推荐阅读