代码质量面试题
基础题
参考答案:
ESLint 与 Prettier 是前端代码质量体系中两个不同定位的工具:
- ESLint:静态代码分析工具,核心职责是发现潜在错误、约束编码风格、保证代码质量。它可以检测未使用变量、可能的空指针、不可达代码、变量提升误用等问题,也能通过自定义规则约束团队规范。
- Prettier:代码格式化工具,核心职责是统一代码外观(缩进、换行、引号、分号等)。它不关注代码逻辑正确性,只关心“代码看起来一致”。
协作方式:ESLint 负责“对不对”,Prettier 负责“好不好看”。两者规则可能冲突(如 max-len 与 Prettier 换行),通常用 eslint-config-prettier 关闭 ESLint 中与格式冲突的规则,再用 eslint-plugin-prettier 把 Prettier 作为一条 ESLint 规则运行。
npm install -D eslint prettier eslint-config-prettier eslint-plugin-prettier
评分维度:
- 概念区分准确(40%)
- 能举例说明各自职责(30%)
- 知道如何配合与解决冲突(30%)
参考答案:
- Husky:Git 钩子管理工具,可在
pre-commit、commit-msg、pre-push等时机执行脚本。它把质量检查左移到提交阶段,避免不符合规范的代码进入仓库。 - lint-staged:只对当前
git add暂存区的文件运行指定命令。它解决了“每次提交都 lint 整个项目”的性能问题,让提交前检查聚焦在改动文件上。
典型配合:在 pre-commit 钩子中运行 lint-staged,对改动的 .js/.ts/.vue 等文件执行 ESLint + Prettier;在 commit-msg 钩子中校验提交信息是否符合 Conventional Commits。
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
评分维度:
- 能解释 Husky 的 Git 钩子作用(30%)
- 能解释 lint-staged 的增量检查作用(30%)
- 能说明两者配合的典型场景(40%)
参考答案:
| 维度 | 单元测试 | E2E 测试 |
|---|---|---|
| 测试对象 | 最小可测单元(函数、组件、Hook) | 完整用户流程 |
| 依赖 | 大量 Mock,隔离外部依赖 | 模拟真实浏览器/设备环境 |
| 速度 | 毫秒级,可频繁运行 | 秒级到分钟级 |
| 定位能力 | 失败时定位精准 | 失败时需排查链路 |
| 工具 | Jest、Vitest、Mocha | Cypress、Playwright、Selenium |
举例:一个表单提交功能,单元测试验证 validatePhone(phone) 对各类手机号的校验结果;E2E 测试则模拟用户输入、点击提交、看到成功提示的完整流程。
两者应形成测试金字塔:大量单元测试打底,适量集成测试衔接,少量 E2E 覆盖核心路径。
评分维度:
- 能清晰区分测试范围(30%)
- 能对比速度、成本、定位能力(30%)
- 能举例说明并提到测试金字塔(40%)
参考答案:
Mock 是用可控的“假对象”替代真实依赖的技术。它让测试聚焦在被测单元本身,而不受外部环境影响。
需要 Mock 的典型原因:
- 隔离性:避免后端接口、数据库、第三方服务不可用导致测试失败。
- 确定性:模拟边界值、异常状态、网络超时等难以在真实环境复现的场景。
- 速度:真实 HTTP 请求慢,Mock 后测试可在毫秒级完成。
- 可重复性:每次运行环境一致,避免 flaky test。
示例(Jest):
jest.mock('./api', () => ({
fetchUser: () => Promise.resolve({ id: 1, name: 'Alice' })
}));
Mock 应遵循“只 Mock 跨越边界的外部依赖”,不要 Mock 被测函数的内部实现,否则测试会失去意义。
评分维度:
- 能解释 Mock 概念(30%)
- 能说明 3 个以上使用原因(40%)
- 能写出示例并知道使用边界(30%)
参考答案:
Conventional Commits 通过规范提交信息自动生成 changelog 和版本号。常见 type:
- feat:新功能,对应 minor 版本升级。
- fix:Bug 修复,对应 patch 版本升级。
- docs:文档变更,不影响产物。
- style:代码格式修改,如空格、分号,不影响逻辑。
- refactor:重构,既不新增功能也不修复 Bug。
- perf:性能优化。
- test:测试相关变更。
- chore:构建、工具、依赖等杂项。
- ci:CI/CD 配置变更。
- BREAKING CHANGE:破坏性变更,对应 major 版本升级。
示例:feat(auth): add OAuth2 login。
评分维度:
- 能说出 5 个以上 type(40%)
- 能区分 feat/fix/BREAKING CHANGE 与版本号关系(30%)
- 能写出规范提交信息示例(30%)
进阶题
参考答案:
避免冲突的核心思路是:Prettier 管格式,ESLint 管质量;关闭 ESLint 中与格式冲突的规则,并让 Prettier 以 ESLint 规则形式运行。
步骤:
- 安装依赖:
npm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier - 在 ESLint 配置中 extends 最后加入
'prettier',关闭冲突规则:// eslint.config.js (Flat Config) import js from '@eslint/js'; import prettier from 'eslint-config-prettier'; import prettierPlugin from 'eslint-plugin-prettier'; export default [ js.configs.recommended, { plugins: { prettier: prettierPlugin }, rules: { 'prettier/prettier': 'error', // 其他业务规则 } }, prettier // 放最后,关闭与 Prettier 冲突的规则 ]; - 配置
.prettierrc统一格式规则:{ "semi": true, "singleQuote": true, "trailingComma": "es5" } - 在
lint-staged中先prettier --write再eslint --fix,避免循环修复:{ "lint-staged": { "*.{js,ts,tsx}": ["prettier --write", "eslint --fix"] } }
评分维度:
- 能说明职责分离思路(25%)
- 能写出关键 ESLint 配置(35%)
- 能说明执行顺序和 lint-staged 配合(25%)
- 了解 Flat Config 等新趋势(15%)
参考答案:
不是。覆盖率只衡量代码被执行的程度,不衡量测试是否有效。
覆盖率无法保证的情况:
- 测试没有断言,仅调用函数。
- 边界条件、异常分支未覆盖。
- 并发、时序、兼容性场景未验证。
- 业务逻辑本身理解错误。
高质量测试应关注:
- 核心路径与边界条件。
- 输入输出的等价类划分。
- 异常与降级路径。
- 行为是否符合需求,而非盲目追求数字。
覆盖率是有用指标,但应作为“健康度参考”,而不是唯一目标。建议设定合理阈值(如核心模块 80%),并重点审查未覆盖代码的合理性。
评分维度:
- 能明确回答不是(20%)
- 能解释覆盖率的局限(40%)
- 能提出高质量测试的标准(40%)
参考答案:
TDD(Test-Driven Development,测试驱动开发)是一种“先写测试、再写代码”的开发方式,核心循环为 红-绿-重构:
- 红:写一个失败的测试,明确当前要实现的行为。
- 绿:编写最小代码让测试通过,不追求优雅。
- 重构:在测试保护下优化代码结构,保持测试通过。
价值:
- 提前明确需求接口,减少返工。
- 每个功能都有测试守护,便于后续重构。
- 设计更模块化,因为难测试的代码往往设计也不好。
示例:先写 add(1, 2) === 3 的测试,再实现 add 函数。
TDD 适合业务逻辑清晰、输入输出明确的模块;对 UI 变化频繁、需求不确定的探索性开发,可灵活运用而非教条执行。
评分维度:
- 能解释 TDD 概念(30%)
- 能说清红绿重构循环(40%)
- 能说明价值与适用边界(30%)
参考答案:
一次高质量的 Code Review 应从多个维度检查:
- 正确性:是否满足需求,边界与异常是否处理。
- 可读性:命名、注释、结构是否清晰,是否易于理解。
- 可维护性:是否遵循单一职责,是否存在重复代码。
- 可测试性:逻辑是否解耦,是否方便写测试。
- 性能:是否存在明显低效实现,如重复计算、大数据量遍历。
- 安全性:是否有 XSS、CSRF、敏感信息泄露风险。
- 兼容性:对不同浏览器、设备、降级场景是否考虑。
- 规范符合度:是否遵循团队的 ESLint、Git、架构规范。
Review 时应注意区分“阻塞性问题”与“建议性意见”,避免吹毛求疵;同时给予正面反馈,营造学习型文化。
评分维度:
- 能覆盖 5 个以上维度(40%)
- 能说明 Review 的目的与价值(30%)
- 提到阻塞性问题与建设性反馈的平衡(30%)
参考答案:
常见 Git 工作流:
- Git Flow:拥有
main、develop、feature、release、hotfix分支,适合有明确版本发布节奏的项目。 - GitHub Flow:基于
main分支,功能通过短期 feature branch + PR 合并,适合持续部署、轻量团队。 - GitLab Flow:结合 issue 与 MR,支持环境分支(如
pre-production、production),适合多环境发布。 - Trunk-Based Development:所有开发者直接向主干提交或短期分支快速合并,配合功能开关,适合高频发布和大规模团队。
选择依据:
- 发布频率:高频发布选 GitHub Flow / Trunk-Based。
- 版本管理:需要版本号选 Git Flow。
- 团队规模与 CI 成熟度:大团队、强 CI 更适合 Trunk-Based。
评分维度:
- 能说出 3 种以上工作流(40%)
- 能对比各自特点(30%)
- 能给出选型依据(30%)
高级题
参考答案:
质量门禁应贯穿软件生命周期,形成“不合格代码无法前进”的自动化屏障:
- 提交前:Husky + lint-staged 运行 ESLint/Prettier,拦截格式与基本质量问题。
- 提交信息:commitlint 校验 Conventional Commits。
- PR 阶段:CI 运行单元测试、类型检查、构建检查;Code Review 至少一名核心成员通过。
- 合并前:测试覆盖率门禁、SonarQube 质量阈值、依赖安全扫描(
npm audit/ Snyk)。 - 构建阶段:产物体积预算、构建失败告警。
- 部署前:灰度环境验证、E2E 回归通过。
- 上线后:错误监控、性能监控、业务指标异常告警。
关键点:门禁规则要适度,过严会降低开发效率,过松则失去意义;应根据团队成熟度动态调整。
评分维度:
- 能覆盖 4 个以上阶段(40%)
- 能说明每个阶段的具体手段(30%)
- 能提到动态调整与平衡(30%)
参考答案:
提高代码质量意识不能仅靠制度,更要靠文化与工具:
- 建立清晰规范:编码规范、Review 指南、架构原则文档化,让大家知道“好代码长什么样”。
- 工具化检查:ESLint、Prettier、类型检查、CI 门禁,把规范变成自动约束,降低人为遗漏。
- 营造 Code Review 文化:Review 不是挑刺,而是互相学习;资深成员带头示范。
- 定期技术分享:分享 bad case、重构案例、性能/安全专题,让团队看到质量问题的真实影响。
- 质量指标可视化:在团队看板展示覆盖率、Bug 率、线上故障数,形成正向激励。
- 预留重构时间:把技术债清理纳入迭代计划,而不是临时救火。
- 领导以身作则:负责人主动参与 Review、接受反馈、承认不足。
评分维度:
- 能提出 4 个以上可落地方法(40%)
- 能说明文化与工具结合(30%)
- 能结合实际场景说明(30%)
参考答案:
技术债务是为了快速交付而有意或无意做出的技术妥协,长期会带来维护成本上升、开发效率下降、稳定性风险增加。
管理方式:
- 识别与记录:建立技术债清单,说明位置、原因、影响、预估修复成本。
- 分类分级:按影响程度分为高/中/低,优先偿还架构债和安全债。
- 量化影响:用数据说明债务导致的 Bug 率、上线时间、重构成本。
- 纳入迭代:每个迭代预留 10%–20% 时间用于还债。
- 先止血再重构:补充测试和监控后再动核心代码,避免引入回归。
- 防止新增债务:通过 Code Review、Lint、架构评审拦截低质量代码。
- 定期复盘:每月/每季度回顾技术债清单,跟踪偿还进度。
评分维度:
- 能解释技术债务概念(30%)
- 能说出 4 个以上管理方法(40%)
- 能提到量化与防止新增债务(30%)
参考答案:
| 维度 | Cypress | Playwright |
|---|---|---|
| 浏览器支持 | 主要 Chromium/Firefox/WebKit 支持有限 | Chromium/Firefox/WebKit 原生支持 |
| 并发能力 | 单线程限制,并行需付费 Dashboard | 原生多 worker 并行,速度快 |
| 多标签页/iframe | 较弱 | 强 |
| 调试体验 | 时间旅行、截图、视频回放优秀 | 调试能力持续提升,Trace Viewer 好用 |
| 生态 | 成熟,插件丰富 | 增长快,微软背书 |
| 语言 | 主要 JS/TS | JS/TS/Python/Java/.NET |
选型建议:
- 团队以 JS/TS 为主、重视调试体验、测试场景相对简单 → Cypress。
- 需要跨浏览器、多标签页、高并行、多语言支持 → Playwright。
评分维度:
- 能分别说出优缺点(40%)
- 能从至少 3 个维度对比(30%)
- 能给出选型建议(30%)
参考答案:
代码质量与交付速度不是对立关系,好的质量往往能提升长期交付速度。平衡策略:
- 质量左移:在需求评审、设计阶段就识别风险,减少后期返工。
- 自动化质量门禁:用 CI 自动跑 lint、测试、类型检查,减少人工审查时间。
- 核心路径严格、边缘路径灵活:关键业务和公共模块必须高质量;实验性需求允许技术债,但要有偿还计划。
- 小步快跑、持续重构:避免一次性大重构,把改进分散到日常迭代。
- 避免过度工程:不为未来不确定的需求做复杂抽象。
- 度量和反馈:用 Bug 率、返工率、上线时长等指标评估是否失衡。
评分维度:
- 能说明两者不是对立关系(20%)
- 能给出 4 个以上平衡方法(50%)
- 能结合实际场景说明(30%)
补充题
参考答案:
BDD(Behavior-Driven Development,行为驱动开发)是一种以业务行为为核心的开发方法。它要求用自然语言(Given-When-Then)描述用户场景,促进开发、测试、产品、业务人员使用统一语言。
与 TDD 的关系:
- TDD 关注“代码是否正确”,从技术测试出发。
- BDD 关注“业务行为是否被满足”,从用户场景出发。
示例(Cucumber):
Feature: 用户登录
Scenario: 使用正确账号密码登录
Given 用户已注册
When 用户输入正确账号密码并提交
Then 用户应进入首页
BDD 适合需求复杂、跨角色协作频繁的项目,能帮助团队对齐理解。
评分维度:
- 能解释 BDD 概念(40%)
- 能与 TDD 区分(30%)
- 能写出 Given-When-Then 示例(30%)
参考答案:
Flaky test 是指结果不稳定、时而通过时而失败的测试。常见原因包括异步等待、外部依赖、共享状态、时序问题。
处理方法:
- 避免固定 sleep:使用
waitFor、元素可见性、网络空闲等确定性等待条件。 - 隔离测试数据:每个测试用独立账号、独立数据,避免互相影响。
- Mock 不稳定外部依赖:接口、第三方服务、时间函数用 Mock 替代。
- 重试机制:CI 中配置失败重试,但不应作为根本解决方案。
- 检查异步操作:确保 async/await、Promise 处理正确。
- 减少全局状态污染:测试间清理 localStorage、DOM、事件监听。
- 根因分析:对高频 flaky 用日志、截图、视频定位,彻底修复而非简单重试。
评分维度:
- 能解释 flaky 测试的危害(30%)
- 能给出 4 种以上解决方法(50%)
- 能强调根因分析而非简单重试(20%)
参考答案:
Mutation Testing(变异测试)是一种评估测试有效性的方法。它通过自动修改源代码中的小部分逻辑(如把 > 改成 <、true 改成 false、删除方法调用、修正常量值),生成大量“变异体”,然后运行测试。
如果测试能发现变异体(即测试失败),称为“变异体被杀死(killed)”,说明测试有效;如果变异体“存活(survived)”,说明测试用例存在盲区。
价值:
- 比单纯覆盖率更能反映测试质量。
- 发现测试断言不足、边界缺失、弱断言等问题。
- 推动开发者写出更高质量的测试。
工具:Stryker(JS/TS)、PIT(Java)、MutPy(Python)。
注意:变异测试运行成本高,通常用于核心模块、关键算法或安全敏感代码,而不是全量跑。建议结合 CI 定期执行或作为发布前的质量门禁之一。
评分维度:
- 能解释变异测试概念(35%)
- 能说明“变异体存活/被杀死”的含义(30%)
- 能说明价值与使用边界(25%)
- 能列举工具(10%)
参考答案:
合理的测试策略应满足“分层合理、覆盖关键、运行稳定、成本可控”。评估维度:
- 测试金字塔结构:单元测试是否占主体,E2E 是否聚焦核心路径。
- 核心功能覆盖:关键业务逻辑、公共组件、工具函数是否有测试守护。
- 关键用户路径覆盖:登录、下单、支付等核心流程是否有 E2E。
- 运行速度与稳定性:CI 反馈是否在可接受范围,flaky 比例是否低。
- 覆盖率趋势:是否合理且持续改进,而非盲目追求 100%。
- 可维护性:测试是否易于理解、修改,Mock 是否适度。
- 与发布流程集成:失败是否阻止合并/发布。
评分维度:
- 能说出 4 个以上评估维度(40%)
- 能结合测试金字塔说明(30%)
- 能提出改进方向(30%)
参考答案:
好的代码不仅是“能跑”,还要“好懂、好改、好测”。主要特征:
- 可读性强:命名清晰、结构直观、注释解释“为什么”而非“做什么”。
- 职责单一:每个函数/模块只做一件事,低耦合高内聚。
- 易于测试:输入输出明确,依赖可注入,避免隐式全局状态。
- 可扩展性:通过接口、配置、插件机制支持未来变化,而非硬编码。
- 错误处理完善:边界条件、异常路径、用户输入校验都有考虑。
- 性能合理:不提前优化,但避免明显的低效实现。
- 符合规范:遵循团队 ESLint、架构约定和代码风格。
- 可观测性:关键路径有日志、埋点或监控,便于排查问题。
评分维度:
- 能说出 4 个以上特征(40%)
- 能结合实际说明(30%)
- 能体现系统性质量思维(30%)