Files
jiebanke/docs/开发规范和最佳实践.md

20 KiB
Raw Blame History

解班客项目开发规范和最佳实践

📋 文档概述

本文档制定了解班客项目的开发规范、编码标准和最佳实践,旨在提高代码质量、团队协作效率和项目可维护性。

文档目标

  • 建立统一的代码规范和编码标准
  • 规范开发流程和团队协作方式
  • 提高代码质量和可维护性
  • 确保项目的长期稳定发展

🎯 开发原则

核心原则

  1. 可读性优先:代码应该易于理解和维护
  2. 一致性:遵循统一的编码风格和命名规范
  3. 简洁性:避免过度设计,保持代码简洁
  4. 可测试性:编写易于测试的代码
  5. 安全性:始终考虑安全因素
  6. 性能意识:在保证功能的前提下优化性能

SOLID原则

  • S - 单一职责原则Single Responsibility Principle
  • O - 开闭原则Open/Closed Principle
  • L - 里氏替换原则Liskov Substitution Principle
  • I - 接口隔离原则Interface Segregation Principle
  • D - 依赖倒置原则Dependency Inversion Principle

📁 项目结构规范

后端项目结构

backend/
├── src/
│   ├── controllers/        # 控制器层
│   │   ├── admin/         # 管理员控制器
│   │   └── user/          # 用户控制器
│   ├── models/            # 数据模型层
│   ├── routes/            # 路由层
│   │   ├── admin/         # 管理员路由
│   │   └── user/          # 用户路由
│   ├── middleware/        # 中间件
│   ├── services/          # 业务逻辑层
│   ├── utils/             # 工具函数
│   ├── config/            # 配置文件
│   └── validators/        # 数据验证
├── tests/                 # 测试文件
│   ├── unit/             # 单元测试
│   ├── integration/      # 集成测试
│   └── fixtures/         # 测试数据
├── docs/                 # API文档
├── scripts/              # 脚本文件
└── package.json

前端项目结构

frontend/
├── src/
│   ├── components/        # 公共组件
│   │   ├── common/       # 通用组件
│   │   └── business/     # 业务组件
│   ├── views/            # 页面组件
│   │   ├── admin/        # 管理员页面
│   │   └── user/         # 用户页面
│   ├── stores/           # Pinia状态管理
│   ├── composables/      # 组合式函数
│   ├── utils/            # 工具函数
│   ├── api/              # API接口
│   ├── router/           # 路由配置
│   ├── assets/           # 静态资源
│   └── styles/           # 样式文件
├── public/               # 公共资源
├── tests/                # 测试文件
└── package.json

frontend/ ├── src/ │ ├── components/ # 公共组件 │ │ ├── common/ # 通用组件 │ │ └── business/ # 业务组件 │ ├── views/ # 页面视图 │ │ ├── user/ # 用户相关页面 │ │ ├── animal/ # 动物相关页面 │ │ └── admin/ # 管理页面 │ ├── stores/ # 状态管理 │ ├── router/ # 路由配置 │ ├── utils/ # 工具函数 │ ├── api/ # API接口 │ ├── assets/ # 静态资源 │ │ ├── images/ # 图片资源 │ │ ├── styles/ # 样式文件 │ │ └── icons/ # 图标资源 │ └── composables/ # 组合式函数 ├── public/ # 公共文件 ├── tests/ # 测试文件 └── package.json


## 🔤 命名规范

### 文件和目录命名
- **文件名**: 使用小写字母和连字符 (`kebab-case`)

user-management.js animal-list.vue UserManagement.js animalList.vue


- **目录名**: 使用小写字母和连字符

user-management/ api-docs/ UserManagement/ apiDocs/


### 变量和函数命名

#### JavaScript/Node.js
- **变量**: 使用驼峰命名法 (`camelCase`)
- **常量**: 使用大写字母和下划线 (`UPPER_SNAKE_CASE`)
- **函数**: 使用驼峰命名法,动词开头
- **类**: 使用帕斯卡命名法 (`PascalCase`)

```javascript
// ✅ 正确示例
const userName = 'john';
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';

function getUserById(id) { }
function createAnimalRecord(data) { }

class UserService { }
class AnimalController { }

// ❌ 错误示例
const user_name = 'john';
const maxretrycount = 3;
function GetUserById(id) { }
class userService { }

Vue.js组件

  • 组件名: 使用帕斯卡命名法
  • Props: 使用驼峰命名法
  • 事件: 使用kebab-case
<!--  正确示例 -->
<template>
  <UserProfile 
    :user-data="userData" 
    @user-updated="handleUserUpdate"
  />
</template>

<script>
export default {
  name: 'UserProfile',
  props: {
    userData: Object,
    isEditable: Boolean
  },
  emits: ['user-updated', 'profile-changed']
}
</script>

数据库命名

  • 表名: 使用复数形式,下划线分隔
  • 字段名: 使用下划线分隔
  • 索引名: 使用 idx_ 前缀
  • 外键名: 使用 fk_ 前缀
-- ✅ 正确示例
CREATE TABLE users (
    id INT PRIMARY KEY,
    user_name VARCHAR(50),
    email_address VARCHAR(100),
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email_address);
ALTER TABLE adoptions ADD CONSTRAINT fk_adoptions_user_id 
    FOREIGN KEY (user_id) REFERENCES users(id);

💻 代码风格规范

JavaScript/Node.js代码规范

基本格式

// ✅ 使用2个空格缩进
if (condition) {
  doSomething();
}

// ✅ 使用单引号
const message = 'Hello World';

// ✅ 对象和数组的格式
const user = {
  name: 'John',
  age: 30,
  email: 'john@example.com'
};

const animals = [
  'dog',
  'cat',
  'bird'
];

// ✅ 函数声明
function calculateAge(birthDate) {
  const today = new Date();
  const birth = new Date(birthDate);
  return today.getFullYear() - birth.getFullYear();
}

// ✅ 箭头函数
const getFullName = (firstName, lastName) => `${firstName} ${lastName}`;

注释规范

/**
 * 获取用户信息
 * @param {number} userId - 用户ID
 * @param {Object} options - 查询选项
 * @param {boolean} options.includeProfile - 是否包含个人资料
 * @returns {Promise<Object>} 用户信息对象
 * @throws {Error} 当用户不存在时抛出错误
 */
async function getUserInfo(userId, options = {}) {
  // 验证用户ID
  if (!userId || typeof userId !== 'number') {
    throw new Error('Invalid user ID');
  }

  // 查询用户基本信息
  const user = await User.findById(userId);
  
  if (!user) {
    throw new Error('User not found');
  }

  // 如果需要包含个人资料
  if (options.includeProfile) {
    user.profile = await UserProfile.findByUserId(userId);
  }

  return user;
}

错误处理

// ✅ 使用try-catch处理异步错误
async function createUser(userData) {
  try {
    // 验证输入数据
    const validatedData = validateUserData(userData);
    
    // 创建用户
    const user = await User.create(validatedData);
    
    // 记录日志
    logger.info('User created successfully', { userId: user.id });
    
    return user;
  } catch (error) {
    // 记录错误日志
    logger.error('Failed to create user', { error: error.message, userData });
    
    // 重新抛出错误
    throw error;
  }
}

// ✅ 使用自定义错误类
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

Vue.js代码规范

组件结构

<template>
  <!-- 模板内容 -->
  <div class="user-profile">
    <div class="user-profile__header">
      <h2 class="user-profile__title">{{ user.name }}</h2>
    </div>
    
    <div class="user-profile__content">
      <UserAvatar 
        :src="user.avatar" 
        :alt="user.name"
        @click="handleAvatarClick"
      />
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';
import UserAvatar from '@/components/common/UserAvatar.vue';

export default {
  name: 'UserProfile',
  
  components: {
    UserAvatar
  },
  
  props: {
    userId: {
      type: Number,
      required: true
    },
    editable: {
      type: Boolean,
      default: false
    }
  },
  
  emits: ['profile-updated', 'avatar-changed'],
  
  setup(props, { emit }) {
    // 响应式数据
    const user = ref(null);
    const loading = ref(false);
    
    // 计算属性
    const displayName = computed(() => {
      return user.value ? user.value.name : 'Unknown User';
    });
    
    // 方法
    const loadUser = async () => {
      loading.value = true;
      try {
        user.value = await userStore.fetchUser(props.userId);
      } catch (error) {
        console.error('Failed to load user:', error);
      } finally {
        loading.value = false;
      }
    };
    
    const handleAvatarClick = () => {
      if (props.editable) {
        emit('avatar-changed');
      }
    };
    
    // 生命周期
    onMounted(() => {
      loadUser();
    });
    
    // 返回模板需要的数据和方法
    return {
      user,
      loading,
      displayName,
      handleAvatarClick
    };
  }
};
</script>

<style lang="scss" scoped>
.user-profile {
  padding: 20px;
  
  &__header {
    margin-bottom: 16px;
  }
  
  &__title {
    font-size: 24px;
    font-weight: 600;
    color: #333;
  }
  
  &__content {
    display: flex;
    align-items: center;
  }
}
</style>

CSS/SCSS规范

// ✅ 使用BEM命名规范
.animal-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 16px;
  
  &__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
  
  &__title {
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }
  
  &__status {
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    
    &--available {
      background-color: #e8f5e8;
      color: #2d8f2d;
    }
    
    &--adopted {
      background-color: #fff3cd;
      color: #856404;
    }
  }
  
  &__content {
    margin-bottom: 16px;
  }
  
  &__actions {
    display: flex;
    gap: 8px;
  }
}

// ✅ 使用CSS变量
:root {
  --primary-color: #007bff;
  --success-color: #28a745;
  --warning-color: #ffc107;
  --danger-color: #dc3545;
  --font-family: 'Helvetica Neue', Arial, sans-serif;
}

🧪 测试规范

测试文件命名

  • 单元测试: *.test.js*.spec.js
  • 集成测试: *.integration.test.js
  • E2E测试: *.e2e.test.js

测试结构

// ✅ 测试文件示例
describe('UserService', () => {
  let userService;
  let mockDatabase;
  
  beforeEach(() => {
    mockDatabase = createMockDatabase();
    userService = new UserService(mockDatabase);
  });
  
  afterEach(() => {
    mockDatabase.reset();
  });
  
  describe('createUser', () => {
    it('should create user with valid data', async () => {
      // Arrange
      const userData = {
        name: 'John Doe',
        email: 'john@example.com'
      };
      
      // Act
      const result = await userService.createUser(userData);
      
      // Assert
      expect(result).toBeDefined();
      expect(result.id).toBeTruthy();
      expect(result.name).toBe(userData.name);
      expect(result.email).toBe(userData.email);
    });
    
    it('should throw error with invalid email', async () => {
      // Arrange
      const userData = {
        name: 'John Doe',
        email: 'invalid-email'
      };
      
      // Act & Assert
      await expect(userService.createUser(userData))
        .rejects
        .toThrow('Invalid email format');
    });
  });
});

测试覆盖率要求

  • 单元测试覆盖率: ≥ 80%
  • 集成测试覆盖率: ≥ 60%
  • 关键业务逻辑: 100%

📝 文档规范

API文档

使用OpenAPI 3.0规范编写API文档

# ✅ API文档示例
paths:
  /api/v1/users/{id}:
    get:
      summary: 获取用户信息
      description: 根据用户ID获取用户详细信息
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: 用户ID
      responses:
        '200':
          description: 成功获取用户信息
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: 用户不存在
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

代码注释

/**
 * 动物认领服务类
 * 处理动物认领相关的业务逻辑
 */
class AdoptionService {
  /**
   * 创建认领申请
   * @param {Object} adoptionData - 认领申请数据
   * @param {number} adoptionData.userId - 申请人ID
   * @param {number} adoptionData.animalId - 动物ID
   * @param {string} adoptionData.reason - 认领原因
   * @param {Object} adoptionData.contact - 联系方式
   * @returns {Promise<Object>} 认领申请对象
   * @throws {ValidationError} 当数据验证失败时
   * @throws {BusinessError} 当业务规则验证失败时
   * 
   * @example
   * const adoption = await adoptionService.createAdoption({
   *   userId: 123,
   *   animalId: 456,
   *   reason: '我想给这只小狗一个温暖的家',
   *   contact: { phone: '13800138000', address: '北京市朝阳区' }
   * });
   */
  async createAdoption(adoptionData) {
    // 实现代码...
  }
}

🔒 安全规范

输入验证

// ✅ 使用joi进行数据验证
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required(),
  phone: Joi.string().pattern(/^1[3-9]\d{9}$/).optional()
});

// 验证用户输入
const { error, value } = userSchema.validate(userData);
if (error) {
  throw new ValidationError(error.details[0].message);
}

SQL注入防护

// ✅ 使用参数化查询
const getUserById = async (id) => {
  const query = 'SELECT * FROM users WHERE id = ?';
  const result = await db.query(query, [id]);
  return result[0];
};

// ❌ 避免字符串拼接
const getUserById = async (id) => {
  const query = `SELECT * FROM users WHERE id = ${id}`; // 危险!
  const result = await db.query(query);
  return result[0];
};

敏感信息处理

// ✅ 密码加密
const bcrypt = require('bcrypt');

const hashPassword = async (password) => {
  const saltRounds = 12;
  return await bcrypt.hash(password, saltRounds);
};

// ✅ 敏感信息过滤
const sanitizeUser = (user) => {
  const { password, salt, ...safeUser } = user;
  return safeUser;
};

🚀 性能优化规范

数据库查询优化

// ✅ 使用索引和限制查询
const getAnimals = async (filters, pagination) => {
  const { page = 1, limit = 20 } = pagination;
  const offset = (page - 1) * limit;
  
  const query = `
    SELECT a.*, u.name as owner_name 
    FROM animals a 
    LEFT JOIN users u ON a.owner_id = u.id 
    WHERE a.status = ? 
    ORDER BY a.created_at DESC 
    LIMIT ? OFFSET ?
  `;
  
  return await db.query(query, [filters.status, limit, offset]);
};

// ✅ 使用缓存
const Redis = require('redis');
const redis = Redis.createClient();

const getCachedUser = async (userId) => {
  const cacheKey = `user:${userId}`;
  
  // 尝试从缓存获取
  let user = await redis.get(cacheKey);
  if (user) {
    return JSON.parse(user);
  }
  
  // 从数据库获取
  user = await User.findById(userId);
  
  // 存入缓存过期时间1小时
  await redis.setex(cacheKey, 3600, JSON.stringify(user));
  
  return user;
};

前端性能优化

<template>
  <!--  使用v-show代替v-if进行频繁切换 -->
  <div v-show="isVisible" class="content">
    <!--  使用key优化列表渲染 -->
    <div 
      v-for="animal in animals" 
      :key="animal.id"
      class="animal-item"
    >
      {{ animal.name }}
    </div>
  </div>
</template>

<script>
import { ref, computed, watchEffect } from 'vue';

export default {
  setup() {
    // ✅ 使用computed缓存计算结果
    const expensiveValue = computed(() => {
      return animals.value.filter(animal => animal.status === 'available')
        .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
    });
    
    // ✅ 使用防抖处理搜索
    const searchTerm = ref('');
    const debouncedSearch = debounce((term) => {
      performSearch(term);
    }, 300);
    
    watchEffect(() => {
      debouncedSearch(searchTerm.value);
    });
    
    return {
      expensiveValue,
      searchTerm
    };
  }
};
</script>

📋 Git工作流规范

分支命名

  • 主分支: main
  • 开发分支: develop
  • 功能分支: feature/功能名称
  • 修复分支: fix/问题描述
  • 发布分支: release/版本号

提交信息规范

使用Conventional Commits规范

# ✅ 正确的提交信息
feat: 添加用户认证功能
fix: 修复动物列表分页问题
docs: 更新API文档
style: 统一代码格式
refactor: 重构用户服务层
test: 添加用户注册测试用例
chore: 更新依赖包版本

# 详细提交信息示例
feat: 添加动物认领申请功能

- 实现认领申请表单
- 添加申请状态跟踪
- 集成邮件通知功能
- 添加相关测试用例

Closes #123

代码审查清单

  • 代码符合项目规范
  • 功能实现正确
  • 测试用例充分
  • 文档更新完整
  • 性能影响评估
  • 安全风险评估
  • 向后兼容性检查

🛠️ 开发工具配置

ESLint配置

{
  "extends": [
    "eslint:recommended",
    "@vue/eslint-config-prettier"
  ],
  "rules": {
    "indent": ["error", 2],
    "quotes": ["error", "single"],
    "semi": ["error", "always"],
    "no-console": "warn",
    "no-debugger": "error",
    "no-unused-vars": "error"
  }
}

Prettier配置

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80,
  "bracketSpacing": true,
  "arrowParens": "avoid"
}

VS Code配置

{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "emmet.includeLanguages": {
    "vue": "html"
  },
  "files.associations": {
    "*.vue": "vue"
  }
}

📚 学习资源

官方文档

最佳实践

工具和库

🔄 规范更新

本规范会根据项目发展和团队反馈持续更新。如有建议或问题,请通过以下方式反馈:

  1. 创建GitHub Issue
  2. 提交Pull Request
  3. 团队会议讨论

文档版本: v1.0.0
最后更新: 2024年1月15日
下次审查: 2024年4月15日