如何写出优雅的代码

news2024/10/3 8:30:19

最近在做代码审查时,发现一个问题,就是代码不够优雅。代码可以有bug,但不能不优雅,毕竟代码不只是运行程序,凡是需要维护的代码都是给人看的,你的代码风格侧面反映了你的编码习惯、思维逻辑和专业性。

那么如何让代码更优雅?逻辑性和可读性更强?我刚工作时对这个问题也很迷茫,然后当时看了《代码整洁之道》这本书,刚读到变量命名部分就虎躯一震,这就是我要找的答案。今天我结合以往开发经历从如下几方面介绍如何让代码更优雅:

  1. 命名

  1. 注释

  1. 方法

  1. 参数传值及校验

  1. 异常捕获

  1. 重复代码

1.命名

变量和函数名要简洁、见名知意,当然长点也无所谓,整点人类能看懂的命名。

示例内容

反例

正例

例如定义商品list,命名能写明白点就不要省那俩字母,能省出来啥?

list; gList; gdList;

goodsList;

例如定义一个类型,这种毫无含义的命名,写起来很快,等自己回头看都不知道是啥玩意。

t; type1; type2; typeOne; typeTwo;

businessType; productType;

messageType;

还有挂羊头卖狗肉的命名,例如定义一个订单Map,但名字是xxList,亲眼所见。

orderMap;

orderList;

2.注释

注释的作用是告诉读者代码逻辑和意义,但经常会有很多注释在说一些废话,如果写了骚逻辑要加上注释说清楚。

反例

正例

备注

//状态

String status;

//状态 0=待审批 1=已审批

String status;

这些变量上过学的都知道什么意思,反例注释属于废话,应该把参数意义和逻辑讲明白。

//定义变量sleepFlag

int sleepFlag;

//定义变量flag,当flag=0时线程睡眠10秒,核心代码,下次优化找客户要钱

int sleepFlag;

3.方法

方法对应的是一个行为,每个方法应该独立做一件事,如果要做多件事应该通过调用多个不同方法实现,好的方法应该是高内聚、低耦合。说白了就是别把一大段错综复杂的逻辑都塞到一个方法里,按照行为拆一拆,拆出一些能复用的方法。

但会有很多情况是一个很长的Service方法写完所有逻辑,做很多事情,甚至在Controller层这样做,这样导致的结果是当时写起来简单,代码复用性差,后续扩展改造麻烦。

举一个简单的例子说明ReUse的作用:

假如有一个订单服务OrderService,订单明细OrderDetailService,商品GoogsService。我需要查询某一笔订单的商品信息。假如我无视方法独立做一件事的原则,可能会写出如下代码:

public class OrderService {

    private OrderMapper orderMapper;
    
    private OrderDetailMapper orderDetailMapper;

    private GoogsMapper goodsMapper;
    
    public List<GoogsList> getGoogsListByOrder(String orderNo) {
        // 查询订单
        Order order = orderMapper.selectByNo(orderNo);
        if (order == null) {
            //返回订单不存在
        }
        // 查询订单明细列表
        List<OrderDetail> orderDetails = orderDetailMapper.list(orderNo);

        // 查询商品列表
        List<Goods> goodsList = orderDetails.stream().map(e-> {
            return goodsService.selectById(e.getGoodsId);
        }).collect(Collectors.toList());

        return goodsList;
    }
}

我们思考以下问题:

1.假如另外也有功能需要查询订单、查询订单明细、查询商品列表功能,怎么复用?

2.如果其他Service也按这种风格引用OrderDetailMapper和GoodsMapper,后续如果发现有bug或者要加校验,怎么统一处理?只能人工一处处修改,漏改了怎么办?

3.在OrderService中直接引入其他Mapper,那这些Mapper对应的Service干啥?反过来调你的Mapper吗?你住我家,我住你家?

从上面代码的问题可以看到,代码耦合性非常强,可复用性差。之前有些小伙伴执意写上面这种代码,我也没能说服他们,当然代码质量也不尽如人意,不知道他们现在是不是还这么执着。

看一下调整后的代码:

public class OrderService {

    private OrderMapper orderMapper;
    
    private OrderDetailService orderDetailService;

    private GoodsService goodsService;
    
    public List<GoogsList> getGoogsListByOrder(String orderNo) {

        selectByNo(orderNo);
            
        // 查询订单明细列表
        List<OrderDetail> orderDetails = OrderDetailService.list(orderNo);

        // 查询商品列表
        List<Goods> goodsList = orderDetails.stream().map(e-> {
            return goodsService.selectById(e.getGoodsId);
        }).collect(Collectors.toList());

        return goodsList;
    } 

    public Order selectByNo(String orderNo) {
         // 查询订单
        Order order = orderMapper.selectByNo(orderNo);
        if (order == null) {
            //返回订单不存在
            throw new BusinessException("40001", "订单不存在!")
        }
        return order;
    }
}

public class OrderDetailService {
    
    private OrderDetailMapper orderDetailMapper;

    /**
     * 查询订单明细列表
     */
    public List<OrderDetail> list(String orderNo) {
        //这里可以统一做一些判空、规则校验和处理
        return orderDetailMapper.list(orderNo);
    }
}

public class GoodsService {
    
    private GoogsMapper goodsMapper;

    public Goods selectById(String goodsId) {
        //这里可以统一做一些判空、规则校验和处理
        return goodsMapper.selectById(e.getGoodsId);
    }
}

之前有小伙伴问我,为什么要给每个表Mapper都创建对应的Service?拿着Mapper直接调用不挺好吗?

我回答是你写的时候是挺好,省事,但好不了几天,比如上面代码里你可以在每个service方法里做一些检验和处理,如果没有做封装,重复代码多了,那就得挨个改。而且会发现重复代码复制太多,年轻人你把握不住啊,逻辑都整不明白了。所以该封装还是要封装,不要拿着mapper到处调用满天飞。

当别人拿着复制的代码愁眉苦脸改bug,无法复用,你拿过来service方法轻松一调用,逻辑都内聚在service方法里,想调整很简单,这不比他们优雅?

4.参数传值及校验

参数传值:

  1. 当方法参数个数大于3个时,就要考虑封装到对象DTO里传递,不然参数列表巨长,打印日志还费劲。

  1. 不要用Map或者JsonObject作为接受参数,谁知道你这里面放了什么东西?

DTO示例如下:

@Data 
@Accessors(chain  = true) 
public class PsbcAftLoanCreateConDTO {
    /**
     * 业务流水号
     */
    @NotBlank(message = "业务号不能为空!")
    private String applCde;
 
    /**
     * 贷后变更申请编号
     */
    @NotNull(message = "贷后变更申请编号不能为空!")
    private Long signNo;
 
    /**
     * 合同环节
     */
    @NotNull(message = "合同环节不能为空!")
    private ContractLinkEnum ctrLink;
 
    /**
     * 是否重签 1=重签;0=非重签;
     */
    @Range(min = 0, max = 1, message = "是否重签只允许0和1")
    private Integer resignFlag = 0;
 
    @Override
    public String toString() {
        return "PsbcAftLoanCreateConDTO{" +
                "applCde='" + applCde + '\'' +
                ", signNo=" + signNo +
                ", ctrLink=" + ctrLink.toString() +
                ", resignFlag=" + resignFlag +
                '}';
    }
}

示例中同时使用了hibernate validation参数校验,代替如下繁琐的校验代码:

if (acctBindParam.getAccChgSeq() == null) {
    throw new BusinessException("-1", "变更流水号为空");
}
if (StringUtil.isEmpty(acctBindParam.getDataSrc())) {
    throw new BusinessException("-1", "数据来源为空");
}
if (StringUtil.isEmpty(acctBindParam.getStartInstuCde())) {
    throw new BusinessException("-1", "资金方为空");
}
if (StringUtil.isEmpty(acctBindParam.getIndivMobile())) {
    throw new BusinessException("-1", "短信接收手机号为空");
}
if (StringUtil.isEmpty(acctBindParam.getIndivMobile())) {
    throw new BusinessException("-1", "短信接收人为空");
}
if (CollectionUtils.isEmpty(acctBindParam.getApptList())) {
    throw new BusinessException("-1", "用户信息为空");
}

当然还需要统一捕获校验异常,封装为响应对象返回给前端,不再赘述。可参考之前的文章《spring参数校验消除重复代码》

5.异常捕获

使用HandlerExceptionResolver统一捕获业务异常,并转换成带有错误码和错误提示的ModuleAndView返回给前端。

千万不要在方法里层层返回进行判断,大概就是下面这个样子,代码耦合,扩展性差:

统一异常捕获是如何做到解耦呢,大体就是这么个意思:

6.重复代码

重复代码让维护者深恶痛绝,在IDEA里阿里巴巴规约也会给重复代码标注晃眼的波浪线,那么为什么重复代码还是这么泛滥呢?

也许是因为对于开发者来说这是最省事的写法,但是,出来混总是要还的,除非你跑路够快,否则还是免不了后期去维护这些重复代码。而且一时的懒惰带来的是短期的舒适,但失去的是思考和成长的机会。

举个栗子,从下图可以看到若干个uploadXXX方法里面有大量的重复代码,如果这些代码出问题需要维护,那么就需要挨个找到重复代码逐个修改,重复代码越多,代码逻辑就越乱,维护成本更高,改错、漏改风险极高。

在实际开发中一定不要偷懒,把基本行为封装通用方法,通过封装消除重复代码,代码逻辑会更清晰,更容易维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/337601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【强化学习】马尔可夫决策过程MDP

1.马尔可夫决策过程MDP 1.1 MDP五元组 MDP<S,A,P,R,γ>MDP<\mathcal{S},\mathcal{A},\mathcal{P},\mathcal{R},\mathcal{\gamma}>MDP<S,A,P,R,γ>&#xff0c;其中&#xff1a; S\mathcal{S}S&#xff1a;状态空间A\mathcal{A}A&#xff1a;动作空间P\mathc…

【VictoriaMetrics】VictoriaMetrics单机版部署(二进制版)

1、下载安装包git路径,本文基于1.87.1版本 进入git地址 :https://github.com/VictoriaMetrics/VictoriaMetrics/tags 2、下载其中linux下的 amd64架构

【数据结构】单向链表的练习题

目录 前言 1、删除链表中等于给定值val的所有节点。 【题目描述】 【代码示例】 【 画图理解】 2、反转一个点链表 【题目描述】 【 代码思路】 【代码示例】 【画图理解】 3、给定一个带有头节点head的非空单链表&#xff0c;返回链表的中间节点&#xff0c;如果有两个…

三维重建——NeuralRecon项目源码解读

代码链接见文末 首先,需要指明的是NeuralRecon项目不支持windows,主要是使用到的例如torchsprase等不支持windows,您也可以在网上查找相应的替代方法。 1.数据与环境配置 ScanNet数据的获取首先需要向作者发送邮件,作者会回复一个下载的脚本,在提供的代码中,提供了…

Mysql数据库09——分组聚合函数

类似pandas里面的groupby函数&#xff0c;SQL里面的GROUP BY子句也是可以达到分组聚合的效果。 常用的聚合函数有COUNT(),SUM(),AVG(),MAX(),MIN()&#xff0c;其用法看名字都看的出来&#xff0c;下面一一介绍 聚合函数 COUNT()计数 统计student表中计科系学生的人数。 SE…

基于nodejs+vue疫情网课管理系统

疫情网课也都将通过计算机进行整体智能化操作,对于疫情网课管理系统所牵扯的管理及数据保存都是非常多的,例如管理员&#xff1a;首页、个人中心、学生管理、教师管理、班级管理、课程分类管理、课程表管理、课程信息管理、作业信息管理、请假信息管理、上课签到管理、论坛交流…

企业微信应用授权,第一次不授权手机号后如何再次开启

文章目录前言一、关于企业微信应用授权1、新建一个应用2、企业微信应用授权二、第一次拒绝后如何手动开启总结前言 简单记录一下&#xff1a; 最近做了几个企业微信应用授权web&#xff0c;和普通微信公众号授权差不多&#xff0c;记录一下 一、关于企业微信应用授权 1、新建…

失手删表删库,赶紧跑路?!

在数据资源日益宝贵的数字时代公司最怕什么&#xff1f;人还在&#xff0c;库没了是粮库、车库&#xff0c;还是小金库&#xff1f;实际上&#xff0c;这里的“库”是指的数据库Ta是公司各类信息的保险柜小到企业官网和客户信息大到金融机构的资产数据和国家秘密即便没有跟数据…

Linux服务器开发-2. Linux多进程开发

文章目录1. 进程概述1.1 程序概览1.2 进程概念1.3 单道、多道程序设计1.4 时间片1.5 并行与并发1.6 进程控制块&#xff08;PCB&#xff09;2. 进程的状态转换2.1 进程的状态2.2 进程相关命令查看进程实时显示进程动态杀死进程进程号和相关函数3. 进程的创建-fork函数3.1 进程创…

抖音线索信息自动汇总到SeaTable流程搭建示例

每当抖音有新意向用户添加时&#xff0c;往往被企业视为意向客户&#xff0c;常需要运营人员查看后同步到SeaTable表单系统进行汇总&#xff0c;但运营人员时常会遗忘&#xff0c;并且难免会遗漏掉部分信息&#xff0c;导致部分意向客户无人跟进&#xff0c;最终流失。 那么&a…

GO语音-切片使用的雷区与性能优化相关

文章目录前言一、切片是什么&#xff1f;二、切片使用注意项1.避免复制数组2.切片初始化3.切片GC三、切片使用注意什么1. 大家来思考一个代码示例&#xff1a;2. 修改切片的值3. 降低切片重复申请内存总结前言 在 Go 语言中&#xff0c;切片(slice)可能是使用最为频繁的数据结…

数据结构 | 线性表

&#x1f525;Go for it!&#x1f525; &#x1f4dd;个人主页&#xff1a;按键难防 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4d6;系列专栏&#xff1a;数据结构与算法 &#x1f52…

Wise-IoU 作者导读:基于动态非单调聚焦机制的边界框损失

论文地址&#xff1a;Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism GitHub&#xff1a;https://github.com/Instinct323/wiou 摘要&#xff1a;目标检测作为计算机视觉的核心问题&#xff0c;其检测性能依赖于损失函数的设计。边界框损失函数作为…

153、【动态规划】leetcode ——416. 分割等和子集:滚动数组(C++版本)

题目描述 原题链接&#xff1a;1049. 最后一块石头的重量 II 解题思路 本题要找的是最小重量&#xff0c;我们可以将石头划分成两个集合&#xff0c;当两个集合的重量越接近时&#xff0c;相减后&#xff0c;可达到的装量就会是最小&#xff0c;此时本题的思路其实就类似于 4…

【光伏功率预测】基于EMD-PCA-LSTM的光伏功率预测模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MSI_MSI-X中断之体验与使用

MSI_MSI-X中断之体验与使用 文章目录MSI_MSI-X中断之体验与使用1. 怎么发出MSI/MSI-X中断1.1 在RK3399上体验1.1.1 安装工具1.1.2 查看设备MSI-X信息1.1.3 验证MSI-X信息2. 怎么使用MSI/MSI-X3. MSI/MSI-X中断源码分析3.1 IRQ Domain创建流程3.1.1 GIC3.1.2 ITS3.1.3 PCI MSI3.…

【Flutter】【Unity】使用 Flutter + Unity 构建(AR 体验工具包)

使用 Flutter Unity 构建&#xff08;AR 体验工具包&#xff09;【翻译】 原文&#xff1a;https://medium.com/potato/building-with-flutter-unity-ar-experience-toolkit-6aaf17dbb725 由于屡获殊荣的独立动画工作室 Aardman 与讲故事的风险投资公司 Fictioneers&#x…

最大公约数:常用的四大算法求解最大公约数,分解质因数法、短除法、辗转相除法、更相减损法。

常用的四大算法求解最大公约数&#xff0c;分解质因数法、短除法、辗转相除法、更相减损法。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#x…

网络基础-虚拟化工具-网桥

系列文章目录 本系列文章主要是回顾和学习工作中常用的网络基础命令&#xff0c;在此记录以便于回顾。 该篇文章主要是讲解虚拟化的工具网桥相关的概念和常用命令 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录系…

C++之多态【详细总结】

前言 想必大家都知道面向对象的三大特征&#xff1a;封装&#xff0c;继承&#xff0c;多态。封装的本质是&#xff1a;对外暴露必要的接口&#xff0c;但内部的具体实现细节和部分的核心接口对外是不可见的&#xff0c;仅对外开放必要功能性接口。继承的本质是为了复用&#x…