Developer Guide

代码规范与最佳实践

Tagtag Starter 项目的代码规范和最佳实践指南。

良好的代码规范是保证代码质量、提高开发效率、减少维护成本的重要手段。本文档总结了 Tagtag Starter 项目的代码规范和最佳实践,包括 Java 后端和 TypeScript 前端的编码规范、命名约定和代码审查标准。

1. 通用原则

无论使用哪种编程语言,以下通用原则都应遵循:

1.1 代码可读性优先

  • 代码是写给人看的,不是写给机器看的
  • 保持代码简洁、清晰、易于理解
  • 使用有意义的命名,避免缩写和隐晦的名称
  • 适当添加注释,但不要过度注释

1.2 一致性

  • 遵循项目已有的代码风格
  • 保持缩进、换行、空格等格式一致
  • 统一使用一种命名风格

1.3 模块化

  • 将代码拆分为可复用的模块
  • 每个模块职责单一
  • 模块间依赖关系清晰

1.4 测试优先

  • 编写可测试的代码
  • 为核心功能编写单元测试
  • 遵循测试驱动开发(TDD)原则

1.5 安全性

  • 避免硬编码敏感信息
  • 注意 SQL 注入、XSS 攻击等安全问题
  • 遵循最小权限原则

2. Java 代码规范

Tagtag Starter 后端使用 Java 语言开发,遵循以下代码规范:

2.1 命名约定

  • 类名:使用 PascalCase,例如 UserServicePostController
  • 方法名:使用 camelCase,例如 getUserByIdsavePost
  • 变量名:使用 camelCase,例如 userNamepageSize
  • 常量名:使用 UPPER_CASE_WITH_UNDERSCORES,例如 MAX_PAGE_SIZEDEFAULT_STATUS
  • 包名:使用小写字母,例如 dev.tagtag.common.util
  • 接口名:使用 PascalCase,例如 UserServicePostMapper

实际项目示例:

// 类名示例
@Service
public class UserServiceImpl implements UserService { }

@RestController
@RequestMapping("/users")
public class UserController { }

// 方法名示例
public UserDTO getUserById(Long userId) { }
public PageResult<UserDTO> pageUsers(UserQuery query) { }
public void createUser(UserDTO user) { }
public void updateUser(UserDTO user) { }
public void deleteUser(Long userId) { }

// 常量示例
private static final int MAX_PAGE_SIZE = 100;
private static final String DEFAULT_AVATAR = "/default-avatar.png";
private static final Integer STATUS_ACTIVE = 1;

2.2 代码格式

  • 缩进:使用 4 个空格进行缩进
  • 换行:每行不超过 120 个字符
  • 空行:在方法之间、代码块之间添加适当的空行
  • 括号:左括号 { 与语句同一行,右括号 } 单独一行
  • 空格
    • 运算符前后添加空格,例如 a = b + c
    • 逗号、分号后添加空格,例如 method(a, b, c)
    • 括号内不添加空格,例如 method(a, b) 而非 method( a, b )

2.3 注释规范

  • 类注释:使用 Javadoc 注释,说明类的用途、作者、创建日期等
    /**
     * 用户服务类,负责用户相关业务逻辑
     *
     * @author Tagtag Starter Team
     * @since 2024-01-01
     */
    @Service
    public class UserService {
        // 类实现
    }
    
  • 方法注释:使用 Javadoc 注释,说明方法的用途、参数、返回值、异常等
    /**
     * 根据用户 ID 获取用户信息
     *
     * @param userId 用户 ID
     * @return 用户信息
     * @throws BusinessException 如果用户不存在
     */
    public UserDTO getUserById(Long userId) {
        // 方法实现
    }
    
  • 行注释:使用 // 注释,说明复杂逻辑或特殊处理
    // 处理特殊情况:当用户未设置头像时,使用默认头像
    if (user.getAvatar() == null) {
        user.setAvatar(DEFAULT_AVATAR);
    }
    

2.4 最佳实践

  • 使用 Lombok 简化代码:使用 @Data@NoArgsConstructor@AllArgsConstructor 等注解简化 POJO 类
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class UserDTO {
        private Long id;
        private String username;
        private String email;
        private Integer status;
    }
    
  • 避免魔法数字:将常量定义为静态常量,例如 private static final int MAX_RETRY = 3
    // 不推荐
    if (user.getStatus() == 1) { }
    
    // 推荐
    private static final Integer STATUS_ACTIVE = 1;
    if (user.getStatus().equals(STATUS_ACTIVE)) { }
    
  • 使用 Optional 处理 null:避免直接使用 null,使用 Optional 包装可能为 null 的值
    // 不推荐
    User user = userMapper.selectById(userId);
    if (user != null) {
        return user.getName();
    }
    return null;
    
    // 推荐
    return Optional.ofNullable(userMapper.selectById(userId))
        .map(User::getName)
        .orElse(null);
    
  • 使用 try-with-resources 管理资源:自动关闭资源,避免内存泄漏
    try (InputStream is = new FileInputStream(file)) {
        // 处理文件
    } catch (IOException e) {
        log.error("文件读取失败", e);
    }
    
  • 遵循 SOLID 原则
    • 单一职责原则:一个类只负责一个功能
    • 开放封闭原则:对扩展开放,对修改封闭
    • 里氏替换原则:子类可以替换父类
    • 接口隔离原则:使用多个专门的接口,而不是一个总接口
    • 依赖倒置原则:依赖抽象,不依赖具体实现

2.5 Spring Boot 最佳实践

  • 使用构造函数注入:避免使用字段注入,提高可测试性
    @Service
    @RequiredArgsConstructor
    public class UserServiceImpl implements UserService {
        private final UserMapper userMapper;
        private final RoleService roleService;
        
        @Override
        public UserDTO getUserById(Long userId) {
            User user = userMapper.selectById(userId);
            return BeanUtil.copyProperties(user, UserDTO.class);
        }
    }
    
  • 使用 @RestController 替代 @Controller + @ResponseBody:简化 REST 接口开发
    @RestController
    @RequestMapping("/users")
    @RequiredArgsConstructor
    public class UserController {
        private final UserService userService;
        
        @GetMapping("/{id}")
        @Operation(summary = "获取用户详情")
        @PreAuthorize("@ss.hasPermission('system:user:query')")
        public Result<UserDTO> getById(@PathVariable Long id) {
            return Result.ok(userService.getById(id));
        }
    }
    
  • 使用 @ExceptionHandler 处理异常:统一处理异常,提高代码可读性
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        
        @ExceptionHandler(BusinessException.class)
        public Result<Void> handleBusinessException(BusinessException e) {
            log.error("业务异常", e);
            return Result.fail(e.getMessage());
        }
        
        @ExceptionHandler(Exception.class)
        public Result<Void> handleException(Exception e) {
            log.error("系统异常", e);
            return Result.fail("系统错误,请联系管理员");
        }
    }
    
  • 使用 @Validated 进行参数校验:自动校验请求参数,减少重复代码
    @PostMapping
    @Operation(summary = "创建用户")
    public Result<Void> create(@Valid @RequestBody UserDTO dto) {
        userService.create(dto);
        return Result.okMsg("创建成功");
    }
    
    // DTO 定义
    @Data
    public class UserDTO {
        @NotBlank(message = "用户名不能为空")
        private String username;
        
        @Email(message = "邮箱格式不正确")
        private String email;
        
        @NotNull(message = "状态不能为空")
        private Integer status;
    }
    
  • 使用 @Transactional 管理事务:明确事务边界,确保数据一致性
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUserWithRoles(UserDTO user, List<Long> roleIds) {
        // 创建用户
        userService.create(user);
        
        // 分配角色
        if (CollectionUtils.isNotEmpty(roleIds)) {
            userRoleService.assignRoles(user.getId(), roleIds);
        }
    }
    

2.6 MyBatis-Plus 最佳实践

  • 使用 LambdaQueryWrapper 避免硬编码字段名:提高代码可维护性
    // 不推荐
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq("username", username);
    
    // 推荐
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getUsername, username);
    
  • 使用分页插件:统一分页查询
    @Override
    public PageResult<UserDTO> page(UserQuery query, PageQuery pageQuery) {
        IPage<UserVO> page = new Page<>(pageQuery.getPage(), pageQuery.getSize());
        IPage<UserVO> result = userMapper.selectPage(page, query);
        
        return PageResult.of(
            result.getRecords().stream()
                .map(vo -> BeanUtil.copyProperties(vo, UserDTO.class))
                .toList(),
            result.getTotal()
        );
    }
    
  • 使用 @TableLogic 实现逻辑删除:避免物理删除数据
    @Data
    @TableName("sys_user")
    public class User {
        @TableId
        private Long id;
        
        private String username;
        
        @TableLogic
        private Integer deleted;
    }
    

3. TypeScript 代码规范

Tagtag Starter 前端使用 TypeScript 语言开发,遵循以下代码规范:

3.1 命名约定

  • 组件名:使用 PascalCase,例如 PostListFormModal
  • 变量名:使用 camelCase,例如 userNameisLoading
  • 函数名:使用 camelCase,例如 getUserInfohandleSubmit
  • 常量名:使用 UPPER_CASE_WITH_UNDERSCORES,例如 MAX_PAGE_SIZEAPI_BASE_URL
  • 类型名:使用 PascalCase,例如 UserDTOPostQuery
  • 接口名:使用 PascalCase,例如 UserServicePostApi

实际项目示例:

// 组件名示例
export default defineComponent({
  name: 'UserList'
});

// 变量名示例
const userName = ref('');
const isLoading = ref(false);
const userList = ref<UserDTO[]>([]);

// 函数名示例
function getUserInfo() { }
function handleSubmit() { }
function handleDelete() { }

// 常量名示例
const MAX_PAGE_SIZE = 100;
const API_BASE_URL = '/api';
const STATUS_ACTIVE = 1;

// 类型名示例
interface UserDTO {
  id?: number;
  username: string;
  email?: string;
  status?: number;
}

interface PostQuery {
  title?: string;
  status?: number;
  page: number;
  size: number;
}

3.2 代码格式

  • 缩进:使用 2 个空格进行缩进
  • 换行:每行不超过 120 个字符
  • 空行:在函数之间、代码块之间添加适当的空行
  • 括号:左括号 { 与语句同一行,右括号 } 单独一行
  • 空格
    • 运算符前后添加空格,例如 a = b + c
    • 逗号、分号后添加空格,例如 method(a, b, c)
    • 括号内不添加空格,例如 method(a, b) 而非 method( a, b )

3.3 注释规范

  • 组件注释:使用 JSDoc 注释,说明组件的用途、属性、事件等
    /**
     * 文章列表组件,展示文章列表并提供筛选、排序功能
     *
     * @component
     * @param {Object} props - 组件属性
     * @param {Function} onEdit - 编辑文章回调函数
     * @param {Function} onDelete - 删除文章回调函数
     */
    export default defineComponent({
        // 组件实现
    });
    
  • 函数注释:使用 JSDoc 注释,说明函数的用途、参数、返回值等
    /**
     * 获取文章列表
     *
     * @param {PostQuery} params - 查询参数
     * @returns {Promise<PageResult<PostDTO>>} 文章列表
     */
    export function getPostList(params: PostQuery): Promise<PageResult<PostDTO>> {
        // 函数实现
    }
    
  • 行注释:使用 // 注释,说明复杂逻辑或特殊处理
    // 处理特殊情况:当文章状态为 0 时,显示为草稿
    const statusText = status === 1 ? '已发布' : '草稿';
    

3.4 最佳实践

  • 使用 TypeScript 严格模式:启用 strict: true 配置,提高类型安全性
    // tsconfig.json
    {
      "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true
      }
    }
    
  • 避免 any 类型:尽量使用具体类型,提高代码可靠性
    // 不推荐
    function getUser(id: any): any {
      return request.get({ url: `/users/${id}` });
    }
    
    // 推荐
    function getUser(id: number): Promise<UserDTO> {
      return request.get<UserDTO>({ url: `/users/${id}` });
    }
    
  • 使用接口定义数据结构:明确数据类型,提高代码可读性
    interface UserDTO {
      id?: number;
      username: string;
      email?: string;
      status?: number;
      createTime?: string;
    }
    
    interface PageResult<T> {
      records: T[];
      total: number;
      current: number;
      size: number;
    }
    
    interface UserQuery {
      username?: string;
      status?: number;
      page: number;
      size: number;
    }
    
  • 使用 const 替代 let:除非需要重新赋值,否则使用 const
    // 不推荐
    let userName = 'admin';
    let status = 1;
    
    // 推荐
    const userName = 'admin';
    const status = 1;
    
    // 需要重新赋值时使用 let
    let currentPage = 1;
    currentPage++;
    
  • 使用箭头函数:简化函数定义,避免 this 指向问题
    // 不推荐
    const handleClick = function() {
      console.log('clicked');
    };
    
    // 推荐
    const handleClick = () => {
      console.log('clicked');
    };
    
  • 使用解构赋值:简化变量赋值,提高代码可读性
    // 不推荐
    const user = response.data;
    const id = user.id;
    const name = user.name;
    
    // 推荐
    const { id, name } = response.data;
    
    // 数组解构
    const [first, second] = [1, 2, 3];
    
  • 使用模板字符串:简化字符串拼接
    // 不推荐
    const url = '/api/users/' + userId + '/roles/' + roleId;
    
    // 推荐
    const url = `/api/users/${userId}/roles/${roleId}`;
    
  • 使用可选链操作符:安全访问嵌套属性
    // 不推荐
    const userName = user && user.profile && user.profile.name;
    
    // 推荐
    const userName = user?.profile?.name;
    
  • 使用空值合并操作符:提供默认值
    // 不推荐
    const userName = user.name || 'Unknown';
    
    // 推荐
    const userName = user.name ?? 'Unknown';
    

3.5 Vue 3 最佳实践

  • 使用 Composition API:利用 setup 语法糖,提高代码组织性和复用性
    <script setup lang="ts">
    import { ref, computed, onMounted } from 'vue';
    import { getUserList } from '@/api/modules/system/user';
    
    const userList = ref<UserDTO[]>([]);
    const loading = ref(false);
    
    const activeUsers = computed(() => 
      userList.value.filter(user => user.status === 1)
    );
    
    async function loadData() {
      loading.value = true;
      try {
        const result = await getUserList({ page: 1, size: 10 });
        userList.value = result.records;
      } finally {
        loading.value = false;
      }
    }
    
    onMounted(() => {
      loadData();
    });
    </script>
    
  • 使用 defineProps 和 defineEmits:简化组件属性和事件定义
    const props = defineProps<{
      title: string;
      visible: boolean;
      formData?: UserDTO;
    }>();
    
    const emit = defineEmits<{
      (e: 'update:visible', value: boolean): void;
      (e: 'save', data: UserDTO): void;
      (e: 'cancel'): void;
    }>();
    
    // 使用 props
    console.log(props.title);
    
    // 触发事件
    emit('save', formData);
    
  • 使用 watch 和 watchEffect 监听数据变化
    // watch:监听特定数据源
    watch(
      () => props.visible,
      (newVal) => {
        if (newVal) {
          // 弹窗打开时的处理
        }
      }
    );
    
    // watchEffect:自动追踪依赖
    watchEffect(() => {
      console.log(`用户列表长度: ${userList.value.length}`);
    });
    
    // 监听多个数据源
    watch([page, size], ([newPage, newSize]) => {
      loadData();
    });
    
  • 使用 provide/inject 进行组件间通信:避免 props 层层传递
    // 父组件
    provide('userService', {
      getUserList,
      createUser,
      updateUser
    });
    
    // 子组件
    const userService = inject('userService');
    await userService?.getUserList({ page: 1, size: 10 });
    
  • 使用 Teleport 处理模态框:优化模态框定位和样式
    <template>
      <Teleport to="body">
        <Modal v-model:open="visible" title="用户信息">
          <!-- 模态框内容 -->
        </Modal>
      </Teleport>
    </template>
    
  • 使用 Suspense 处理异步组件:提供加载状态和错误处理
    <template>
      <Suspense>
        <template #default>
          <AsyncComponent />
        </template>
        <template #fallback>
          <div>加载中...</div>
        </template>
      </Suspense>
    </template>
    
  • 使用 vben-table 表格组件:统一表格开发规范
    import { useTable } from '@vben/plugins/vxe-table';
    
    const [Grid, gridApi] = useTable({
      api: getUserList,
      columns: [
        { type: 'checkbox', width: 50 },
        { field: 'username', title: '用户名' },
        { field: 'email', title: '邮箱' },
        { field: 'status', title: '状态', slots: { default: 'status' } },
        { title: '操作', slots: { default: 'action' }, width: 150 }
      ],
      formConfig: {
        items: [
          { field: 'username', label: '用户名', component: 'Input' },
          { field: 'status', label: '状态', component: 'Select' }
        ]
      }
    });
    
  • 使用 Pinia 进行状态管理:替代 Vuex,简化状态管理
    // stores/user.ts
    import { defineStore } from 'pinia';
    
    export const useUserStore = defineStore('user', {
      state: () => ({
        currentUser: null as UserDTO | null,
        token: ''
      }),
      getters: {
        isLoggedIn: (state) => !!state.token,
        userName: (state) => state.currentUser?.username || ''
      },
      actions: {
        async login(username: string, password: string) {
          const result = await login({ username, password });
          this.token = result.token;
          this.currentUser = result.user;
        },
        logout() {
          this.token = '';
          this.currentUser = null;
        }
      }
    });
    
    // 使用 store
    const userStore = useUserStore();
    await userStore.login('admin', '123456');
    console.log(userStore.userName);
    
  • 使用 composables 复用逻辑:提取可复用的逻辑
    // composables/useTableData.ts
    export function useTableData<T>(api: Function) {
      const data = ref<T[]>([]);
      const loading = ref(false);
      const total = ref(0);
      
      async function loadData(params: any) {
        loading.value = true;
        try {
          const result = await api(params);
          data.value = result.records;
          total.value = result.total;
        } finally {
          loading.value = false;
        }
      }
      
      return { data, loading, total, loadData };
    }
    
    // 使用 composable
    const { data, loading, total, loadData } = useTableData<UserDTO>(getUserList);
    

4. 命名约定

4.1 包/模块命名

  • Java 包名:使用小写字母,例如 dev.tagtag.common.util
  • TypeScript 模块名:使用小写字母,例如 @/api/modules/system/post
  • 目录名:使用 kebab-case,例如 user-managementpost-list

4.2 类/接口命名

  • Java 类名:使用 PascalCase,例如 UserServicePostController
  • TypeScript 类型名:使用 PascalCase,例如 UserDTOPostQuery
  • 接口名:使用 PascalCase,例如 UserRepositoryPostService

4.3 方法命名

  • 获取数据:使用 getfind 前缀,例如 getUserByIdfindPosts
  • 保存数据:使用 savecreate 前缀,例如 saveUsercreatePost
  • 更新数据:使用 update 前缀,例如 updateUserupdatePost
  • 删除数据:使用 delete 前缀,例如 deleteUserdeletePost
  • 查询列表:使用 listpage 前缀,例如 listUserspagePosts
  • 处理事件:使用 handle 前缀,例如 handleSubmithandleDelete

实际项目示例:

// 后端方法命名示例
public interface UserService {
    UserDTO getUserById(Long userId);
    PageResult<UserDTO> pageUsers(UserQuery query);
    List<UserDTO> listActiveUsers();
    void createUser(UserDTO user);
    void updateUser(UserDTO user);
    void deleteUser(Long userId);
    void batchDeleteUsers(List<Long> ids);
    void updateUserStatus(Long userId, Integer status);
    boolean existsByUsername(String username);
}
// 前端方法命名示例
export function getUserById(id: number): Promise<UserDTO> { }
export function getUserList(params: UserQuery): Promise<PageResult<UserDTO>> { }
export function createUser(data: UserDTO): Promise<void> { }
export function updateUser(data: UserDTO): Promise<void> { }
export function deleteUser(id: number): Promise<void> { }
export function batchDeleteUsers(ids: number[]): Promise<void> { }
export function updateUserStatus(id: number, status: number): Promise<void> { }

// 事件处理方法命名示例
function handleSubmit() { }
function handleEdit(row: UserDTO) { }
function handleDelete(id: number) { }
function handleBatchDelete(ids: number[]) { }
function handleStatusChange(id: number, status: number) { }
function handlePageChange(page: number) { }
function handleSearch() { }
function handleReset() { }

4.4 变量命名

  • 布尔变量:使用 ishas 前缀,例如 isLoadinghasPermission
  • 数组变量:使用复数形式,例如 usersposts
  • 集合变量:使用具体集合类型,例如 userListpostMap
  • 常量变量:使用 UPPER_CASE_WITH_UNDERSCORES,例如 MAX_PAGE_SIZEDEFAULT_AVATAR

实际项目示例:

// 后端变量命名示例
private boolean isLoading;
private boolean hasPermission;
private boolean isDeleted;
private boolean isActive;

private List<UserDTO> users;
private List<PostDTO> posts;
private List<RoleDTO> roles;

private Map<Long, UserDTO> userMap;
private Map<String, RoleDTO> roleMap;

private static final int MAX_PAGE_SIZE = 100;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final String DEFAULT_AVATAR = "/default-avatar.png";
private static final Integer STATUS_ACTIVE = 1;
private static final Integer STATUS_INACTIVE = 0;
// 前端变量命名示例
const isLoading = ref(false);
const hasPermission = ref(false);
const isModalVisible = ref(false);
const isFormValid = ref(false);

const users = ref<UserDTO[]>([]);
const posts = ref<PostDTO[]>([]);
const roles = ref<RoleDTO[]>([]);

const selectedUserIds = ref<number[]>([]);
const currentPage = ref(1);
const pageSize = ref(10);

const MAX_PAGE_SIZE = 100;
const DEFAULT_PAGE_SIZE = 10;
const API_BASE_URL = '/api';
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 0;

5. 代码审查标准

代码审查是保证代码质量的重要环节,以下是 Tagtag Starter 项目的代码审查标准:

5.1 审查内容

  • 功能正确性:代码是否实现了预期功能
  • 代码规范:是否遵循项目的代码规范
  • 代码可读性:代码是否易于理解和维护
  • 性能:是否存在性能问题,例如循环嵌套过深、不必要的计算等
  • 安全性:是否存在安全漏洞,例如 SQL 注入、XSS 攻击等
  • 测试覆盖率:是否为核心功能编写了测试用例
  • 依赖关系:模块间依赖关系是否清晰,是否存在循环依赖

5.2 审查流程

  1. 提交代码前
    • 运行本地测试,确保所有测试通过
    • 运行代码格式化工具,例如 Prettier
    • 运行静态代码分析工具,例如 ESLint、SonarQube
  2. 提交代码后
    • 创建 Pull Request (PR)
    • 指定至少一位 reviewer
    • 填写清晰的 PR 描述,包括功能、修改内容、测试情况等
  3. Reviewer 审查
    • 检查代码是否符合规范
    • 提出修改建议
    • 批准或拒绝 PR
  4. 修改代码
    • 根据 reviewer 的建议修改代码
    • 重新运行测试和代码分析
    • 提交修改
  5. 合并代码
    • 所有 reviewer 批准后,合并 PR
    • 删除临时分支

5.3 审查标准

  • 必须修复的问题
    • 语法错误
    • 逻辑错误
    • 安全漏洞
    • 性能问题
    • 违反基本设计原则

实际项目示例:

// 必须修复:SQL 注入漏洞
// 不推荐
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

// 推荐
String sql = "SELECT * FROM user WHERE username = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
// 必须修复:性能问题 - N+1 查询
// 不推荐
List<User> users = userMapper.selectList(null);
for (User user : users) {
    List<Role> roles = roleMapper.selectByUserId(user.getId());
    user.setRoles(roles);
}

// 推荐
List<UserVO> users = userMapper.selectUsersWithRoles();
// 必须修复:XSS 漏洞
// 不推荐
div.innerHTML = userInput;

// 推荐
div.textContent = userInput;
// 或使用 DOMPurify 等库进行清理
div.innerHTML = DOMPurify.sanitize(userInput);
  • 建议修复的问题
    • 代码可读性问题
    • 命名不规范
    • 注释不足
    • 重复代码
    • 可以优化的代码

实际项目示例:

// 建议修复:命名不规范
// 不推荐
public class Usr { }
public void getUsrInfo() { }

// 推荐
public class User { }
public void getUserInfo() { }
// 建议修复:重复代码
// 不推荐
public void createUser(UserDTO user) {
    if (user.getUsername() == null || user.getUsername().isEmpty()) {
        throw new BusinessException("用户名不能为空");
    }
    if (user.getEmail() == null || user.getEmail().isEmpty()) {
        throw new BusinessException("邮箱不能为空");
    }
}

public void updateUser(UserDTO user) {
    if (user.getUsername() == null || user.getUsername().isEmpty()) {
        throw new BusinessException("用户名不能为空");
    }
    if (user.getEmail() == null || user.getEmail().isEmpty()) {
        throw new BusinessException("邮箱不能为空");
    }
}

// 推荐
private void validateUser(UserDTO user) {
    if (StringUtils.isBlank(user.getUsername())) {
        throw new BusinessException("用户名不能为空");
    }
    if (StringUtils.isBlank(user.getEmail())) {
        throw new BusinessException("邮箱不能为空");
    }
}

public void createUser(UserDTO user) {
    validateUser(user);
}

public void updateUser(UserDTO user) {
    validateUser(user);
}

5.4 审查技巧

  • 专注于代码逻辑:不要纠结于排版等细节问题,使用自动化工具解决
  • 提出具体建议:不要只说 "这个地方不好",要说明具体问题和改进方案
  • 保持建设性:使用积极的语言,避免指责
  • 尊重作者:理解作者的设计思路,不要轻易否定
  • 学习他人代码:从审查中学习好的设计和实现

6. Git 最佳实践

6.1 分支管理

  • 主分支 (main):稳定的生产分支,只接收合并请求
  • 开发分支 (develop):开发分支,包含最新的开发代码
  • 功能分支 (feature/xxx):开发新功能的分支,从 develop 分支创建
  • 修复分支 (fix/xxx):修复 bug 的分支,从 main 分支创建
  • 发布分支 (release/xxx):准备发布的分支,从 develop 分支创建

6.2 提交规范

  • 提交信息清晰:使用简洁明了的提交信息,说明修改内容
  • 提交信息格式
    <type>(<scope>): <subject>
    
    <body>
    
    <footer>
    
    • type:提交类型,包括 feat(新功能)、fix(修复bug)、docs(文档)、style(样式)、refactor(重构)、test(测试)、chore(构建)等
    • scope:修改范围,例如 user、post、auth 等
    • subject:简短的提交描述,不超过 50 个字符
    • body:详细的提交描述,说明修改的原因和内容
    • footer:包含 breaking changes 或 issue 链接
  • 示例
    feat(user): 新增用户头像上传功能
    
    1. 添加头像上传接口
    2. 实现头像裁剪功能
    3. 更新用户信息页面
    
    Closes #123
    

6.3 提交频率

  • 频繁提交:每次提交一个小功能或修复一个小 bug
  • 避免大提交:不要一次提交大量代码,难以审查和回滚
  • 保持提交原子性:每个提交只包含一个逻辑变更

7. 总结

良好的代码规范和最佳实践是保证项目质量的重要基础。通过遵循本文档的规范和建议,我们可以:

  • 提高代码的可读性和可维护性
  • 减少 bug 和安全漏洞
  • 提高开发效率和协作效率
  • 降低项目的长期维护成本

希望所有开发者都能严格遵循这些规范和最佳实践,共同维护一个高质量的代码库。