开发最佳实践
项目结构规范
模块划分
packages/main/src/
├── apps/ # 应用层 - 应用初始化和配置
├── core/ # 核心层 - 窗口、托盘等核心功能
├── services/ # 服务层 - 业务逻辑封装
├── handlers/ # 处理器层 - 事件和请求处理
├── ipc-handlers/ # IPC 层 - 进程间通信
├── plugins/ # 插件层 - 插件系统
├── config/ # 配置层 - 配置读取和管理
├── utils/ # 工具层 - 通用工具函数
└── constants/ # 常量层 - 常量定义分层原则
- 单向依赖: 上层可以依赖下层,下层不能依赖上层
- 职责单一: 每个模块只负责一个功能领域
- 高内聚低耦合: 模块内部紧密相关,模块之间松散耦合
应用层 (apps/)
↓
核心层 (core/) → 处理器层 (handlers/)
↓ ↓
服务层 (services/) ← IPC层 (ipc-handlers/)
↓
工具层 (utils/) + 配置层 (config/)
↓
常量层 (constants/)代码规范
命名规范
文件命名
javascript
// ✅ 推荐:kebab-case
machine - service.js;
floating - ball - ipc - handler.js;
update - handler.js;
// ❌ 避免:camelCase 或 PascalCase
machineService.js;
FloatingBallIpcHandler.js;类命名
javascript
// ✅ 推荐:PascalCase
class MachineService {}
class FloatingBallService {}
class UpdateHandler {}
// ❌ 避免:camelCase
class machineService {}
class floatingBallService {}变量和函数命名
javascript
// ✅ 推荐:camelCase
const machineInfo = {};
function getMachineInfo() {}
// 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_TIMEOUT = 30000;
// 私有属性/方法:下划线前缀
class MyClass {
_privateMethod() {}
_privateProperty = null;
}代码组织
模块导出
javascript
// ✅ 推荐:导出类或对象
class MachineService {
static async initialize() {}
static getMachineInfo() {}
}
module.exports = MachineService;
// ✅ 或导出多个函数
module.exports = {
initialize,
getMachineInfo,
};
// ❌ 避免:混合导出
module.exports = MachineService;
module.exports.extraFunction = () => {};文件结构
javascript
// 1. 依赖导入
const { app } = require('electron');
const logger = require('../utils/logger');
// 2. 常量定义
const MAX_RETRY = 3;
const TIMEOUT = 5000;
// 3. 类或函数定义
class MyService {
// 3.1 静态属性
static instance = null;
// 3.2 实例属性
constructor() {
this.data = {};
}
// 3.3 静态方法
static getInstance() {
if (!this.instance) {
this.instance = new MyService();
}
return this.instance;
}
// 3.4 公共方法
publicMethod() {}
// 3.5 私有方法
_privateMethod() {}
}
// 4. 模块导出
module.exports = MyService;异步编程
使用 async/await
javascript
// ✅ 推荐:async/await
async function loadData() {
try {
const data = await fetchData();
const processed = await processData(data);
return processed;
} catch (error) {
logger.error('加载数据失败:', error);
throw error;
}
}
// ❌ 避免:Promise 链
function loadData() {
return fetchData()
.then((data) => processData(data))
.then((processed) => {
return processed;
})
.catch((error) => {
logger.error('加载数据失败:', error);
throw error;
});
}并行执行
javascript
// ✅ 推荐:使用 Promise.all
async function initializeServices() {
await Promise.all([
MachineService.initialize(),
PluginService.initialize(),
LoggerClean.getInstance().initialize(),
]);
}
// ❌ 避免:串行执行(除非有依赖关系)
async function initializeServices() {
await MachineService.initialize();
await PluginService.initialize();
await LoggerClean.getInstance().initialize();
}错误处理
javascript
// ✅ 推荐:细粒度错误处理
async function doSomething() {
try {
const result = await riskyOperation();
return result;
} catch (error) {
if (error.code === 'ENOENT') {
logger.warn('文件不存在,使用默认值');
return defaultValue;
}
logger.error('操作失败:', error);
throw new Error(`操作失败: ${error.message}`);
}
}
// ❌ 避免:吞掉所有错误
async function doSomething() {
try {
return await riskyOperation();
} catch (error) {
return null; // 错误被隐藏了
}
}IPC 通信
主进程注册
javascript
// ✅ 推荐:统一管理 IPC 处理器
// ipc-handlers/my-handler.js
const { ipcMain } = require('electron');
const logger = require('../utils/logger');
function registerMyHandlers() {
// 使用 handle 处理需要返回值的请求
ipcMain.handle('my-action', async (event, params) => {
try {
logger.info('收到请求:', params);
const result = await doSomething(params);
return { success: true, data: result };
} catch (error) {
logger.error('处理失败:', error);
return { success: false, error: error.message };
}
});
// 使用 on 处理不需要返回值的事件
ipcMain.on('my-event', (event, data) => {
logger.info('收到事件:', data);
handleEvent(data);
});
logger.info('MyHandler IPC 注册成功');
}
module.exports = { registerMyHandlers };渲染进程调用
javascript
// ✅ 推荐:使用预加载脚本暴露的 API
// 渲染进程
const result = await window.electron.invoke('my-action', params);
window.electron.send('my-event', data);
window.electron.on('notification', (data) => {
console.log('收到通知:', data);
});
// ❌ 避免:直接使用 ipcRenderer(不安全)
const { ipcRenderer } = require('electron');
ipcRenderer.send('my-event', data);预加载脚本
javascript
// ✅ 推荐:精确控制暴露的 API
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
// 双向通信(有返回值)
invoke: (channel, data) => {
const validChannels = ['my-action', 'another-action'];
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, data);
}
},
// 单向通信(无返回值)
send: (channel, data) => {
const validChannels = ['my-event'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
// 监听事件
on: (channel, callback) => {
const validChannels = ['notification'];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => callback(...args));
}
},
});日志记录
日志级别
javascript
// ✅ 推荐:使用合适的日志级别
logger.info('正常信息'); // 一般信息
logger.warn('警告信息'); // 警告,不影响运行
logger.error('错误信息'); // 错误,需要关注
logger.debug('调试信息'); // 调试信息(生产环境不输出)
// ❌ 避免:全部使用 console.log
console.log('这是什么级别的日志?');结构化日志
javascript
// ✅ 推荐:提供上下文信息
logger.info('用户登录', {
userId: 123,
username: 'user@example.com',
ip: '192.168.1.1',
timestamp: new Date().toISOString(),
});
logger.error('API 请求失败', {
url: 'https://api.example.com/data',
method: 'POST',
status: 500,
error: error.message,
stack: error.stack,
});
// ❌ 避免:信息不完整
logger.info('用户登录');
logger.error('请求失败', error);敏感信息保护
javascript
// ✅ 推荐:过滤敏感信息
function sanitizeData(data) {
const sanitized = { ...data };
delete sanitized.password;
delete sanitized.token;
delete sanitized.creditCard;
return sanitized;
}
logger.info('用户数据', sanitizeData(userData));
// ❌ 避免:记录敏感信息
logger.info('用户数据', userData); // 可能包含密码错误处理
自定义错误类
javascript
// ✅ 推荐:定义业务错误类
class AppError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'AppError';
this.code = code;
this.details = details;
}
}
class NetworkError extends AppError {
constructor(message, details) {
super(message, 'NETWORK_ERROR', details);
this.name = 'NetworkError';
}
}
// 使用
throw new NetworkError('API 请求失败', {
url: 'https://api.example.com',
status: 500,
});全局错误捕获
javascript
// ✅ 推荐:捕获未处理的异常
process.on('uncaughtException', (error) => {
logger.error('未捕获的异常:', error);
// 记录错误但不退出
// 可选:重启应用或提示用户
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('未处理的 Promise 拒绝:', reason);
});
// 渲染进程错误
window.addEventListener('error', (event) => {
logger.error('渲染进程错误:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
logger.error('未处理的 Promise 拒绝:', event.reason);
});性能优化
懒加载
javascript
// ✅ 推荐:按需加载大模块
class PluginService {
static _heavyModule = null;
static async getHeavyModule() {
if (!this._heavyModule) {
this._heavyModule = require('./heavy-module');
await this._heavyModule.initialize();
}
return this._heavyModule;
}
}
// ❌ 避免:启动时加载所有模块
const heavyModule = require('./heavy-module'); // 立即加载资源清理
javascript
// ✅ 推荐:及时清理资源
class MyService {
constructor() {
this.timers = [];
this.listeners = [];
}
startPolling() {
const timer = setInterval(() => {
this.poll();
}, 5000);
this.timers.push(timer);
}
destroy() {
// 清理定时器
this.timers.forEach((timer) => clearInterval(timer));
this.timers = [];
// 清理事件监听
this.listeners.forEach(({ target, event, handler }) => {
target.removeListener(event, handler);
});
this.listeners = [];
}
}
// 应用退出时清理
app.on('before-quit', () => {
myService.destroy();
});缓存策略
javascript
// ✅ 推荐:使用缓存减少重复计算
class DataService {
static cache = new Map();
static CACHE_TTL = 5 * 60 * 1000; // 5 分钟
static async getData(key) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.data;
}
const data = await this.fetchData(key);
this.cache.set(key, {
data,
timestamp: Date.now(),
});
return data;
}
static clearCache() {
this.cache.clear();
}
}安全实践
上下文隔离
javascript
// ✅ 推荐:启用上下文隔离
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true, // 隔离上下文
nodeIntegration: false, // 禁用 Node.js 集成
sandbox: true, // 启用沙箱
preload: path.join(__dirname, 'preload.js'),
},
});
// ❌ 避免:直接暴露 Node.js(不安全)
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true, // 危险!
contextIsolation: false, // 危险!
},
});输入验证
javascript
// ✅ 推荐:验证所有外部输入
ipcMain.handle('save-file', async (event, { path, content }) => {
// 验证路径
if (!path || typeof path !== 'string') {
throw new Error('无效的文件路径');
}
// 防止路径遍历攻击
const normalizedPath = path.normalize(path);
const basePath = app.getPath('userData');
if (!normalizedPath.startsWith(basePath)) {
throw new Error('不允许访问该路径');
}
// 验证内容大小
if (content.length > 10 * 1024 * 1024) {
// 10MB
throw new Error('文件内容过大');
}
await fs.writeFile(normalizedPath, content);
});权限控制
javascript
// ✅ 推荐:最小权限原则
const session = require('electron').session;
session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
const allowedPermissions = ['notifications', 'clipboard-read'];
if (allowedPermissions.includes(permission)) {
callback(true);
} else {
logger.warn('拒绝权限请求:', permission);
callback(false);
}
});测试实践
单元测试
javascript
// ✅ 推荐:为核心逻辑编写测试
// tests/services/machine-service.test.js
const MachineService = require('../../src/services/machine-service');
describe('MachineService', () => {
test('应该返回机器信息', async () => {
await MachineService.initialize();
const info = MachineService.getMachineInfo();
expect(info).toHaveProperty('machineId');
expect(info).toHaveProperty('cpu');
expect(info).toHaveProperty('memory');
});
test('应该处理初始化失败', async () => {
// 模拟失败场景
jest.spyOn(MachineService, '_collectInfo').mockRejectedValue(new Error('Failed'));
await expect(MachineService.initialize()).rejects.toThrow('Failed');
});
});E2E 测试
javascript
// ✅ 推荐:使用 Spectron 或 Playwright
const { Application } = require('spectron');
describe('应用启动', () => {
let app;
beforeEach(async () => {
app = new Application({
path: electronPath,
args: [path.join(__dirname, '..')],
});
await app.start();
});
afterEach(async () => {
if (app && app.isRunning()) {
await app.stop();
}
});
test('应该显示主窗口', async () => {
const count = await app.client.getWindowCount();
expect(count).toBe(1);
const title = await app.client.getTitle();
expect(title).toBe('Vue3-Simple-Electron');
});
});文档规范
代码注释
javascript
/**
* 获取机器信息
*
* @description 收集系统的 CPU、内存、硬盘等信息
* @returns {Promise<MachineInfo>} 机器信息对象
* @throws {Error} 当收集信息失败时抛出异常
*
* @example
* const info = await MachineService.getMachineInfo();
* console.log(info.cpu.model);
*/
static async getMachineInfo() {
// 实现...
}README 编写
markdown
# 模块名称
## 功能说明
简要描述模块的功能和用途。
## 使用方法
```javascript
// 示例代码
const result = await MyModule.doSomething();
```API 文档
doSomething(params)
参数:
params.name(string): 名称params.value(number): 数值
返回值:
Promise<Result>示例:
javascriptawait MyModule.doSomething({ name: 'test', value: 42 });
注意事项
- 使用前需要先初始化
- 线程不安全,避免并发调用