Appearance
代码审查中的性能优化
性能优化是代码审查中的重要环节。通过在审查阶段识别和解决性能问题,可以避免在生产环境中出现性能瓶颈。本章将详细介绍如何在代码审查中关注性能问题。
性能审查清单
在代码审查中,应重点关注以下性能相关问题:
1. 算法复杂度
检查点:
- 时间复杂度是否合理?
- 空间复杂度是否可控?
- 是否存在不必要的嵌套循环?
常见问题:
- O(n²) 或更高复杂度的算法
- 重复计算相同的结果
- 不必要的递归深度
2. 数据库查询
检查点:
- 是否存在 N+1 查询问题?
- 查询是否使用了适当的索引?
- 是否只选择了必要的字段?
常见问题:
- SELECT * 查询
- 缺少 WHERE 条件的查询
- 在循环中执行查询
3. 内存使用
检查点:
- 是否及时释放不需要的对象?
- 是否存在内存泄漏?
- 是否有大数据结构的不当使用?
4. 网络请求
检查点:
- 是否合理使用缓存?
- 是否合并了多个小请求?
- 是否设置了适当的超时时间?
前端性能优化
1. 渲染性能
避免不必要的重渲染
Bad:
jsx
// 问题:每次父组件更新都会重新渲染子组件
function ParentComponent({ data }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* 每次count变化都会重新渲染ChildComponent */}
<ChildComponent data={data} />
</div>
);
}
function ChildComponent({ data }) {
return <div>{data.items.map(item => <span key={item.id}>{item.name}</span>)}</div>;
}
Good:
jsx
// 解决方案:使用React.memo避免不必要的重渲染
function ParentComponent({ data }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<MemoizedChildComponent data={data} />
</div>
);
}
const MemoizedChildComponent = React.memo(({ data }) => {
return <div>{data.items.map(item => <span key={item.id}>{item.name}</span>)}</div>;
}, (prevProps, nextProps) => {
// 自定义比较函数
return prevProps.data.items === nextProps.data.items;
});
使用虚拟滚动处理大量数据
Bad:
javascript
// 问题:渲染大量DOM元素会导致页面卡顿
function LargeList({ items }) {
return (
<div>
{items.map(item => (
<div key={item.id} className="list-item">
{item.content}
</div>
))}
</div>
);
}
Good:
javascript
// 解决方案:使用虚拟滚动
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style} className="list-item">
{items[index].content}
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}
2. 资源加载优化
代码分割和懒加载
Bad:
javascript
// 问题:一次性加载所有模块
import Dashboard from './Dashboard';
import Reports from './Reports';
import Settings from './Settings';
import Analytics from './Analytics';
function App() {
const [currentView, setCurrentView] = useState('dashboard');
return (
<div>
{currentView === 'dashboard' && <Dashboard />}
{currentView === 'reports' && <Reports />}
{currentView === 'settings' && <Settings />}
{currentView === 'analytics' && <Analytics />}
</div>
);
}
Good:
javascript
// 解决方案:使用动态导入实现代码分割
const Dashboard = lazy(() => import('./Dashboard'));
const Reports = lazy(() => import('./Reports'));
const Settings = lazy(() => import('./Settings'));
const Analytics = lazy(() => import('./Analytics'));
function App() {
const [currentView, setCurrentView] = useState('dashboard');
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
{currentView === 'dashboard' && <Dashboard />}
{currentView === 'reports' && <Reports />}
{currentView === 'settings' && <Settings />}
{currentView === 'analytics' && <Analytics />}
</Suspense>
</div>
);
}
图片优化
Bad:
jsx
// 问题:加载大图未优化
function ProductImage({ src, alt }) {
return <img src={src} alt={alt} />;
}
Good:
jsx
// 解决方案:使用现代图片格式和懒加载
function OptimizedImage({ src, alt, width, height }) {
return (
<picture>
<source srcSet={`${src}.webp`} type="image/webp" />
<source srcSet={`${src}.avif`} type="image/avif" />
<img
src={`${src}.jpg`}
alt={alt}
loading="lazy"
width={width}
height={height}
style={{
maxWidth: '100%',
height: 'auto',
objectFit: 'cover'
}}
/>
</picture>
);
}
3. 事件处理优化
防抖和节流
Bad:
javascript
// 问题:每次按键都触发API调用
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = async (value) => {
const response = await fetch(`/api/search?q=${value}`);
const data = await response.json();
setResults(data.results);
};
return (
<input
type="text"
value={query}
onChange={(e) => {
const value = e.target.value;
setQuery(value);
handleSearch(value); // 每次输入都调用API
}}
/>
);
}
Good:
javascript
import { useMemo } from 'react';
import { debounce } from 'lodash';
function OptimizedSearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// 防抖处理
const debouncedSearch = useMemo(
() => debounce(async (searchQuery) => {
if (searchQuery) {
const response = await fetch(`/api/search?q=${searchQuery}`);
const data = await response.json();
setResults(data.results);
} else {
setResults([]);
}
}, 300),
[]
);
const handleInputChange = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Search..."
/>
);
}
后端性能优化
1. 数据库查询优化
识别和解决 N+1 查询问题
Bad:
javascript
// N+1 查询问题示例
async function getOrdersWithDetails() {
const orders = await db.orders.findMany();
// 对每个订单单独查询客户信息
for (const order of orders) {
order.customer = await db.customers.findUnique({
where: { id: order.customerId }
});
}
return orders;
}
Good:
javascript
// 解决 N+1 查询问题
async function getOrdersWithDetails() {
// 使用 JOIN 查询一次性获取所有数据
return await db.orders.findMany({
include: {
customer: true, // 自动 JOIN 获取客户信息
items: {
include: {
product: true // 还可以包含订单项和产品信息
}
}
}
});
}
// 或者使用 Prisma 的原生查询
async function getOrdersWithDetailsRaw() {
return await db.$queryRaw`
SELECT
o.*,
c.name as customer_name,
c.email as customer_email,
JSON_AGG(
JSON_BUILD_OBJECT(
'product_name', p.name,
'quantity', oi.quantity,
'price', oi.price
)
) as items
FROM orders o
JOIN customers c ON o.customer_id = c.id
LEFT JOIN order_items oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id
GROUP BY o.id, c.name, c.email
`;
}
索引优化
审查要点:
- WHERE 子句中的字段是否有索引?
- ORDER BY 和 GROUP BY 字段是否有索引?
- 复合索引的字段顺序是否合理?
示例:
sql
-- 为常用的查询模式创建复合索引
CREATE INDEX idx_orders_status_date ON orders(status, created_at DESC);
-- 为外键关系创建索引
CREATE INDEX idx_order_customer_id ON orders(customer_id);
-- 为全文搜索创建索引
CREATE INDEX idx_products_name_gin ON products USING gin(to_tsvector('english', name));
2. 缓存策略
应用层缓存
Bad:
javascript
// 问题:每次都查询数据库
class ProductService {
async getProduct(productId) {
// 每次都查询数据库
return await db.products.findUnique({
where: { id: productId }
});
}
}
Good:
javascript
// 解决方案:实现缓存策略
class CachedProductService {
constructor(db, cache) {
this.db = db;
this.cache = cache;
this.cacheTTL = 3600; // 1小时
}
async getProduct(productId) {
const cacheKey = `product:${productId}`;
// 先从缓存获取
let product = await this.cache.get(cacheKey);
if (!product) {
// 缓存未命中,查询数据库
product = await this.db.products.findUnique({
where: { id: productId }
});
if (product) {
// 存入缓存
await this.cache.set(cacheKey, product, this.cacheTTL);
}
}
return product;
}
async updateProduct(productId, data) {
// 更新数据库
const updatedProduct = await this.db.products.update({
where: { id: productId },
data
});
// 更新或删除缓存
const cacheKey = `product:${productId}`;
await this.cache.set(cacheKey, updatedProduct, this.cacheTTL);
return updatedProduct;
}
async deleteProduct(productId) {
// 删除数据库记录
await this.db.products.delete({
where: { id: productId }
});
// 删除缓存
const cacheKey = `product:${productId}`;
await this.cache.del(cacheKey);
}
}
HTTP 缓存
审查要点:
- 是否设置了适当的缓存头?
- 是否使用了 ETags 进行条件请求?
- 静态资源是否有长期缓存?
示例:
javascript
// Express.js 中设置缓存头
app.get('/api/products/:id', async (req, res) => {
const product = await productService.getProduct(req.params.id);
// 设置ETag
const etag = generateETag(product);
res.set('ETag', etag);
// 检查条件请求
if (req.headers['if-none-match'] === etag) {
return res.status(304).send();
}
// 设置缓存控制
res.set('Cache-Control', 'public, max-age=3600'); // 1小时
res.json(product);
});
// 静态资源长期缓存
app.use('/static', express.static('public', {
maxAge: '1y', // 1年缓存
immutable: true // 不可变资源
}));
3. 异步处理
避免阻塞操作
Bad:
javascript
// 问题:同步操作阻塞事件循环
app.post('/api/upload', upload.single('file'), async (req, res) => {
// 同步处理大文件 - 阻塞事件循环
const processedData = heavySyncProcessing(req.file.buffer);
// 同步保存到数据库
await saveToDatabase(processedData);
res.json({ success: true });
});
Good:
javascript
// 解决方案:使用异步处理
app.post('/api/upload', upload.single('file'), async (req, res) => {
// 异步处理文件,不阻塞事件循环
const processPromise = heavyAsyncProcessing(req.file.buffer);
// 立即返回响应
res.json({ accepted: true, jobId: req.file.filename });
// 在后台完成处理
try {
const processedData = await processPromise;
await saveToDatabase(processedData);
// 可以通过WebSocket或队列通知处理完成
notifyJobCompletion(req.file.filename, processedData);
} catch (error) {
console.error('File processing failed:', error);
// 记录错误或重试
}
});
// 或使用消息队列
const Queue = require('bull');
const fileProcessingQueue = new Queue('file processing');
fileProcessingQueue.process(async (job) => {
const { filePath, userId } = job.data;
const processedData = await heavyAsyncProcessing(filePath);
await saveToDatabase(processedData, userId);
return { success: true };
});
app.post('/api/upload', upload.single('file'), async (req, res) => {
const jobId = await fileProcessingQueue.add({
filePath: req.file.path,
userId: req.user.id
});
res.json({ accepted: true, jobId });
});
性能监控和度量
1. 性能指标
前端性能指标
javascript
// 使用 Performance API 监控前端性能
function measurePerformance() {
// 页面加载时间
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('Page Load Time:', perfData.loadEventEnd - perfData.fetchStart);
});
// 资源加载时间
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
}).observe({ entryTypes: ['resource'] });
// 首屏渲染时间
new PerformanceObserver((list) => {
for (const entry of list.getEntriesByName('first-contentful-paint')) {
console.log('FCP:', entry.startTime);
}
}).observe({ entryTypes: ['paint'] });
}
后端性能指标
javascript
// Express 中间件监控API响应时间
function responseTimeMiddleware(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${duration}ms`);
// 可以发送到监控系统
metrics.histogram('response_time', duration, {
method: req.method,
route: req.route?.path || req.path,
statusCode: res.statusCode
});
});
next();
}
app.use(responseTimeMiddleware);
2. 性能审查工具
前端工具
- Lighthouse: 全面的性能审计工具
- Webpack Bundle Analyzer: 分析包大小
- React DevTools Profiler: React组件性能分析
- Chrome DevTools: 网络和性能面板
后端工具
- APM工具: New Relic, DataDog, AppSignal
- 数据库分析: EXPLAIN ANALYZE, Query Profiler
- 系统监控: Prometheus + Grafana
审查实践
1. 性能审查检查清单
在审查代码时,使用以下检查清单:
前端:
- [ ] 是否有不必要的重渲染?
- [ ] 大列表是否使用了虚拟滚动?
- [ ] 图片是否进行了优化?
- [ ] 是否实现了适当的缓存?
- [ ] 事件处理是否使用了防抖/节流?
后端:
- [ ] 是否存在 N+1 查询问题?
- [ ] 数据库查询是否使用了适当索引?
- [ ] 是否实现了合理的缓存策略?
- [ ] 是否有阻塞操作?
- [ ] 是否使用了适当的分页?
2. 性能回归测试
javascript
// 性能回归测试示例
describe('Performance Tests', () => {
test('should render large list efficiently', async () => {
const startTime = performance.now();
// 渲染10000个项目
render(<LargeList items={generateItems(10000)} />);
const endTime = performance.now();
const renderTime = endTime - startTime;
// 渲染时间应该在合理范围内
expect(renderTime).toBeLessThan(100); // 100ms以内
});
test('database query should use index', async () => {
// 使用数据库探查工具
const queryPlan = await db.$queryRaw`EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com'`;
// 验证是否使用了索引
expect(queryPlan).toContain('Index Scan');
});
});
通过在代码审查中关注这些性能优化点,可以及早发现和解决性能问题,确保应用程序具有良好的用户体验和可扩展性。