找回密码
 立即注册
首页 业界区 业界 都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑 ...

都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛

驳嗦 7 天前
开心一刻

今天心情不好,给哥们发语音
我:哥们,晚上出来喝酒聊天吧
哥们:咋啦,心情不好?
我:嗯,刚刚在公交车上看见前女友了
哥们:然后呢?
我:给她让座时,发现她怀孕了...
哥们:所以难受了?
我:不是她怀孕让我难受,是她怀孕还坐公交车让我难受
哥们:不是,她跟着你就不用坐公交车了?不还是也要坐,有区别吗?
我默默的挂断了语音,心情更难受了
1.gif
Java开发手册

作为一个 javaer,我们肯定看过 Alibaba 的 Java开发手册,作为国内Java开发领域的标杆性编码规范,我们或多或少借鉴了其中的一些规范,其中有一点
2.png
我印象特别深,也一直在奉行,自己还从未试过用 is 作为布尔类型变量的前缀,不知道会有什么坑;正好前段时间同事这么用了,很不幸,他挖坑,我踩坑,阿西吧!
3.png
is前缀的布尔变量有坑

为了复现问题,我先简单搞个 demo;调用很简单,服务 workflow 通过 openfeign 调用 offline-sync,代码结构如下
4.png
qsl-data-govern-common:整个项目的公共模块
qsl-offline-sync:离线同步

  • qsl-offline-sync-api:向外提供 openfeign 接口
  • qsl-offline-sync-common:离线同步公共模块
  • qsl-offline-sync-server:离线同步服务
qsl-workflow:工作流

  • qsl-workflow-api:向外提供 openfeign 接口,暂时空实现
  • qsl-workflow-common:工作流公共模块
  • qsl-workflow-server:工作流服务
完整代码:qsl-data-govern
qsl-offline-sync-server 提供删除接口
  1. /**
  2. * @author 青石路
  3. */
  4. @RestController
  5. @RequestMapping("/task")
  6. public class SyncTaskController {
  7.     private static final Logger LOG = LoggerFactory.getLogger(SyncTaskController.class);
  8.     @PostMapping("/delete")
  9.     public ResultEntity<String> delete(@RequestBody SyncTaskDTO syncTask) {
  10.         // TODO 删除处理
  11.         LOG.info("删除任务[taskId={}]", syncTask.getTaskId());
  12.         return ResultEntity.success("删除成功");
  13.     }
  14. }
复制代码
qsl-offline-sync-api 对外提供 openfeign 接口
  1. /**
  2. * @author 青石路
  3. */
  4. @FeignClient(name = "data-govern-offline-sync", contextId = "dataGovernOfflineSync", url = "${offline.sync.server.url}")
  5. public interface OfflineSyncApi {
  6.     @PostMapping(value = "/task/delete")
  7.     ResultEntity<String> deleteTask(@RequestBody SyncTaskDTO syncTaskDTO);
  8. }
复制代码
qsl-workflow-server 调用 openfeign 接口
  1. /**
  2. * @author 青石路
  3. */
  4. @RestController
  5. @RequestMapping("/definition")
  6. public class WorkflowController {
  7.     private static final Logger LOG = LoggerFactory.getLogger(WorkflowController.class);
  8.     @Resource
  9.     private OfflineSyncApi offlineSyncApi;
  10.     @PostMapping("/delete")
  11.     public ResultEntity<String> delete(@RequestBody WorkflowDTO workflow) {
  12.         LOG.info("删除工作流[workflowId={}]", workflow.getWorkflowId());
  13.         // 1.查询工作流节点,查到离线同步节点(taskId = 1)
  14.         // 2.删除工作流节点,删除离线同步节点
  15.         ResultEntity<String> syncDeleteResult = offlineSyncApi.deleteTask(new SyncTaskDTO(1L));
  16.         if (syncDeleteResult.getCode() != 200) {
  17.             LOG.error("删除离线同步任务[taskId={}]失败:{}", 1, syncDeleteResult.getMessage());
  18.             ResultEntity.fail(syncDeleteResult.getMessage());
  19.         }
  20.         return ResultEntity.success("删除成功");
  21.     }
  22. }
复制代码
逻辑是不是很简单?我们启动两个服务,然后发起 http 请求
POST http://localhost:8081/data-govern/workflow/definition/delete
Content-Type: application/json
{
"workflowId": 99
}
此时 qsl-offline-sync-server 日志输出如下
2025-06-30 14:53:06.165|INFO|http-nio-8080-exec-4|25|c.q.s.s.controller.SyncTaskController   :删除任务[taskId=1]
至此,一切都很正常,第一版也是这么对接的;后面 offline-sync 进行调整,删除接口增加了一个参数:isClearData
  1. public class SyncTaskDTO {
  2.     public SyncTaskDTO(){}
  3.     public SyncTaskDTO(Long taskId, Boolean isClearCache) {
  4.         this.taskId = taskId;
  5.         this.isClearData = isClearCache;
  6.     }
  7.     private Long taskId;
  8.     private Boolean isClearData = false;
  9.     public Long getTaskId() {
  10.         return taskId;
  11.     }
  12.     public void setTaskId(Long taskId) {
  13.         this.taskId = taskId;
  14.     }
  15.     public Boolean getClearData() {
  16.         return isClearData;
  17.     }
  18.     public void setClearData(Boolean clearData) {
  19.         isClearData = clearData;
  20.     }
  21. }
复制代码
然后实现对应的逻辑
  1. /**
  2. * @author 青石路
  3. */
  4. @RestController
  5. @RequestMapping("/task")
  6. public class SyncTaskController {
  7.     private static final Logger LOG = LoggerFactory.getLogger(SyncTaskController.class);
  8.     @PostMapping("/delete")
  9.     public ResultEntity<String> delete(@RequestBody SyncTaskDTO syncTask) {
  10.         // TODO 删除处理
  11.         LOG.info("删除任务[taskId={}]", syncTask.getTaskId());
  12.         if (syncTask.getClearData()) {
  13.             LOG.info("清空任务[taskId={}]历史数据", syncTask.getTaskId());
  14.             // TODO 清空历史数据
  15.         }
  16.         return ResultEntity.success("删除成功");
  17.     }
  18. }
复制代码
调整完之后,同事通知我,让我做对 qsl-workflow 做对应的调整。调整很简单,qsl-workflow 删除时直接传 true 即可
  1. /**
  2. * @author 青石路
  3. */
  4. @RestController
  5. @RequestMapping("/definition")
  6. public class WorkflowController {
  7.     private static final Logger LOG = LoggerFactory.getLogger(WorkflowController.class);
  8.     @Resource
  9.     private OfflineSyncApi offlineSyncApi;
  10.     @PostMapping("/delete")
  11.     public ResultEntity<String> delete(@RequestBody WorkflowDTO workflow) {
  12.         LOG.info("删除工作流[workflowId={}]", workflow.getWorkflowId());
  13.         // 1.查询工作流节点,查到离线同步节点(taskId = 1)
  14.         // 2.删除工作流节点,删除离线同步节点
  15.         // 删除离线同步任务,isClearData直接传true
  16.         ResultEntity<String> syncDeleteResult = offlineSyncApi.deleteTask(new SyncTaskDTO(1L, true));
  17.         if (syncDeleteResult.getCode() != 200) {
  18.             LOG.error("删除离线同步任务[taskId={}]失败:{}", 1, syncDeleteResult.getMessage());
  19.             ResultEntity.fail(syncDeleteResult.getMessage());
  20.         }
  21.         return ResultEntity.success("删除成功");
  22.     }
  23. }
复制代码
调整完成之后,发起 http 请求,发现历史数据没有被清除,看日志发现
LOG.info("清空任务[taskId={}]历史数据", syncTask.getTaskId());
没有打印,参数明明传的是 true 吖!!!
offlineSyncApi.deleteTask(new SyncTaskDTO(1L, true));
这是哪里出了问题?
5.png
问题排查

因为 qsl-offline-sync-api 是直接引入的,并非我实现的,所以我第一时间找到了其实现者,反馈了问题后让其自测下;一开始他还很自信,说这么简单怎么会有问题
6.jpeg
当他启动 qsl-offline-sync-server 后,发起 http 请求
POST http://localhost:8080/data-govern/sync/task/delete
Content-Type: application/json
{
"taskId": 123,
"isClearData": true
}
发现 isClearData 的值是 false
7.png
此刻,疑问从我的额头转移到了他的额头上,他懵逼了,我轻松了。为了功能能够正常交付,我还是决定看下这个问题,没有了心理压力,也许更容易发现问题所在。第一眼看到 isClearData,我就隐约觉得有问题,所以我决定仔细看下 SyncTaskDTO 这个类,发现 isClearData 的 setter 和 getter 方法有点不一样
  1. private Boolean isClearData = false;
  2. public Boolean getClearData() {
  3.     return isClearData;
  4. }
  5. public void setClearData(Boolean clearData) {
  6.     isClearData = clearData;
  7. }
复制代码
方法名是不是少了 Is?带着这个疑问我找到了同事,问他 setter 、getter 为什么要这么命名?他说是 idea 工具自动生成的(也就是我们平时用到的idea自动生成setter、getter方法的功能)
8.png
我让他把 Is 补上试试
  1. private Boolean isClearData = false;
  2. public Boolean getIsClearData() {
  3.     return isClearData;
  4. }
  5. public void setIsClearData(Boolean isClearData) {
  6.     this.isClearData = isClearData;
  7. }
复制代码
发现传值正常了,他回过头看着我,我看着他,两人同时提问
他:为什么加了 Is 就可以了?
我:布尔类型的变量,你为什么要加 is 前缀?
问题延申

作为一个严谨的开发,不只是要知其然,更要知其所以然;关于
为什么加了 Is 就可以了
这个问题,我们肯定是要会上一会的;会这个问题之前,我们先来捋一下参数的流转,因为是基于 Spring MVC 实现的 Web 应用,所以我们可以这么问 deepseek
Spring MVC 是如何将前端参数转换成POJO的
能够查到如下重点信息
9.png
RequestResponseBodyMethodProcessor 的 resolveArgument
  1. /**
  2. * Throws MethodArgumentNotValidException if validation fails.
  3. * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
  4. * is {@code true} and there is no body content or if there is no suitable
  5. * converter to read the content with.
  6. */
  7. @Override
  8. public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
  9.         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  10.     parameter = parameter.nestedIfOptional();
  11.     Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
  12.     String name = Conventions.getVariableNameForParameter(parameter);
  13.     if (binderFactory != null) {
  14.         WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
  15.         if (arg != null) {
  16.             validateIfApplicable(binder, parameter);
  17.             if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
  18.                 throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
  19.             }
  20.         }
  21.         if (mavContainer != null) {
  22.             mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
  23.         }
  24.     }
  25.     return adaptArgumentIfNecessary(arg, parameter);
  26. }
复制代码
正是解析参数的地方,我们打个断点,再发起一次 http 请求
10.png
很明显,readWithMessageConverters 是处理并转换参数的地方,继续跟进去会来到 MappingJackson2HttpMessageConverter 的 readJavaType 方法
11.png
此刻我们可以得到,是通过 jackson 完成数据绑定与数据转换的。继续跟进,会看到 isClearData 的赋值过程
12.png
通过前端传过来的参数 isClearData 找对应的 setter方法是 setIsClearData,而非 setClearData,所以问题
为什么加了 Is 就可以了
是不是就清楚了?
问题解决


  • 按上述方式调整 isClearData 的 setter、getter 方法
    带上 is
    1. public Boolean getIsClearData() {
    2.     return isClearData;
    3. }
    4. public void setIsClearData(Boolean isClearData) {
    5.     this.isClearData = isClearData;
    6. }
    复制代码
  • 布尔类型的变量,不用 is 前缀
    可以用 if 前缀
    1. private Boolean ifClearData = false;
    2. public Boolean getIfClearData() {
    3.     return ifClearData;
    4. }
    5. public void setIfClearData(Boolean ifClearData) {
    6.     this.ifClearData = ifClearData;
    7. }
    复制代码
  • 可以结合 @JsonProperty 来处理
    1. @JsonProperty("isClearData")
    2. private Boolean isClearData = false;
    复制代码
总结


  • Spring MVC 对参数的绑定与转换,内容不同,采用的处理器也不同

    • form表单数据(application/x-www-form-urlencoded)
      处理器:ServletModelAttributeMethodProcessor
    • JSON 数据 (application/json)
      处理器:RequestResponseBodyMethodProcessor
      转换器:MappingJackson2HttpMessageConverter
    • 多部分文件 (multipart/form-data)
      处理器:MultipartResolver

  • POJO 的布尔类型变量,不要加 is 前缀
    命名不符合规范,集成第三方框架的时候就很容易出不好排查的问题
    成不了规范的制定者,那就老老实实遵循规范!


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册