Skip to content
On this page

代码审查中的性能优化

性能优化是代码审查中的重要环节。通过在审查阶段识别和解决性能问题,可以避免在生产环境中出现性能瓶颈。本章将详细介绍如何在代码审查中关注性能问题。

性能审查清单

在代码审查中,应重点关注以下性能相关问题:

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');
  });
});

通过在代码审查中关注这些性能优化点,可以及早发现和解决性能问题,确保应用程序具有良好的用户体验和可扩展性。