前端工程质量保障:代码质量

247 阅读17分钟

代码质量是工程质量的重要一环。代码质量通常指代码本身的质量,它能够在一定程度上反映当前工程的质量和研发人员的能力。软件行业有个公理:代码会不可避免地走向腐烂,使得维护成本变高。为了缓解代码的腐烂速度,软件工程师们提出了重构、DDD、TDD等方案,建立了复杂度、重复率等指标衡量代码质量;推出了策略模式、适配器模式等设计模式来提高代码的可读性、扩展性。

1. 代码质量指标

代码质量指标总体可以分为两大类:主观指标和客观指标。

1.1 主观指标

主观指标指代码评审人员给出的主观评定,评估代码在特定业务场景下的质量可靠性。它需要耗费人力去进行代码评审(Code Review, CR),并且对CR人员的能力素质有一定的要求。CR有两个要点:

  • 统一团队规范
  • 评审到位,避免形式主义

主观指标有很多维度,主要包括可读性、可扩展性、可维护性、鲁棒性等。一般来讲,在实际开发中只要能满足可读性可扩展性,就基本可以保障代码的可维护性,CR时应该着重从这两点进行考核。

可读性

可读性指的是代码是否易于理解,包括变量命名是否清晰、函数和类的命名是否符合逻辑等。好的可读性可以让其他开发人员(甚至是几个月后的自己)能够快速理解代码的功能和意图,有助于协同开发和后续维护。

提升可读性的方法有很多,比如:

  • 注释和文档:解释代码的复杂部分或者特殊的业务逻辑
  • 统一的缩进:合理的缩进能够清晰地展示代码的层次结构
  • 统一的变量命名方案:比如驼峰式命名法、帕斯卡命名法等

可扩展性

在代码层面,可扩展性体现为当业务需求发生变化,如功能的添加、业务规则的修改或者系统负载的增加时,代码能够以相对较小的改动代价来适应这些变化。

影响可扩展性的因素包括:

  • 模块化编程:把系统分解为多个独立的模块,每个模块负责特定的功能,降低模块间的耦合度。模块化的设计使得系统可以像搭积木一样,根据需要添加、替换或者删除模块来实现功能的扩展。

  • 接口的开放性和规范性:良好的接口设计可以方便其他模块或外部系统与之集成,从而实现扩展性。接口的开放性使得系统能够与外部的新技术、新服务进行对接,以满足不断变化的业务需求。

常见的设计模式大多遵循以上原则,采用合理的设计模式可以有效地提升可扩展性。

  • 发布-订阅模式:发布-订阅模式包含发布者订阅者消息中心这三个核心要素。发布者发布消息到消息中心,订阅者向消息中心注册感兴趣的消息类型,而消息中心在发布者发布相应消息时通知订阅者进行处理。消息中心就像是一个消息中转站,借助消息中心实现了发布者和订阅者之间的解耦,使得系统更加灵活和可扩展。

    应用场景示例:前端组件通信

    • 场景描述:在一个复杂的网页应用中,有一个导航栏组件和多个内容页面组件。当用户在导航栏点击某个菜单选项时,需要通知相应的内容页面组件进行显示或更新。
    • 实现方式:可以使用发布-订阅模式,将导航栏组件作为发布者,内容页面组件作为订阅者。当点击导航栏菜单时,通过事件总线发布一个与菜单对应的消息。各个内容页面组件在初始化时订阅相应的消息类型,当收到相应消息时,内容页面组件就可以进行显示或更新操作。
  • 观察者模式:观察者模式包含主题(Subject)和观察者(Observer)两个核心要素。主题对象维护着一个观察者对象列表,当主题的状态发生改变时,会主动遍历观察者列表,并调用每个观察者中定义的更新(Update)方法,将状态变化的信息通知给观察者。观察者接收到通知后,会根据自身的逻辑来对主题的状态变化做出相应的反应。这样就使得主题对象和观察者对象之间能够保持松耦合,实现了一种灵活的事件通知机制。

    应用场景示例:路由变化与组件更新

    • 场景描述:在单页应用(SPA)中,当路由发生变化时,不同的页面组件需要根据新的路由加载相应的数据并更新自己的显示状态。
    • 实现方式:将路由系统看作是主题,各个页面组件看作是观察者。路由变化时,路由系统这个主题的状态发生改变,路由系统会通知所有注册的页面组件观察者。页面组件收到通知后,根据新的路由信息,获取相应的数据并更新自己的显示内容。
  • 策略模式:定义一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户。

    应用场景示例:表单验证

    • 在表单验证场景中需要对用户名、密码、邮箱等字段进行验证,我们可以定义一个验证策略的接口

    • 当需要添加新的验证规则,只需要创建一个新的验证策略类,实现validate方法,在其中定义具体的验证逻辑,而表单提交的主逻辑(调用验证策略的部分)不需要进行大规模修改,它只需要根据新的字段需求,选择对应的验证策略即可。

  • 适配器模式:适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。在软件开发中,这种情况经常出现在系统升级或者整合第三方组件的过程中。

    应用场景示例:适配不同的第三方地图 API

    • 使用地图功能时,有时候需要集成不同的第三方地图 API(如百度地图和高德地图),它们的接口和调用方式可能不同。我们可以创建适配器类来统一接口。例如,定义一个抽象的地图适配器接口,有initMap(初始化地图)、addMarker(添加标记)等方法。然后针对百度地图和高德地图分别创建适配器类,在这些适配器类内部,将统一接口的调用转换为对应的百度地图 API 或者高德地图 API 的实际调用。

    • 当需要接入新的第三方地图(如腾讯地图)时,只需要创建一个新的适配器类,按照统一的地图适配器接口实现相应的方法。而使用地图功能的其他前端组件只需要通过统一的地图适配器接口来调用地图功能即可,不需要进行大规模修改。这样就方便了前端代码在集成不同地图资源方面的扩展,提高了代码的灵活性和可扩展性。

1.2 客观指标

主观指标需要依赖评审人员的素质,并占用人员的时间,仅靠主观指标往往难以达到预期的作用,还需要建立客观指标来衡量代码质量,让计算机代替代码评审人员进行代码质量评估。

客观指标主要包括以下3个。

  • 圈复杂度(Cyclomatic Complexity):也称为条件复杂度,用来衡量模块判定结构的复杂程度。简单来说,就是衡量代码中不同执行路径的数量。例如,一个简单的if-else语句有两条执行路径,它的圈复杂度为 2;一个包含if-else if-else的语句块,圈复杂度为 3。较高的圈复杂度意味着代码的逻辑更加复杂,理解和维护起来可能更困难,并且在测试过程中需要更多的测试用例来覆盖所有可能的执行路径,在运行时出现问题的概率也更大。

  • 千行代码 Bug 率:衡量代码中每千行代码所包含的缺陷数量。例如,如果一个项目有 10000 行代码,经过测试发现了 20 个 Bug,那么千行代码 Bug 率就是 2(20/10000×1000)。千行代码 Bug 率是一个直接反映代码质量的指标,Bug 率越低代表代码质量越好。

  • 代码重复率:代码重复率用于衡量代码中重复部分占总代码量的比例。高代码重复率是代码质量不佳的一个表现。重复代码增加了代码的体积,而且当需要修改重复部分的功能时,得在多个地方进行相同的修改,容易遗漏导致错误,增加了维护成本。在项目迭代的过程中,重复代码的出现是难以避免的。

通过以上指标,团队可以在开发过程中有效审视研发效率和代码质量问题。通过度量指标可以激励团队重视并改善能度量的元素,但是也可能导致哪些无法度量的元素被忽略,团队管理者应该慎重评估指标的重要性和合理性。

2. 代码管理

2.1 代码规范

代码规范是一组规则和指南,用于确保代码在风格、结构和编写习惯等方面保持一致和高质量。代码质量可以细分为编码规范注释规范两类。

编码规范方面,又包含了很多子类,包括代码风格、代码性能、代码安全性等。以代码风格为例:

  • 代码格式:通过统一的缩进展示代码的层次结构;对于长的函数调用或者复杂的表达式,可以适当换行;将长语句拆分成短句,让代码更清晰简单;
  • 命名规范:在 HTML 中,标签的idclass命名应该具有描述性;在JavaScript中,变量和函数名应该清晰地表达它们的用途,采用小写驼峰命名法命名变量和函数,使用动词+名词的形式描述函数的功能特性;

注释规范方面,按照注释功能可以分为说明性注释提示性注释

  • 说明性注释:对代码的功能、意图、业务逻辑或复杂算法进行详细的解释。比如入参、出参、函数作用等。
  • 提示性注释:提醒开发人员注意某些潜在的问题、可能的改进方向、临时的解决方案或者尚未完成的任务,比如针对以TODO开头的注释,如果在项目发布时扫描检测到,就中断发布并且提示开发人员进行修复。

2.2 代码格式化

通过制定代码规范,可以在一定程度上保证团队的代码风格一致性。然而光靠代码规范来确保代码风格一致是不够的,更可靠的做法是在工程中配置代码自动格式化工具。

Prettier是前端的代码格式化工具,可以支持多语言的代码自动格式化,包括JavaScript、TypeScript、HTML、CSS、JSON 等,可以在保存文件时自动修复代码格式问题,大大提高了开发效率。这样在代码评审阶段也不再需要关注代码风格的问题。

Prettier还可以结合husky使用,在代码提交前对变更的文件代码进行自动格式化:

"husky": {
  "hooks": {
    "pre-commit": "pretty-quick --staged"
  }
}

2.3 工程目录结构

一个优秀的工程目录结构可以很好地将代码的作用域进行划分。如果开发人员可以通过目录结构快速地找到对应的功能模块,那么将有效提高协同开发的效率。

在目录结构的设计上,可以遵循领域驱动设计(Domain-Driven Design,DDD)原则。比如:

|-- assets // 静态资源,例如图片、svg等
|-- components // 公共组件
|-- configs // 工程配置相关
|-- models // 业务模型
|-- pages // 页面
|-- scripts // 脚本目录,比如构建、发布脚本
|-- services // 接口服务
|-- utils // 工具函数
|-- README.md // 工程使用说明文件

2.4 类型约束

JavaScript是一门弱类型语言,在开发时非常灵活,但是也带来很多隐患,比如类型隐式转换。这样的问题对于大型工程来说是致命的,排查起来极其麻烦。因此,需要引入TypeScript,对代码进行类型约束。

引入TypeScript类型约束可以带来很多好处:

  • 减少潜在错误:类型约束可以在编译阶段发现许多潜在的错误
  • 提供最新的JavaScript特性:使用新特性编写代码,然后编译成可兼容的代码
  • 优化开发工具支持:编辑器可以根据类型定义提供智能的代码补全建议,以及更好的跳转导航
  • 增强代码文档化:类型约束起到了一种代码文档的作用,可以帮助团队成员更快地理解代码的功能和结构,减少沟通成本

3. 接口管理

接口作为前端工程中的重要一环,其代码质量的管理也尤为重要。我们主要从接口数据接口类型两个方面来考虑。

3.1 接口mock

前端在开发联调时往往面临接口数据量少、数据过于简单的情况,需要通过mock数据来支持。简单的做法就是在代码里注释掉相应接口然后插入硬编码mock数据,等联调完再恢复。这样的做法虽然比较方便,但也存在很大的问题:mock数据直接侵入工程代码,在提交过程中产生很多无用的代码,而且容易出现误上线的风险。因此,我们更倾向于采用接口mock来模拟数据。

在实际开发过程中,开发人员对数据mock的诉求大致可以分为三类:

  • 开发无侵入:通过代理(Proxy)或者中间件(Middleware)来拦截数据请求,然后返回模拟的数据
  • 多数据类型模拟:比如生成随机的文本、数字等数据,通过返回随机数据来模拟各种场景
  • 多场景模拟:除了正常场景,还有异常情况(服务器错误等)、边界情况(数据为空等)等场景

通过接口mock,开发人员可以轻松地模拟请求数据。开源社区有很多与接口mock相关的解决方案,主要分为工具和平台两种。

  • 工具:以Mock.js为例。它的原理是拦截XMLHttpRequest请求来实现数据mock,不会发起真实的请求。另外,使用mockjs-fetch可以支持对Fetch API请求的拦截。
  • 平台:以YApi为例,它是一个可视化接口管理平台,可以帮助开发人员创建、发布和维护API。在YApi平台上配置好需要mock的接口之后,只需在webpack-dev-server中配置接口转发服务,即可将请求转发到YApi平台,从而返回mock数据。

3.2 接口类型约束

在开发过程中,开发人员为了查看接口字段的类型和作用,就需要依赖接口文档。这种方式不但降低开发效率,还增加了系统的不稳定性。当系统需要重构或者对接口涉及字段进行增删类改动时,就要先查找关键词再手动替换修改,人力成本非常高,且容易改错或遗漏。

为了避免这样的问题,可以使用TypeScript对接口方法进行类型约束,在实现接口方法时直接声明入参和出参的数据类型。编辑器就可以基于接口类型进行提示和校验,大大提升了开发效率。

3.3 接口类型自动化

使用TypeScript对接口方法进行类型约束,可以有效保障接口质量,但是如果每个接口类型约束都要开发人员手动实现,就带来了很大的负担。因此,最好能够自动化生成接口类型约束。

自动化生成接口类型约束的方式有多种。

  • 基于mock数据JSON实现。比如使用json-schema-to-typescript可以对mock数据JSON生成TypeScript定义。
  • 通过Swagger和YApi等接口平台实现。比如Swagger Codegen工具可以根据 Swagger定义文件来生成接口类型约束。

4. 代码质量平台SonarQube

SonarQube 是一个开源的代码质量管理平台,用于对代码进行静态分析,以检测代码中的潜在问题、漏洞、代码异味(code smell)等,从而帮助开发团队提高代码质量。

SonarQube提供了可靠性、安全性、可维护性、覆盖率、重复、大小、复杂度、问题等一系列的指标,以量化的形式度量代码质量的变化,并且以可视化的方式提供报告,可以很方便地统计和管理代码质量。

SonarQube 可以与开发工具以及CI/CD工具集成:

  • 与开发工具集成:在 IDE 中安装 SonarQube 插件后,开发人员在编写代码时,就能在 IDE 中直接看到 SonarQube 检测到的代码问题提示,就像代码检查工具一样,方便开发人员及时发现和解决问题。
  • 与CI/CD工具集成:它能够很好地集成到 CI/CD 管道中,如 Jenkins、GitLab CI 等。在 CI/CD 流程中,每次代码构建和部署时,自动触发 SonarQube 分析,确保只有代码质量达到一定标准的代码才能进入后续的部署阶段,从而保证整个软件交付过程中的代码质量。

5. 代码托管平台GitLab

代码托管平台有很多,比如国际上的有GitHub、GitLab、Bitbucket、SourceForge等;国内的有Gitee,还有其他的企业代码托管平台。可以根据实际情况选择合适的代码托管平台。

在实际中,大型公司很少愿意将核心代码托管在第三方平台上,以免带来风险。GitLab是一个开源的代码版本控制平台,可以部署在私人服务器上,大大提升了代码的安全性。因此,大型公司基本都会选择GitLab作为私有化部署或者自建代码托管平台。