跳到主要内容前端代码质量保证与最佳实践 | 极客日志JavaScriptNode.js大前端
前端代码质量保证与最佳实践
探讨了前端代码质量保证的重要性及实施方法。通过对比反面教材与正确实践,介绍了使用 ESLint、Prettier、Stylelint 和 TypeScript 进行代码风格与类型检查的工具配置。涵盖了 Jest 和 Playwright 在单元测试与端到端测试中的应用,以及 GitHub Actions 实现自动化代码审查的流程。最后总结了文件组织、命名规范、错误处理及模块化等最佳实践,强调在保证代码质量的同时需平衡开发效率,避免过度工程化。
字节跳动1 浏览 前端代码质量保证与最佳实践
常见误区
代码质量?听起来就像是前端工程师为了显得自己很专业而特意搞的一套复杂流程。你以为随便写几个测试就能保证代码质量?实际上,测试代码比业务代码还多时,维护起来会非常麻烦。
你以为 ESLint 能解决所有问题?ESLint 只能检查代码风格,无法检查逻辑错误。还有那些所谓的代码质量工具,看起来高大上,用起来却各种问题。
为什么你需要这个
- 减少错误:代码质量保证可以帮助你发现和修复代码中的错误,减少生产环境中的问题。
- 提高可维护性:高质量的代码更容易理解和维护,减少后期的维护成本。
- 促进团队协作:统一的代码质量标准可以便于团队成员之间的协作,减少沟通成本。
- 提高开发效率:高质量的代码可以减少调试和修复错误的时间,提高开发效率。
- 提升代码安全性:代码质量保证可以帮助你发现和修复安全漏洞,提升代码的安全性。
反面教材
function getUser(id) {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => { return data; });
}
function getProduct(id){
return fetch(`/api/products/${id}`).then(res=>res.json()).then(data=>data);
}
function calculateTotal(price, quantity) {
const tax = 0.08;
const discount = ;
price * quantity;
}
() {
(weight < ) {
;
} (weight < ) {
;
} {
;
}
}
() {
()
.( response.())
.( .(data));
}
() {
(user. && user. >= ) {
(product. === ) {
;
} (product. === ) {
;
} {
;
}
} (user.) {
(product. === ) {
;
} (product. === ) {
;
} {
;
}
} {
;
}
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
0.1
return
function
getShippingCost
weight
if
10
return
5.99
else
if
20
return
9.99
else
return
14.99
function
fetchData
fetch
'/api/data'
then
response =>
json
then
data =>
console
log
function
getDiscount
user, product
if
isMember
membershipLevel
3
if
category
'electronics'
return
0.2
else
if
category
'clothing'
return
0.15
else
return
0.1
else
if
isMember
if
category
'electronics'
return
0.1
else
if
category
'clothing'
return
0.08
else
return
0.05
else
return
0
- 代码风格不一致,影响可读性
- 未使用的变量,增加代码复杂度
- 硬编码值,难以维护
- 缺少错误处理,容易导致应用崩溃
- 复杂的条件语句,难以理解和维护
正确的做法
代码风格规范
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
plugins: [
'react',
'react-hooks'
],
rules: {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'indent': ['error', 2],
'linebreak-style': ['error', 'unix'],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'no-unused-vars': 'error',
'no-console': 'warn'
}
};
module.exports = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 80,
tabWidth: 2
};
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
代码质量工具
{
"scripts": {
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
}
}
{
"scripts": {
"format": "prettier --write ."
}
}
module.exports = {
extends: 'stylelint-config-standard',
rules: {
'indentation': 2,
'string-quotes': 'single'
}
};
{
"scripts": {
"lint:css": "stylelint '**/*.css'"
}
}
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
{
"scripts": {
"typecheck": "tsc --noEmit"
}
}
测试工具
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.(js|jsx)$': 'babel-jest'
},
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy'
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js']
};
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
test('increments counter when button is clicked', () => {
render(<App />);
const buttonElement = screen.getByText(/increment/i);
const counterElement = screen.getByText(/count:/i);
fireEvent.click(buttonElement);
expect(counterElement).toHaveTextContent('Count: 1');
});
module.exports = {
use: {
baseURL: 'http://localhost:3000',
headless: true,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true
}
};
const { test, expect } = require('@playwright/test');
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/React App/);
});
test('get started link', async ({ page }) => {
await page.goto('/');
await page.click('text=Learn React');
await expect(page).toHaveURL(/.*react.dev/);
});
{
"scripts": {
"test:e2e": "playwright test"
}
}
代码审查
name: Code Quality
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Run ESLint
run: npm run lint
- name: Run Prettier
run: npm run format -- --check
- name: Run TypeScript
run: npm run typecheck
- name: Run tests
run: npm test
// 安装 npm install --save-dev eslint-plugin-security eslint-plugin-sonarjs
// 配置 // .eslintrc.js
module.exports = {
plugins: [
'security',
'sonarjs'
],
rules: {
'security/detect-unsafe-regex': 'error',
'security/detect-buffer-noassert': 'error',
'sonarjs/cognitive-complexity': ['error', 15],
'sonarjs/no-duplicate-string': ['error', { threshold: 3 }]
}
};
最佳实践
function UserProfile() {
return <div>User Profile</div>;
}
const userCount = 10;
const API_BASE_URL = 'https://api.example.com';
function getUserData() {
return fetch('/api/user');
}
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw error;
}
}
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
export async function fetchUsers() {
const response = await fetch('/api/users');
return response.json();
}
export async function fetchProducts() {
const response = await fetch('/api/products');
return response.json();
}
import { fetchUsers, fetchProducts } from './utils/api';
async function loadData() {
const [users, products] = await Promise.all([
fetchUsers(),
fetchProducts()
]);
return { users, products };
}
总结与建议
代码质量保证确实很重要,但我见过太多开发者滥用这个特性,导致开发流程变得过于复杂。
想象一下,当你为了通过代码审查,写了大量的测试代码和注释,结果导致代码量增加了几倍,这真的值得吗?
还有那些过度使用代码质量工具的开发者,为了满足工具的要求,写了大量的冗余代码,结果导致代码变得难以理解和维护。
所以,在进行代码质量保证时,一定要把握好度。不要为了追求代码质量而牺牲开发效率,要根据实际情况来决定代码质量保证的策略。
当然,对于大型项目来说,代码质量保证是必不可少的。但对于小型项目,过度的代码质量保证反而会增加开发成本和维护难度。
最后,记住一句话:代码质量保证的目的是为了提高代码的可靠性和可维护性,而不是为了炫技。如果你的代码质量保证策略导致开发变得更慢或更复杂,那你就失败了。