引言
随着人工智能技术的迅猛发展,智能编程辅助工具正逐渐成为软件开发领域的重要组成部分。这些工具通过提供自动代码补全、智能提示、错误检测等功能极大地提升了开发者的效率与生产力。在众多智能编程辅助工具中,通义灵码、baidu comate 和 Fitten Code 是近期备受关注的几款产品。它们不仅代表了当前智能编程辅助工具的最高水平,而且各自拥有独特的功能和优势,旨在满足不同开发者的需求。
本文将对这三款工具进行简单的测评与对比,旨在帮助读者了解它们的特点、适用场景以及潜在的优势与局限性。无论你是经验丰富的开发人员还是编程新手,这些信息都将有助于你选择最适合自己的智能编程辅助工具,从而提升个人或团队的开发效率。接下来,我们将逐一探索这些工具的核心功能,并通过实际案例来展示它们如何在日常开发工作中发挥作用。
工具概述
功能对比
| 插件 | 私有化部署 | 代码/注释生成 | 问题排查及修复建议 | 代码优化及单测生成 | 本地工程/企业库问答 | IDE 集成 |
|---|---|---|---|---|---|---|
| 通义灵码 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Fitten Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| baidu Comate | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
更新频率
从 JetBrain 插件官网看,三个插件的都是在做持续更新,活跃度高,项目比较有价值。

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

使用体验
真实的业务开发中,编程工具更多以辅助开发发现确认问题的功能为主,而代码生成功能则相对较少,故这里只介绍问题排查及修复建议、代码优化及单测生成 两大块。
- 测试项目: https://github.com/codedrinker/community
- 测试语言:Java
- 测试设置:
- 不对插件做自定义配置
- 以第一次生成的结果为准
- 没充钱所以只做白嫖功能的测试
- 虽然有些功能可以通过对话的形式间接实现,但本次测试不考虑该途径,只直接测试插件提供的功能
功能截图

TONGYI-lingma 和 Baidu Comate 都有 gutter icons,使用起来会方便很多,Fitten Code 没有,所有功能放在一块,导致每次使用,需要手动选定代码块,比较麻烦。
使用小结
代码解释 fitten code 解释时会带上原始代码,相较其他两个会更清晰点
- TONGYI-lingma,在结果上,支持高纬度解释代码,或者更详细的解释代码,点个赞
单测生成 生成的代码都无法直接使用,按场景分用例,造成用例膨胀且用例数据都不好管理,最好是 TONGYI-lingma的断言 + Fitten Code的场景
- TONGYI-lingma,断言种类比较全,有注释,分场景生成测试代码
- Baidu Comate,断言很少,少注释,一般般
- Fitten Code,初版代码是错误最多的,少 package,少 import,但代码主体都是正确的,且场景最多,有异常测试,有中文注释
代码注释 TONGYI-lingma 效果最好
- TONGYI-lingma 生成的代码注释比较全且详细
- Baidu Comate 将注释生成分为函数注释和行间注释,优劣伯仲间吧
- Fitten Code 生成时需要框选指定代码(可以双击函数里空白行),有点麻烦,且生成的注释较简单,应该需要丰富点
代码优化 都不是很全面,就使用而言,TONGYI-lingma 更为流畅,但是函数拆分真的很香,算是给了一个思路
- Baidu Comate,有 函数拆分 功能,对重构代码会有些用处
- Fitten Code,分 寻找bug 和 优化代码 两个功能,但是寻找出来的bug,优化代码时候又没有完全解决,比较割裂
总体感觉 由于都是白嫖的,所以没办法测试代码库问答,没办法试试一键生成整个项目文档的快感,从官网描述上看,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 异常,提高了系统的健壮性。
通过这些优化,代码变得更加简洁、有效,并增强了错误处理能力。