Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第十二章:最佳实践

本章总结 VSDB 开发中的最佳实践,包括架构设计、性能优化、安全考虑和代码规范。

12.1 架构设计原则

进程隔离原则

graph TD
    A["架构原则"] --> B["进程隔离"]
    A --> C["异步优先"]
    A --> D["错误边界"]
    A --> E["状态持久化"]
    
    B --> B1["数据库操作在 Worker"]
    B --> B2["Worker 崩溃不影响 VSCode"]
    
    C --> C1["IPC 消息异步"]
    C --> C2["UI 不阻塞"]
    
    D --> D1["错误分类"]
    D --> D2["自动恢复"]
    
    E --> E1["连接配置 JSON"]
    E --> E2["密码 SecretStorage"]

关键原则总结

原则实现原因
进程隔离Worker 进程数据库操作异常不影响 VSCode
异步优先Promise + AsyncGeneratorUI 保持响应
错误边界try-catch + 错误分类友好错误提示
状态持久化JSON + SecretStorage配置不丢失

12.2 性能优化

IPC 优化

// 1. 流式传输避免大消息
async *streamQuery(sql: string): AsyncGenerator<StreamChunk> {
  for (let offset = 0; offset < rows.length; offset += 1000) {
    yield { rows: rows.slice(offset, offset + 1000) };
  }
}

// 2. 批量请求合并
async getFullSchema(connectionId: string): Promise<FullSchema> {
  // 并行发送多个 schema 请求
  const [databases, tables, columns] = await Promise.all([
    this.sendRequest({ type: 'schema', payload: { schemaType: 'databases' } }),
    this.sendRequest({ type: 'schema', payload: { schemaType: 'tables' } }),
    this.sendRequest({ type: 'schema', payload: { schemaType: 'columns' } }),
  ]);
  
  return { databases, tables, columns };
}

UI 渲染优化

// 1. 虚拟滚动
import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={400}
  itemCount={rows.length}
  itemSize={35}
>
  {Row}
</FixedSizeList>

// 2. 惰性加载
async getChildren(element: TreeItem): Promise<TreeItem[]> {
  // 仅在展开时加载子节点
  if (element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
    return [];  // 空数组,等待展开时加载
  }
  
  return await this.loadChildren(element);
}

// 3. 缓存策略
class SchemaCache {
  private cache = new Map<string, { data: any; expireAt: number }>();
  private ttl = 60000;  // 1 分钟
  
  get(key: string): any | null {
    const entry = this.cache.get(key);
    if (entry && entry.expireAt > Date.now()) {
      return entry.data;
    }
    this.cache.delete(key);
    return null;
  }
  
  set(key: string, data: any): void {
    this.cache.set(key, {
      data,
      expireAt: Date.now() + this.ttl,
    });
  }
}

数据库查询优化

// 1. 连接池配置(PostgreSQL)
const pool = new Pool({
  max: 5,                    // 限制连接数
  connectionTimeoutMillis: 10000,
  idleTimeoutMillis: 10000,
});

// 2. 查询超时
const queryQueue = new QueryQueue({
  maxConcurrency: 5,
  defaultTimeout: 30000,    // 30 秒超时
});

// 3. 结果限制
const sql = `${baseSql} LIMIT 10000`;  // 防止返回过多数据

12.3 安全考虑

密码存储

// 使用 VSCode SecretStorage API
export class Storage {
  constructor(
    private globalState: vscode.Memento,
    private secrets: vscode.SecretStorage,
  ) {}
  
  async savePassword(connectionId: string, password: string): Promise<void> {
    await this.secrets.store(`vsdb.password.${connectionId}`, password);
  }
  
  async getPassword(connectionId: string): Promise<string | undefined> {
    return await this.secrets.get(`vsdb.password.${connectionId}`);
  }
}

SQL 注入防护

// 使用参数化查询
async query(sql: string, params?: any[]): Promise<QueryResult> {
  // mysql2 和 pg 都支持参数化查询
  await this.connection.execute(sql, params);
}

// 不要拼接 SQL
// 错误示例:
const sql = `SELECT * FROM users WHERE name = '${name}'`;  // 危险!

// 正确示例:
const sql = `SELECT * FROM users WHERE name = ?`;
await this.connection.execute(sql, [name]);

连接字符串处理

// 安全解析 DATABASE_URL
parseConnectionString(url: string): ScannedConnection | null {
  try {
    const parsed = new URL(url);
    
    // 解码用户名和密码
    const username = decodeURIComponent(parsed.username || '');
    const password = decodeURIComponent(parsed.password || '');
    
    // 不暴露原始 URL
    return {
      username,
      password,
      // 不存储原始 URL
    };
  } catch {
    return null;
  }
}

12.4 错误处理规范

错误分类

type ErrorClass = 'connection' | 'syntax' | 'timeout' | 'permission' | 'unknown';

interface ClassifiedError {
  errorClass: ErrorClass;
  retryable: boolean;
  userMessage: string;
}

const ERROR_MESSAGES: Record<ErrorClass, string> = {
  connection: 'Connection failed. Check if database is running.',
  syntax: 'SQL syntax error.',
  timeout: 'Query timed out. Try again or simplify query.',
  permission: 'Access denied. Check credentials.',
  unknown: 'An unexpected error occurred.',
};

错误恢复策略

错误类型策略
connection重试 3 次,提示检查服务
syntax不重试,显示错误位置
timeout提示重试或简化查询
permission不重试,提示检查权限
Worker crash自动重启,恢复连接

用户友好提示

toUserMessage(error: unknown): string {
  const message = this.extractMessage(error);
  
  // 技术错误转换为用户友好提示
  const mappings: Array<{ pattern: RegExp; message: string }> = [
    { pattern: /ECONNREFUSED/, message: 'Cannot connect to database. Is it running?' },
    { pattern: /ETIMEDOUT/, message: 'Connection timed out. Check network.' },
    { pattern: /syntax error/, message: 'SQL syntax error. Check your query.' },
    { pattern: /Access denied/, message: 'Permission denied. Check username/password.' },
  ];
  
  for (const { pattern, message } of mappings) {
    if (pattern.test(message)) {
      return `VSDB: ${message}`;
    }
  }
  
  return `VSDB: ${message}`;
}

12.5 代码规范

TypeScript 规范

// 1. 严格类型
// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
  }
}

// 2. 接口优先
interface DbConnection {
  id: string;
  name: string;
  // ...
}

// 3. 枚举替代字符串常量
type DatabaseType = 'mysql' | 'postgresql' | 'sqlite';

// 4. 明确的返回类型
async query(sql: string): Promise<QueryResult> {
  // ...
}

命名规范

// 文件命名
connectionManager.ts    // Manager 类
envParser.ts            // Parser 类
mysqlDriver.ts          // Driver 类

// 类命名
class ConnectionManager {}
class EnvParser {}
class MySqlDriver {}

// 接口命名
interface DbConnection {}
interface WorkerRequest {}

// 函数命名
async getConnection() {}      // 获取
async addConnection() {}      // 添加
async deleteConnection() {}   // 删除
async executeQuery() {}       // 执行

// 变量命名
const connectionId = '...';   // camelCase
const MAX_CONNECTIONS = 5;    // 常量 UPPER_CASE

文档规范

/**
 * IPC Manager for communicating with the DB Worker process.
 * 
 * Responsibilities:
 * - Spawn and manage Worker process lifecycle
 * - Send WorkerRequest messages and match responses
 * - Handle Worker crashes and auto-restart
 * 
 * @example
 * const ipc = new IpcManager({ workerScriptPath: './worker.js' });
 * ipc.start();
 * const response = await ipc.sendRequest({ type: 'query', ... });
 */
export class IpcManager {
  // ...
}

12.6 测试规范

测试覆盖率要求

// vitest.config.ts
coverage: {
  threshold: {
    lines: 80,
    functions: 80,
    branches: 80,
  },
}

测试命名

describe('IpcManager', () => {
  describe('start', () => {
    it('should start worker process', async () => {
      // ...
    });
    
    it('should throw if worker script not found', async () => {
      // ...
    });
  });
  
  describe('sendRequest', () => {
    it('should return response for valid request', async () => {
      // ...
    });
    
    it('should timeout for long request', async () => {
      // ...
    });
  });
});

测试结构

describe('Module', () => {
  let instance: Module;
  
  beforeEach(() => {
    instance = new Module();
  });
  
  afterEach(() => {
    instance.dispose();
  });
  
  describe('feature', () => {
    it('should do something', async () => {
      // 准备
      const input = '...';
      
      // 执行
      const result = await instance.doSomething(input);
      
      // 验证
      expect(result).toBe('...');
    });
  });
});

12.7 小结

本章总结了 VSDB 开发的最佳实践:

主题关键点
架构设计进程隔离、异步优先、错误边界、状态持久化
性能优化流式传输、虚拟滚动、缓存、并行请求
安全考虑SecretStorage、参数化查询、不暴露原始 URL
错误处理分类、友好提示、恢复策略
代码规范严格类型、接口优先、命名规范
测试规范覆盖率 80%、describe/it 结构

这些最佳实践确保 VSDB 的:

  • 稳定性:Worker 崩溃不影响 VSCode
  • 性能:大数据集流畅渲染
  • 安全性:密码加密、防注入
  • 可维护性:清晰代码结构、完整测试