AI工具简单测评

EmiyaCC 于 2024-08-10 发布

引言

随着人工智能技术的迅猛发展,智能编程辅助工具正逐渐成为软件开发领域的重要组成部分。这些工具通过提供自动代码补全、智能提示、错误检测等功能极大地提升了开发者的效率与生产力。在众多智能编程辅助工具中,通义灵码baidu comateFitten Code 是近期备受关注的几款产品。它们不仅代表了当前智能编程辅助工具的最高水平,而且各自拥有独特的功能和优势,旨在满足不同开发者的需求。

本文将对这三款工具进行简单的测评与对比,旨在帮助读者了解它们的特点、适用场景以及潜在的优势与局限性。无论你是经验丰富的开发人员还是编程新手,这些信息都将有助于你选择最适合自己的智能编程辅助工具,从而提升个人或团队的开发效率。接下来,我们将逐一探索这些工具的核心功能,并通过实际案例来展示它们如何在日常开发工作中发挥作用。

工具概述

功能对比

插件 私有化部署 代码/注释生成 问题排查及修复建议 代码优化及单测生成 本地工程/企业库问答 IDE 集成
通义灵码
Fitten Code
baidu Comate

更新频率

从 JetBrain 插件官网看,三个插件的都是在做持续更新,活跃度高,项目比较有价值。

数据对比

通义灵码和百度comate 并没有在官网上直接说明同其他竞品在各项场景下的对比数据,故暂不列出。 fitten code 在官网上直接说明,如下图所示

使用体验

真实的业务开发中,编程工具更多以辅助开发发现确认问题的功能为主,而代码生成功能则相对较少,故这里只介绍问题排查及修复建议、代码优化及单测生成 两大块。

  • 测试项目: https://github.com/codedrinker/community
  • 测试语言:Java
  • 测试设置:
    1. 不对插件做自定义配置
    2. 以第一次生成的结果为准
    3. 没充钱所以只做白嫖功能的测试
    4. 虽然有些功能可以通过对话的形式间接实现,但本次测试不考虑该途径,只直接测试插件提供的功能

功能截图

TONGYI-lingma 和 Baidu Comate 都有 gutter icons,使用起来会方便很多,Fitten Code 没有,所有功能放在一块,导致每次使用,需要手动选定代码块,比较麻烦。

使用小结

代码解释 fitten code 解释时会带上原始代码,相较其他两个会更清晰点

单测生成 生成的代码都无法直接使用,按场景分用例,造成用例膨胀且用例数据都不好管理,最好是 TONGYI-lingma的断言 + Fitten Code的场景

代码注释 TONGYI-lingma 效果最好

代码优化 都不是很全面,就使用而言,TONGYI-lingma 更为流畅,但是函数拆分真的很香,算是给了一个思路

总体感觉 由于都是白嫖的,所以没办法测试代码库问答,没办法试试一键生成整个项目文档的快感,从官网描述上看,TONGYI-lingma 和 Baidu Comate 在这块都花了比较多的心思,Fitten Code 只是在设置中找到有个整项目感知补全的功能。 从已体验的这些内容感觉看 TONGYI-lingma 还是最为流畅的,毕竟是阿里的模型,Java大户还是有点东西的。 Fitten Code 比较吸引我的点在于生成的单测场景较为全面,这点真的很重要,毕竟写单测真的很难为人,同时对代码优化,单独拆出一个找 bug 的功能,也是很 nice,毕竟更多还是希望插件能找出自己代码的问题。 Baidu Comate 算是三款产品中表现平平的,中规中矩,函数拆分有用,但是也没那么有用,毕竟重构代码,吃力不讨好,可能 C++ 它更擅长点?

测试结果

待测试代码块

 // 测试代码块
public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {

        if (StringUtils.isNotBlank(search)) {
            String[] tags = StringUtils.split(search, " ");
            search = Arrays
                    .stream(tags)
                    .filter(StringUtils::isNotBlank)
                    .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.joining("|"));
        }

        PaginationDTO paginationDTO = new PaginationDTO();

        Integer totalPage;

        QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
        questionQueryDTO.setSearch(search);
        if (StringUtils.isNotBlank(tag)) {
            tag = tag.replace("+", "").replace("*", "").replace("?", "");
            questionQueryDTO.setTag(tag);
        }

        for (SortEnum sortEnum : SortEnum.values()) {
            if (sortEnum.name().toLowerCase().equals(sort)) {
                questionQueryDTO.setSort(sort);

                if (sortEnum == SortEnum.HOT7) {
                    questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
                }
                if (sortEnum == SortEnum.HOT30) {
                    questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
                }
                break;
            }
        }

        Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);

        if (totalCount % size == 0) {
            totalPage = totalCount / size;
        } else {
            totalPage = totalCount / size + 1;
        }

        if (page < 1) {
            page = 1;
        }
        if (page > totalPage) {
            page = totalPage;
        }

        paginationDTO.setPagination(totalPage, page);
        Integer offset = page < 1 ? 0 : size * (page - 1);
        questionQueryDTO.setSize(size);
        questionQueryDTO.setPage(offset);
        List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);
        List<QuestionDTO> questionDTOList = new ArrayList<>();

        for (Question question : questions) {
            User user = userMapper.selectByPrimaryKey(question.getCreator());
            QuestionDTO questionDTO = new QuestionDTO();
            BeanUtils.copyProperties(question, questionDTO);
            questionDTO.setDescription("");
            questionDTO.setUser(user);
            questionDTOList.add(questionDTO);
        }
        List<QuestionDTO> stickies = questionCache.getStickies();
        if (stickies != null && stickies.size() != 0) {
            questionDTOList.addAll(0, stickies);
        }
        paginationDTO.setData(questionDTOList);
        return paginationDTO;
    }

代码解释

TONGYI lingma

这个函数是一个用于处理分页和搜索的Java方法,返回一个PaginationDTO对象,其中包含根据指定条件查询的问题列表。以下是该方法的主要功能解释:

1. 搜索处理:
如果search参数不为空,它会被分割成多个搜索标签,并且清除其中的空白字符和特殊字符(+、*、?),然后重新连接为一个字符串。

1. 标签处理:
如果tag参数不为空,它会被清除其中的空白字符和特殊字符(+、*、?)。

1. 排序处理:
根据枚举类SortEnum处理sort参数,设置相应的排序规则和时间限制(如果有的话)。

1. 分页计算:
通过查询符合条件的问题总数来计算总页数。
如果page参数小于1,则设置为1;如果大于总页数,则设置为总页数。
计算跳过的记录数(offset)。

1. 数据查询和转换:
根据计算好的offset和size查询问题列表。
对每个问题,查询其创建者信息,并将问题对象转换为QuestionDTO。
将问题DTO添加到questionDTOList列表中。

1. 置顶问题处理:
从缓存中获取置顶的问题列表,如果存在,则将其插入到问题列表的开头。

1. 返回结果:
将问题列表设置到PaginationDTO对象中,并返回该对象。

简而言之,这个方法用于根据搜索关键字、标签、排序方式和分页信息来查询和处理问题列表,并将结果封装在一个PaginationDTO对象中返回。

Baidu Comate

这段Java代码定义了一个名为list的方法,它接收五个参数:search(搜索关键词),tag(标签),sort(排序方式),page(页码),和size(每页大小),并返回一个PaginationDTO对象。这个方法的目的是根据给定的条件从数据库中查询问题列表,并返回分页后的结果。下面是对这个方法主要步骤的详细解释:

1. 处理搜索关键词:
+ 如果search不为空或不仅仅是空白字符,它会将search字符串按空格分割成多个部分(tags),然后对每个部分进行过滤和清理(去除+、*、?字符),最后使用|连接成一个新的search字符串。这个处理是为了构建一个可以在数据库查询中使用的搜索条件,其中|可能表示“或”的意思,具体取决于后端的数据库查询逻辑。

2. 初始化分页和查询对象:
+ 创建一个PaginationDTO对象来存储分页信息和查询结果。
+ 创建一个QuestionQueryDTO对象来存储查询条件,如搜索关键词、标签、排序方式等。

3. 设置查询条件:
+ 将处理后的search字符串设置到QuestionQueryDTO中。
+ 如果tag不为空或不仅仅是空白字符,则进行类似的清理操作后设置到QuestionQueryDTO中。
+ 遍历SortEnum枚举的所有值,找到与sort参数匹配的排序方式,并设置到QuestionQueryDTO中。对于特定的排序方式(如HOT7和HOT30),还会设置一个时间戳,可能是为了限制查询结果的时间范围。

4. 计算总页数和调整页码:
+ 通过调用questionExtMapper.countBySearch(questionQueryDTO)+ 查询满足条件的记录总数(totalCount)。
+ 根据totalCount和size计算总页数(totalPage)。
+ 如果传入的page小于1,则将其设置为1;如果大于totalPage,则设置为totalPage。

5. 执行查询并构建结果:
+ 设置QuestionQueryDTO的size和page(实际上是偏移量offset),然后调用questionExtMapper.selectBySearch(questionQueryDTO)执行查询,获取当前页的问题列表。
+ 遍历查询结果,对每个Question对象进行转换,创建QuestionDTO对象,并通过userMapper.selectByPrimaryKey(question.getCreator())获取问题的创建者信息,然后设置到QuestionDTO中。同时,将description字段清空,并将QuestionDTO对象添加到结果列表中。
+ 从缓存中获取置顶问题列表(stickies),如果存在且不为空,则将其添加到结果列表的开头。

6. 设置分页信息和返回结果:
+ 将总页数和当前页码设置到PaginationDTO中。
+ 将处理后的结果列表(questionDTOList)设置到PaginationDTO中作为数据部分。
+ 返回PaginationDTO对象。

这个方法的主要作用是提供了一个灵活的分页查询接口,支持根据搜索关键词、标签和排序方式对问题进行查询,并返回分页后的结果列表,同时支持将置顶问题添加到结果列表的开头。

Fitten Code

1. 处理搜索条件
if (StringUtils.isNotBlank(search)) {
    String[] tags = StringUtils.split(search, " ");
    search = Arrays
            .stream(tags)
            .filter(StringUtils::isNotBlank)
            .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
            .filter(StringUtils::isNotBlank)
            .collect(Collectors.joining("|"));
}
如果 search 不为空将其按空格分割成多个标签
过滤掉空标签并去除特殊字符 +, *, ?
将处理后的标签用 | 连接成一个字符串

2. 初始化分页对象
PaginationDTO paginationDTO = new PaginationDTO();

3. 设置查询条件
QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
questionQueryDTO.setSearch(search);
if (StringUtils.isNotBlank(tag)) {
    tag = tag.replace("+", "").replace("*", "").replace("?", "");
    questionQueryDTO.setTag(tag);
}
创建 QuestionQueryDTO 对象并设置搜索条件和标签

4. 设置排序条件
for (SortEnum sortEnum : SortEnum.values()) {
    if (sortEnum.name().toLowerCase().equals(sort)) {
        questionQueryDTO.setSort(sort);

        if (sortEnum == SortEnum.HOT7) {
            questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
        }
        if (sortEnum == SortEnum.HOT30) {
            questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
        }
        break;
    }
}
遍历 SortEnum 枚举找到匹配的排序方式并设置到 questionQueryDTO 
如果是 HOT7  HOT30设置时间范围

5. 计算总页数
Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);
Integer totalPage;
if (totalCount % size == 0) {
    totalPage = totalCount / size;
} else {
    totalPage = totalCount / size + 1;
}
根据查询条件计算总问题数
根据每页大小计算总页数

6. 处理分页参数
if (page < 1) {
    page = 1;
}
if (page > totalPage) {
    page = totalPage;
}
paginationDTO.setPagination(totalPage, page);
Integer offset = page < 1 ? 0 : size * (page - 1);
questionQueryDTO.setSize(size);
questionQueryDTO.setPage(offset);
确保页码在有效范围内
设置分页信息到 paginationDTO
计算偏移量并设置到 questionQueryDTO

7. 查询问题列表
List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);
List<QuestionDTO> questionDTOList = new ArrayList<>();
for (Question question : questions) {
    User user = userMapper.selectByPrimaryKey(question.getCreator());
    QuestionDTO questionDTO = new QuestionDTO();
    BeanUtils.copyProperties(question, questionDTO);
    questionDTO.setDescription("");
    questionDTO.setUser(user);
    questionDTOList.add(questionDTO);
}
根据查询条件获取问题列表
将每个问题转换为 QuestionDTO 对象并设置用户信息

8. 处理置顶问题
List<QuestionDTO> stickies = questionCache.getStickies();
if (stickies != null && stickies.size() != 0) {
    questionDTOList.addAll(0, stickies);
}
获取置顶问题列表并添加到结果列表的顶部

9. 设置分页数据并返回
paginationDTO.setData(questionDTOList);
return paginationDTO;

单测生成

TONGYI-lingma

package life.majiang.community.service;

import life.majiang.community.cache.QuestionCache;
import life.majiang.community.dto.PaginationDTO;
import life.majiang.community.dto.QuestionDTO;
import life.majiang.community.dto.QuestionQueryDTO;
import life.majiang.community.enums.SortEnum;
import life.majiang.community.mapper.QuestionExtMapper;
import life.majiang.community.mapper.UserMapper;
import life.majiang.community.model.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class QuestionServiceTest {

    @Mock
    private QuestionCache questionCache;

    @Mock
    private UserMapper userMapper;

    @Mock
    private QuestionExtMapper questionExtMapper;

    @InjectMocks
    private QuestionService questionService;

    private List<User> userList;

    @BeforeEach
    public void setUp() {
        userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId((long) i);
            userList.add(user);
        }
    }

    @Test
    public void testListNoSearchTag() {
        // Setup
        String search = "";
        String tag = "";
        String sort = SortEnum.NEW.name();
        Integer page = 1;
        Integer size = 10;

        // Mocking behavior
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(30);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(new ArrayList<>());
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        // Execute
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // Verify
        assertEquals(3, result.getTotalPage());
        assertEquals(1, result.getPage());
        assertEquals(10, result.getData().size());
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(10)).selectByPrimaryKey(anyLong());
    }

    @Test
    public void testListWithSearch() {
        // Setup
        String search = "java";
        String tag = "";
        String sort = SortEnum.NEW.name();
        Integer page = 1;
        Integer size = 10;

        // Mocking behavior
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(20);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(new ArrayList<>());
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        // Execute
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // Verify
        assertEquals(2, result.getTotalPage());
        assertEquals(1, result.getPage());
        assertEquals(10, result.getData().size());
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(10)).selectByPrimaryKey(anyLong());
    }

    @Test
    public void testListWithTag() {
        // Setup
        String search = "";
        String tag = "spring";
        String sort = SortEnum.NEW.name();
        Integer page = 1;
        Integer size = 10;

        // Mocking behavior
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(15);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(new ArrayList<>());
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        // Execute
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // Verify
        assertEquals(2, result.getTotalPage());
        assertEquals(1, result.getPage());
        assertEquals(10, result.getData().size());
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(10)).selectByPrimaryKey(anyLong());
    }

    @Test
    public void testListWithInvalidPageNumber() {
        // Setup
        String search = "";
        String tag = "";
        String sort = SortEnum.NEW.name();
        Integer page = -5;
        Integer size = 10;

        // Mocking behavior
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(30);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(new ArrayList<>());
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        // Execute
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // Verify
        assertEquals(3, result.getTotalPage());
        assertEquals(1, result.getPage()); // Page number less than 1 should default to 1
        assertEquals(10, result.getData().size());
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(10)).selectByPrimaryKey(anyLong());
    }

    @Test
    public void testListWithPageNumberGreaterThanTotalPages() {
        // Setup
        String search = "";
        String tag = "";
        String sort = SortEnum.NEW.name();
        Integer page = 10;
        Integer size = 10;

        // Mocking behavior
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(30);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(new ArrayList<>());
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        // Execute
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // Verify
        assertEquals(3, result.getTotalPage());
        assertEquals(3, result.getPage()); // Page number greater than total pages should default to total pages
        assertEquals(0, result.getData().size()); // Last page with no data
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(0)).selectByPrimaryKey(anyLong()); // No users should be fetched for an empty result set
    }
}

Baidu Comate

package life.majiang.community.service;

import life.majiang.community.cache.QuestionCache;
import life.majiang.community.dto.PaginationDTO;
import life.majiang.community.dto.QuestionDTO;
import life.majiang.community.dto.QuestionQueryDTO;
import life.majiang.community.enums.SortEnum;
import life.majiang.community.mapper.QuestionExtMapper;
import life.majiang.community.mapper.QuestionMapper;
import life.majiang.community.mapper.UserMapper;
import life.majiang.community.model.Question;
import life.majiang.community.model.User;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class QuestionServiceTest {

    @Mock
    private QuestionCache questionCache;

    @Mock
    private UserMapper userMapper;

    @Mock
    private QuestionExtMapper questionExtMapper;

    @Mock
    private QuestionMapper questionMapper;

    @InjectMocks
    private QuestionService questionService;

    private List<Question> questionList;
    private List<User> userList;

    @BeforeEach
    public void setUp() {
        questionList = new ArrayList<>();
        userList = new ArrayList<>();

        // Initialize questionList and userList with sample data
        for (int i = 0; i < 10; i++) {
            Question question = new Question();
            question.setCreator(1L);
            questionList.add(question);

            User user = new User();
            user.setId(1L);
            userList.add(user);
        }
    }

    @Test
    public void testList_withSearchAndTag() {
        String search = "java";
        String tag = "spring";
        String sort = "HOT";
        Integer page = 1;
        Integer size = 10;

        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(questionList);
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        assertEquals(1, result.getTotalPage());
        assertEquals(10, result.getData().size());
        verify(questionExtMapper, times(1)).countBySearch(any(QuestionQueryDTO.class));
        verify(questionExtMapper, times(1)).selectBySearch(any(QuestionQueryDTO.class));
        verify(userMapper, times(10)).selectByPrimaryKey(anyLong());
    }

    @Test
    public void testList_withInvalidPage() {
        String search = "java";
        String tag = "spring";
        String sort = "HOT";
        Integer page = -1;
        Integer size = 10;

        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(questionList);
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        assertEquals(1, result.getPage());
        assertEquals(10, result.getData().size());
    }

    @Test
    public void testList_withExceedingPage() {
        String search = "java";
        String tag = "spring";
        String sort = "HOT";
        Integer page = 5;
        Integer size = 10;

        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(questionList);
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));

        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        assertEquals(1, result.getPage());
        assertEquals(10, result.getData().size());
    }

    @Test
    public void testList_withEmptyStickies() {
        String search = "java";
        String tag = "spring";
        String sort = "HOT";
        Integer page = 1;
        Integer size = 10;

        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(questionList);
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));
        when(questionCache.getStickies()).thenReturn(new ArrayList<>());

        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        assertEquals(10, result.getData().size());
        verify(questionCache, times(1)).getStickies();
    }

    @Test
    public void testList_withNonEmptyStickies() {
        String search = "java";
        String tag = "spring";
        String sort = "HOT";
        Integer page = 1;
        Integer size = 10;

        List<QuestionDTO> stickies = new ArrayList<>();
        stickies.add(new QuestionDTO());

        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        when(questionExtMapper.selectBySearch(any(QuestionQueryDTO.class))).thenReturn(questionList);
        when(userMapper.selectByPrimaryKey(anyLong())).thenReturn(userList.get(0));
        when(questionCache.getStickies()).thenReturn(stickies);

        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        assertEquals(11, result.getData().size());
        verify(questionCache, times(1)).getStickies();
    }
}

Fitten Code

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import life.majiang.community.cache.QuestionCache;
import life.majiang.community.dto.PaginationDTO;
import life.majiang.community.dto.QuestionDTO;
import life.majiang.community.dto.QuestionQueryDTO;
import life.majiang.community.enums.SortEnum;
import life.majiang.community.exception.CustomizeErrorCode;
import life.majiang.community.exception.CustomizeException;
import life.majiang.community.mapper.QuestionExtMapper;
import life.majiang.community.mapper.QuestionMapper;
import life.majiang.community.mapper.UserMapper;
import life.majiang.community.model.Question;
import life.majiang.community.model.QuestionExample;
import life.majiang.community.model.User;
import org.apache.ibatis.session.RowBounds;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class QuestionServiceTest {

    @Mock
    private QuestionMapper questionMapper;

    @Mock
    private QuestionExtMapper questionExtMapper;

    @Mock
    private UserMapper userMapper;

    @Mock
    private QuestionCache questionCache;

    @InjectMocks
    private QuestionService questionService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testList_HappyPath() {
        // 设置输入参数
        String search = "test";
        String tag = "java";
        String sort = "new";
        Integer page = 1;
        Integer size = 10;

        // 模拟返回值
        QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
        questionQueryDTO.setSearch(search);
        questionQueryDTO.setTag(tag);
        questionQueryDTO.setSort(sort);
        when(questionExtMapper.countBySearch(questionQueryDTO)).thenReturn(100);

        List<Question> questions = new ArrayList<>();
        Question question = new Question();
        question.setCreator(1L);
        questions.add(question);
        when(questionExtMapper.selectBySearch(questionQueryDTO)).thenReturn(questions);

        User user = new User();
        when(userMapper.selectByPrimaryKey(1L)).thenReturn(user);

        // 执行测试方法
        PaginationDTO result = questionService.list(search, tag, sort, page, size);

        // 验证结果
        assertNotNull(result);
        assertEquals(10, result.getData().size());
    }

    @Test
    public void testList_EdgeCases() {
        // 测试空搜索词
        PaginationDTO resultEmptySearch = questionService.list("", "java", "new", 1, 10);
        assertNotNull(resultEmptySearch);

        // 测试空标签
        PaginationDTO resultEmptyTag = questionService.list("test", "", "new", 1, 10);
        assertNotNull(resultEmptyTag);

        // 测试空排序
        PaginationDTO resultEmptySort = questionService.list("test", "java", "", 1, 10);
        assertNotNull(resultEmptySort);

        // 测试页码小于1
        PaginationDTO resultPageLessThan1 = questionService.list("test", "java", "new", -1, 10);
        assertNotNull(resultPageLessThan1);

        // 测试页码大于总页数
        when(questionExtMapper.countBySearch(any(QuestionQueryDTO.class))).thenReturn(10);
        PaginationDTO resultPageGreaterThanTotal = questionService.list("test", "java", "new", 2, 10);
        assertNotNull(resultPageGreaterThanTotal);
    }

    @Test
    public void testGetById_HappyPath() {
        Long id = 1L;
        Question question = new Question();
        question.setId(id);
        question.setCreator(1L);
        when(questionMapper.selectByPrimaryKey(id)).thenReturn(question);

        User user = new User();
        when(userMapper.selectByPrimaryKey(1L)).thenReturn(user);

        QuestionDTO result = questionService.getById(id);
        assertNotNull(result);
        assertEquals(id, result.getId());
    }

    @Test(expected = CustomizeException.class)
    public void testGetById_NotFound() {
        Long id = 1L;
        when(questionMapper.selectByPrimaryKey(id)).thenReturn(null);

        questionService.getById(id);
    }

    @Test
    public void testCreateOrUpdate_Create() {
        Question question = new Question();
        question.setTitle("title");
        question.setDescription("description");
        question.setTag("tag");

        questionService.createOrUpdate(question);

        verify(questionMapper, times(1)).insert(question);
    }

    @Test
    public void testCreateOrUpdate_Update() {
        Question question = new Question();
        question.setId(1L);
        question.setCreator(1L);
        question.setTitle("title");
        question.setDescription("description");
        question.setTag("tag");

        Question dbQuestion = new Question();
        dbQuestion.setId(1L);
        dbQuestion.setCreator(1L);
        when(questionMapper.selectByPrimaryKey(1L)).thenReturn(dbQuestion);

        questionService.createOrUpdate(question);

        verify(questionMapper, times(1)).updateByExampleSelective(any(Question.class), any(QuestionExample.class));
    }

    @Test(expected = CustomizeException.class)
    public void testCreateOrUpdate_InvalidOperation() {
        Question question = new Question();
        question.setId(1L);
        question.setCreator(2L);

        Question dbQuestion = new Question();
        dbQuestion.setId(1L);
        dbQuestion.setCreator(1L);
        when(questionMapper.selectByPrimaryKey(1L)).thenReturn(dbQuestion);

        questionService.createOrUpdate(question);
    }

    @Test
    public void testIncView() {
        Long id = 1L;
        questionService.incView(id);

        verify(questionExtMapper, times(1)).incView(any(Question.class));
    }

    @Test
    public void testSelectRelated_HappyPath() {
        QuestionDTO queryDTO = new QuestionDTO();
        queryDTO.setId(1L);
        queryDTO.setTag("java,spring");

        Question question = new Question();
        question.setId(1L);
        question.setTag("java|spring");

        List<Question> questions = new ArrayList<>();
        questions.add(question);
        when(questionExtMapper.selectRelated(question)).thenReturn(questions);

        List<QuestionDTO> result = questionService.selectRelated(queryDTO);
        assertNotNull(result);
        assertEquals(1, result.size());
    }

    @Test
    public void testSelectRelated_EmptyTag() {
        QuestionDTO queryDTO = new QuestionDTO();
        queryDTO.setId(1L);
        queryDTO.setTag("");

        List<QuestionDTO> result = questionService.selectRelated(queryDTO);
        assertNotNull(result);
        assertEquals(0, result.size());
    }
}

代码注释

TONGYI-lingma

/**
 * 实现分页查询功能,根据搜索关键字、标签、排序方式、页码和页面大小返回问题列表
 * 
 * @param search 搜索关键字,多个关键字用空格分隔
 * @param tag 问题标签,用于筛选特定标签的问题
 * @param sort 排序方式,决定问题的排序逻辑
 * @param page 页码,用户请求的页码
 * @param size 页面大小,每页包含的问题数量
 * @return PaginationDTO 分页数据传输对象,包含问题列表和分页信息
 */
public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {

    // 如果搜索关键字不为空,则处理搜索关键字,将多个关键字用竖线分隔,以适应正则表达式查询
    if (StringUtils.isNotBlank(search)) {
        String[] tags = StringUtils.split(search, " ");
        search = Arrays
                .stream(tags)
                .filter(StringUtils::isNotBlank)
                .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.joining("|"));
    }

    // 初始化分页数据传输对象
    PaginationDTO paginationDTO = new PaginationDTO();

    // 用于存储总页数
    Integer totalPage;

    // 创建问题查询对象,并设置搜索关键字
    QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
    questionQueryDTO.setSearch(search);
    // 如果标签不为空,则处理并设置标签
    if (StringUtils.isNotBlank(tag)) {
        tag = tag.replace("+", "").replace("*", "").replace("?", "");
        questionQueryDTO.setTag(tag);
    }

    // 遍历排序枚举,根据传入的排序方式设置问题查询对象的排序和时间
    for (SortEnum sortEnum : SortEnum.values()) {
        if (sortEnum.name().toLowerCase().equals(sort)) {
            questionQueryDTO.setSort(sort);

            if (sortEnum == SortEnum.HOT7) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
            }
            if (sortEnum == SortEnum.HOT30) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
            }
            break;
        }
    }

    // 调用扩展映射器的方法,根据搜索条件计算总问题数
    Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);

    // 计算总页数
    if (totalCount % size == 0) {
        totalPage = totalCount / size;
    } else {
        totalPage = totalCount / size + 1;
    }

    // 确保页码在有效范围内
    if (page < 1) {
        page = 1;
    }
    if (page > totalPage) {
        page = totalPage;
    }

    // 设置分页信息
    paginationDTO.setPagination(totalPage, page);

    // 计算数据库查询的偏移量
    Integer offset = page < 1 ? 0 : size * (page - 1);

    // 设置问题查询对象的页面大小和偏移量
    questionQueryDTO.setSize(size);
    questionQueryDTO.setPage(offset);

    // 调用扩展映射器的方法,根据搜索条件和分页设置获取问题列表
    List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);

    // 初始化问题数据传输对象列表
    List<QuestionDTO> questionDTOList = new ArrayList<>();

    // 将问题实体转换为问题数据传输对象,并附加用户信息
    for (Question question : questions) {
        User user = userMapper.selectByPrimaryKey(question.getCreator());
        QuestionDTO questionDTO = new QuestionDTO();
        BeanUtils.copyProperties(question, questionDTO);
        questionDTO.setDescription("");
        questionDTO.setUser(user);
        questionDTOList.add(questionDTO);
    }

    // 获取置顶的问题列表,并将其添加到问题列表的开头
    List<QuestionDTO> stickies = questionCache.getStickies();
    if (stickies != null && stickies.size() != 0) {
        questionDTOList.addAll(0, stickies);
    }

    // 设置分页数据传输对象的数据部分
    paginationDTO.setData(questionDTOList);

    // 返回分页数据传输对象
    return paginationDTO;
}

Baidu Comate

// 函数注释
/**
 * 分页查询问题列表
 *
 * @param search 搜索关键词,多个关键词以空格分隔
 * @param tag    标签
 * @param sort   排序方式
 * @param page   页码
 * @param size   每页数量
 * @return 分页结果PaginationDTO
 */

// 行间注释
public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {
    // 如果搜索条件不为空
    if (StringUtils.isNotBlank(search)) {
        // 按空格分割搜索条件
        String[] tags = StringUtils.split(search, " ");
        // 使用流处理过滤和替换特殊字符,最后使用管道符连接
        search = Arrays
                .stream(tags)
                .filter(StringUtils::isNotBlank)
                .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.joining("|"));
    }

    PaginationDTO paginationDTO = new PaginationDTO();

    Integer totalPage;

    QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
    questionQueryDTO.setSearch(search);

    // 如果标签不为空
    if (StringUtils.isNotBlank(tag)) {
        // 替换标签中的特殊字符
        tag = tag.replace("+", "").replace("*", "").replace("?", "");
        questionQueryDTO.setTag(tag);
    }

    // 遍历排序枚举
    for (SortEnum sortEnum : SortEnum.values()) {
        // 如果排序枚举的名称(不区分大小写)与传入的排序参数相等
        if (sortEnum.name().toLowerCase().equals(sort)) {
            // 设置排序方式
            questionQueryDTO.setSort(sort);

            // 根据不同的排序枚举设置时间
            if (sortEnum == SortEnum.HOT7) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
            }
            if (sortEnum == SortEnum.HOT30) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
            }
            break;
        }
    }

    // 根据查询条件获取总数
    Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);

    // 计算总页数
    if (totalCount % size == 0) {
        totalPage = totalCount / size;
    } else {
        totalPage = totalCount / size + 1;
    }

    // 处理页码,确保页码在合理范围内
    if (page < 1) {
        page = 1;
    }
    if (page > totalPage) {
        page = totalPage;
    }

    // 设置分页信息
    paginationDTO.setPagination(totalPage, page);

    // 计算偏移量
    Integer offset = page < 1 ? 0 : size * (page - 1);
    questionQueryDTO.setSize(size);
    questionQueryDTO.setPage(offset);

    // 根据查询条件获取问题列表
    List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);
    List<QuestionDTO> questionDTOList = new ArrayList<>();

    // 遍历问题列表,转换并设置用户信息
    for (Question question : questions) {
        User user = userMapper.selectByPrimaryKey(question.getCreator());
        QuestionDTO questionDTO = new QuestionDTO();
        BeanUtils.copyProperties(question, questionDTO);
        questionDTO.setDescription("");
        questionDTO.setUser(user);
        questionDTOList.add(questionDTO);
    }

    // 获取置顶问题列表
    List<QuestionDTO> stickies = questionCache.getStickies();

    // 如果置顶问题列表不为空,则将其添加到问题列表的开头
    if (stickies != null && stickies.size() != 0) {
        questionDTOList.addAll(0, stickies);
    }

    // 设置数据并返回分页结果
    paginationDTO.setData(questionDTOList);
    return paginationDTO;
}

Fitten Code

/**
 * 获取分页数据的方法
 * 
 * @param search 搜索关键词
 * @param tag 标签
 * @param sort 排序方式
 * @param page 当前页码
 * @param size 每页大小
 * @return 分页数据传输对象
 */
public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {

    // 处理搜索关键词
    if (StringUtils.isNotBlank(search)) {
        String[] tags = StringUtils.split(search, " ");
        search = Arrays
                .stream(tags)
                .filter(StringUtils::isNotBlank)
                .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.joining("|"));
    }

    // 创建分页数据传输对象
    PaginationDTO paginationDTO = new PaginationDTO();

    Integer totalPage;

    // 创建问题查询数据传输对象
    QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
    questionQueryDTO.setSearch(search);
    if (StringUtils.isNotBlank(tag)) {
        tag = tag.replace("+", "").replace("*", "").replace("?", "");
        questionQueryDTO.setTag(tag);
    }

    // 设置排序方式
    for (SortEnum sortEnum : SortEnum.values()) {
        if (sortEnum.name().toLowerCase().equals(sort)) {
            questionQueryDTO.setSort(sort);

            if (sortEnum == SortEnum.HOT7) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
            }
            if (sortEnum == SortEnum.HOT30) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
            }
            break;
        }
    }

    // 获取总记录数
    Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);

    if (totalCount % size == 0) {
        totalPage = totalCount / size;
    } else {
        totalPage = totalCount / size + 1;
    }

    // 校验页码
    if (page < 1) {
        page = 1;
    }
    if (page > totalPage) {
        page = totalPage;
    }

    // 设置分页信息
    paginationDTO.setPagination(totalPage, page);
    Integer offset = page < 1 ? 0 : size * (page - 1);
    questionQueryDTO.setSize(size);
    questionQueryDTO.setPage(offset);
    List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);
    List<QuestionDTO> questionDTOList = new ArrayList<>();

    // 转换问题数据
    for (Question question : questions) {
        User user = userMapper.selectByPrimaryKey(question.getCreator());
        QuestionDTO questionDTO = new QuestionDTO();
        BeanUtils.copyProperties(question, questionDTO);
        questionDTO.setDescription("");
        questionDTO.setUser(user);
        questionDTOList.add(questionDTO);
    }
    List<QuestionDTO> stickies = questionCache.getStickies();
    if (stickies != null && stickies.size() != 0) {
        questionDTOList.addAll(0, stickies);
    }
    paginationDTO.setData(questionDTOList);
    return paginationDTO;
}

代码优化

TONGYI-lingma

代码审查与优化建议
1. 重复过滤空白字符串
在处理search时两次进行了非空白字符串的过滤这可能导致不必要的性能开销
修改建议 只保留一次过滤操作即可
修改后的代码
if (StringUtils.isNotBlank(search)) {
    String[] tags = StringUtils.split(search, " ");
    search = Arrays
            .stream(tags)
            .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
            .filter(StringUtils::isNotBlank)
            .collect(Collectors.joining("|"));
}

2. 枚举匹配的效率问题
在匹配sort时使用了循环遍历所有枚举值的方式当枚举数量较多时这会降低性能
修改建议 可以考虑使用枚举映射提高查找效率
修改后的代码
Map<String, SortEnum> sortEnumMap = Arrays.stream(SortEnum.values())
        .collect(Collectors.toMap(e -> e.name().toLowerCase(), e -> e));
SortEnum sortEnum = sortEnumMap.getOrDefault(sort.toLowerCase(), SortEnum.DEFAULT);
questionQueryDTO.setSort(sortEnum.name());
questionQueryDTO.setTime(sortEnum == SortEnum.HOT7 ? System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7 :
                        sortEnum == SortEnum.HOT30 ? System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30 : 0);

3. 计算总页数的逻辑
计算总页数时可以简化逻辑以提高可读性
修改建议 使用Math类提供的方法来简化计算
修改后的代码
Integer totalPage = Math.toIntExact(Math.ceil((double) totalCount / size));

4. 边界条件处理
当page小于1或大于totalPage时直接设置为1或totalPage但没有明确提示用户
修改建议 添加日志记录或返回值以便告知调用者发生了边界调整
修改后的代码
if (page < 1) {
    page = 1;
    // 可以添加日志记录或返回值,例如:
    // logger.warn("Page number is less than 1, set to 1.");
}

if (page > totalPage) {
    page = totalPage;
    // 可以添加日志记录或返回值,例如:
    // logger.warn("Page number is greater than total pages, set to total pages.");
}

5. 数据库查询参数设置
在设置questionQueryDTO的size和page属性时命名不太直观
修改建议 将page改为offset使参数名称更符合其实际用途
修改后的代码
Integer offset = page < 1 ? 0 : size * (page - 1);
questionQueryDTO.setSize(size);
questionQueryDTO.setOffset(offset); // 修改为offset

6. 拼接tag时的清理操作
对tag进行清理操作时可以考虑使用正则表达式一次性替换多个字符
修改建议 使用正则表达式替换多个特殊字符
修改后的代码
if (StringUtils.isNotBlank(tag)) {
    tag = tag.replaceAll("[+*?]", "");
    questionQueryDTO.setTag(tag);
}

Baidu Comate

代码问题分析

+ 逻辑上当search参数包含特殊字符"+", "*", "?"代码会替换这些字符但是这种替换方式可能不适用于所有情况因为它会无差别地替换所有出现的特殊字符而不仅仅是查询关键词中的
+ 在处理sort参数时使用了SortEnum.values()来遍历枚举值然后逐个比较这种方法效率不高特别是当枚举值较多时
+ 在计算总页数时如果size为0会导致除以0的异常代码中没有对此进行处理
+ 在从数据库中获取用户信息时对于每个问题都执行了一次查询这可能导致N+1查询问题影响性能
+ stickies列表在添加到questionDTOList时没有检查是否有重复的元素可能会导致数据重复
+ 代码风格上部分变量命名可以更加明确例如search在处理后实际上变成了正则表达式可以重命名为searchRegex或类似名称以提高代码可读性

public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {
    // 处理search参数,构建正则表达式
    String searchRegex = "";
    if (StringUtils.isNotBlank(search)) {
        String[] tags = StringUtils.split(search, " ");
        searchRegex = Arrays
                .stream(tags)
                .filter(StringUtils::isNotBlank)
                .map(t -> t.replaceAll("[+*?]", "")) // 使用replaceAll替换所有特殊字符
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.joining("|"));
    }

    PaginationDTO paginationDTO = new PaginationDTO();
    Integer totalPage;

    QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
    questionQueryDTO.setSearch(searchRegex);
    // ... [省略部分代码,与原始代码相同] ...

    // 优化sort参数处理
    SortEnum sortEnum = null;
    for (SortEnum se : SortEnum.values()) {
        if (se.name().toLowerCase().equals(sort)) {
            sortEnum = se;
            break;
        }
    }
    if (sortEnum != null) {
        questionQueryDTO.setSort(sort);
        // ... [省略部分代码,与原始代码相同] ...
    }

    Integer totalCount = questionExtMapper.countBySearch(questionQueryDTO);
    // 处理size为0的情况,避免除以0的异常
    if (size == 0) {
        size = 1; // 或者抛出一个更明确的异常,如IllegalArgumentException
    }
    // ... [省略部分代码,与原始代码相同] ...

    // 优化N+1查询问题,可以考虑批量获取用户信息,或者使用JOIN查询在数据库层面解决
    Map<Long, User> userMap = new HashMap<>(); // 用于缓存用户信息,避免重复查询
    List<QuestionDTO> questionDTOList = new ArrayList<>();
    for (Question question : questions) {
        User user = userMap.computeIfAbsent(question.getCreator(), id -> userMapper.selectByPrimaryKey(id));
        QuestionDTO questionDTO = new QuestionDTO();
        BeanUtils.copyProperties(question, questionDTO);
        questionDTO.setDescription("");
        questionDTO.setUser(user);
        questionDTOList.add(questionDTO);
    }
    // ... [省略部分代码,与原始代码相同] ...
    return paginationDTO;
}

Fitten Code

// 这块分 寻找bug 和 优化代码 两块

// 寻找 bug
这段代码中存在以下几个可能导致错误行为的问题
1. 搜索关键字处理问题
if (StringUtils.isNotBlank(search)) {
    String[] tags = StringUtils.split(search, " ");
    search = Arrays
            .stream(tags)
            .filter(StringUtils::isNotBlank)
            .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
            .filter(StringUtils::isNotBlank)
            .collect(Collectors.joining("|"));
}
这里在处理搜索关键字时使用了replace("+", "").replace("*", "").replace("?", "")这可能会导致一些特殊字符没有被正确转义或处理可能会影响后续的查询结果另外如果search参数是一个复杂字符串这段处理方式可能会导致字符串被意外改变

2. 分页参数校验
if (page < 1) {
    page = 1;
}
if (page > totalPage) {
    page = totalPage;
}
虽然这段代码对page参数进行了校验但是如果totalPage为零这段代码会将page设为1这在某些情况下可能会导致错误或异常行为应当首先处理边界情况例如当totalPage为零时避免分页逻辑

3. 偏移量计算
Integer offset = page < 1 ? 0 : size * (page - 1);
这段代码中page < 1 ? 0 : size * (page - 1)的条件判断似乎多余因为在前面的校验中已经保证了page的最小值为1所以这里直接使用size * (page - 1)即可

4. SQL注入风险
在处理search和tag参数时如果未能正确校验和转义这些参数有可能引发SQL注入风险特别是在处理搜索关键字和标签时

5. 并发问题
List<QuestionDTO> stickies = questionCache.getStickies();
if (stickies != null && stickies.size() != 0) {
    questionDTOList.addAll(0, stickies);
}
这段代码从缓存中获取置顶问题并加入到结果列表的最前面如果多个线程同时访问这段代码可能会引发并发问题导致缓存中的数据被修改或读取时出现错误应考虑对缓存的并发访问进行适当处理确保线程安全
虽然这些问题各自可能不会直接导致非常明显的错误但在特定条件下可能会导致结果不正确或系统异常建议针对这些问题仔细检查和完善代码


// 优化代码
以下是经过优化后的代码
public PaginationDTO list(String search, String tag, String sort, Integer page, Integer size) {
        // 过滤和处理搜索关键词
        if (StringUtils.isNotBlank(search)) {
            search = Arrays.stream(StringUtils.split(search, " "))
                    .map(t -> t.replaceAll("[+*?]", ""))
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.joining("|"));
        }

        PaginationDTO paginationDTO = new PaginationDTO();
        QuestionQueryDTO questionQueryDTO = new QuestionQueryDTO();
        questionQueryDTO.setSearch(search);

        if (StringUtils.isNotBlank(tag)) {
            questionQueryDTO.setTag(tag.replaceAll("[+*?]", ""));
        }

        SortEnum sortEnum = Arrays.stream(SortEnum.values())
                .filter(se -> se.name().equalsIgnoreCase(sort))
                .findFirst()
                .orElse(null);
  
        if (sortEnum != null) {
            questionQueryDTO.setSort(sort);
            if (sortEnum == SortEnum.HOT7) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 7);
            } else if (sortEnum == SortEnum.HOT30) {
                questionQueryDTO.setTime(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
            }
        }

        int totalCount = questionExtMapper.countBySearch(questionQueryDTO);
        int totalPage = (int) Math.ceil((double) totalCount / size);
        
        page = Math.max(1, Math.min(page, totalPage));

        paginationDTO.setPagination(totalPage, page);
        int offset = size * (page - 1);
        questionQueryDTO.setSize(size);
        questionQueryDTO.setPage(offset);
        
        List<Question> questions = questionExtMapper.selectBySearch(questionQueryDTO);
        List<QuestionDTO> questionDTOList = new ArrayList<>();
        
        for (Question question : questions) {
            User user = userMapper.selectByPrimaryKey(question.getCreator());
            if (user == null) {
                throw new CustomizeException(CustomizeErrorCode.USER_NOT_FOUND);
            }
            QuestionDTO questionDTO = new QuestionDTO();
            BeanUtils.copyProperties(question, questionDTO);
            questionDTO.setDescription("");  // 清除描述内容
            questionDTO.setUser(user);
            questionDTOList.add(questionDTO);
        }
        
        List<QuestionDTO> stickies = questionCache.getStickies();
        if (stickies != null && !stickies.isEmpty()) {
            questionDTOList.addAll(0, stickies);
        }

        paginationDTO.setData(questionDTOList);
        return paginationDTO;
    }
优化和改进点
1. 简化标签和搜索关键词处理 使用正则表达式 replaceAll("[+*?]", "") 一步到位替换不合法字符简化了代码
2. 简化排序枚举的查找逻辑 使用 stream().filter().findFirst().orElse(null) 简化排序枚举的查找逻辑
3. 去除冗余的 TotalPage 计算 使用 Math.ceil 计算总页数减少了冗余的逻辑
4. 简化页码检查逻辑 使用 Math.max  Math.min 减少冗余的代码
5. 增加用户不存在时的错误处理 当查询用户信息失败时抛出 USER_NOT_FOUND 异常提高了系统的健壮性
通过这些优化代码变得更加简洁有效并增强了错误处理能力