Redian新闻
>
用1个月重构了同事写的烂代码,我总结出了15条重写烂代码的经验!

用1个月重构了同事写的烂代码,我总结出了15条重写烂代码的经验!

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:捡田螺的小男孩

前言

毕业五年多,一共待过3家公司,碰到各种各样的同事,见识过各种各样的代码,有优雅的,赏心悦目的,也有垃圾的,屎山一样的。因此,写这篇文章,来记录一下一个优秀的后端开发程序员,应该有哪些好的开发习惯。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

1.注释尽可能全面,写有意义的注释

接口方法、类、复杂的业务逻辑,都应该添加有意义的注释

  • 对于接口方法的注释,应该包含详细的入参和结果说明,有异常抛出的情况也要详细叙述
  • 类的注释应该包含类的功能说明、作者和修改者。
  • 如果是业务逻辑很复杂的代码,真的非常有必要写清楚注释。

清楚的注释,更有利于后面的维护。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

2.项目拆分合理的目录结构

记得读大学那会,刚学做各种各样的管理系统,都是用MVC模式,也就是controller、service、mapper、entity。如果未来业务扩展,你没有拆分业务结构的话,很可能就会发现,一个service包下,有上百个服务。。。

正确的做法,如果服务过多,应该根据不同的业务进行划分,比如订单、登陆、积分等等

当然,你也可以根据不同的业务划分模块,比如建一个moudles包,然后按订单、登陆等业务划分,每个业务都有自己的controller、service、mapper、entity

我们拆分的目的,就是让项目结构更清晰,可读性更强,更容易维护 而已。

3. 不在循环里远程调用、或者数据库操作,优先考虑批量进行。

远程操作或者数据库操作都是比较耗网络、IO资源 的,所以尽量不在循环里远程调用、不在循环里操作数据库,能批量一次性查回来尽量不要循环多次去查 。(但是呢,如果是操作数据库,也不要一次性查太多数据哈,可以分批500一次酱紫)。

正例:

remoteBatchQuery(param);

反例:

for(int i=0;i<n;i++){
  remoteSingleQuery(param)
}

4. 封装方法形参

如果你的方法参数过多,要封装一个对象出来。反例如下:

public void getUserInfo(String name,String age,String sex,String mobile,String idNo){
  // do something ...
}

如果参数很多,做新老接口兼容处理也比较麻烦。建议写个对象出来,如下:

public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
  // do something ...
}

class UserInfoParamDTO{
  private String name;
  private String age; 
  private String sex;
  private String mobile;
  private String idNo;
}

5. 封装通用模板

一个优秀的后端开发,应该具备封装通用模板 的编码能力。

我们来看一个业务需求:假设我们有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)。走类似这么一个流程,如下:

一个请求都会经历这几个流程:

  • 查询商户信息
  • 对请求报文加签
  • 发送http请求出去
  • 对返回的报文验签

通过HTTP发请求出去时,有的商户可能是走代理 的,有的是走直连。假设当前有A,B商户接入,不少伙伴可能这么实现,伪代码如下:

// 商户A处理句柄
CompanyAHandler implements RequestHandler {
   Resp hander(req){
   //查询商户信息
   queryMerchantInfo();
   //加签
   signature();
   //http请求(A商户假设走的是代理)
   httpRequestbyProxy()
   //验签
   verify();
   }
}
// 商户B处理句柄
CompanyBHandler implements RequestHandler {
   Resp hander(Rreq){
   //查询商户信息
   queryMerchantInfo();
   //加签
   signature();
   // http请求(B商户不走代理,直连)
   httpRequestbyDirect();
   // 验签
   verify(); 
   }
}

假设新加一个C商户接入,你需要再实现一套这样的代码。显然,这样代码就重复了。这时候我们可以封装一个通用模板 !我们就可以定义一个抽象类,包含请求流程的几个方法,伪代码如下:

abstract class AbstractMerchantService  

     //模板方法流程
     Resp handlerTempPlate(req){
           //查询商户信息
           queryMerchantInfo();
           //加签
           signature();
           //http 请求
           httpRequest();
           // 验签
           verifySinature();
     }
      // Http是否走代理(提供给子类实现)
      abstract boolean isRequestByProxy();
}

然后所有商户接入,都做这个流程。如果这个通用模板是你抽取的,别的小伙伴接到开发任务,都是接入你的模板,是不是会有点自豪呀,哈哈~

封装通用模板 ,就是抽个模板模式嘛?其实不仅仅是,而是自己对需求、代码的思考与总结 ,一种编程思想的升华

6. 封装复杂的逻辑判断条件

我们来看下这段代码:

    public void test(UserStatus userStatus){
        if (userStatus != UserStatus.BANNED && userStatus != UserStatus.DELETED && userStatus != UserStatus.FROZEN) {
            //doSomeThing
            return
        }
    }

这段代码有什么问题呢?是的,逻辑判断条件太复杂啦,我们可以封装一下它 。如下:

    public void test(UserStatus userStatus){
        if (isUserActive(userStatus)) {
            //doSomeThing
        }
    }

    private boolean isUserActive(UserStatus userStatus) {
        return userStatus != UserStatus.BANNED && userStatus != UserStatus.DELETED && userStatus != UserStatus.FROZEN;
    }

7. 保持优化性能的嗅觉

优秀的后端开发,应该保持优化性能的嗅觉。比如避免创建比必要的对象、异步处理、使用缓冲流,减少IO操作等等。

比如,我们设计一个APP首页的接口,它需要查用户信息、需要查banner信息、需要查弹窗信息等等。假设耗时如下:

查用户信息200ms,查banner信息100ms、查弹窗信息50ms,那一共就耗时350ms了。如果还查其他信息,那耗时就更大了。如何优化它呢?可以并行发起,耗时可以降为200ms。如下:

8. 可变参数的配置化处理

日常开发中,我们经常会遇到一些可变参数,比如用户多少天没登录注销运营活动,不同节日红包皮肤切换、订单多久没付款就删除等等。对于这些可变的参数,不用该直接写死在代码。优秀的后端,要做配置化处理,你可以把这些可变参数,放到数据库一个配置表里面,也可以放到项目的配置文件或者apollo上。

比如产品经理提了个红包需求,圣诞节的时候,红包皮肤为圣诞节相关的,春节的时候,为春节红包皮肤等。如果在代码写死控制,可有类似以下代码:

if(duringChristmas){
   img = redPacketChristmasSkin;
}else if(duringSpringFestival){
   img =  redSpringFestivalSkin;
}

如果到了元宵节的时候,运营小姐姐突然又有想法,红包皮肤换成灯笼相关的,这时候,是不是要去修改代码了,重新发布了?

从一开始接口设计时,可以实现一张红包皮肤的配置表 ,将红包皮肤做成配置化呢?更换红包皮肤,只需修改一下表数据就好了。当然,还有一些场景适合一些配置化的参数:一个分页多少数量控制、某个抢红包多久时间过期这些,都可以搞到参数配置化表里面。这也是扩展性思想的一种体现。

9. 会总结并使用工具类。

很多小伙伴,判断一个list是否为空,会这么写:

if (list == null || list.size() == 0) {
  return null;
}

这样写呢,逻辑是没什么问题的。但是更建议用工具类,比如:

if (CollectionUtils.isEmpty(list)) {
   return null;
}

日常开发中,我们既要会用工具类,更要学会自己去总结工具类。比如去文件处理工具类、日期处理工具类等等。这些都是优秀后端开发的一些好习惯。

10. 控制方法函数复杂度

你的方法不要写得太复杂,逻辑不要混乱,也不要太长 。一个函数不能超过80行。写代码不仅仅是能跑就行,而是为了以后更好的维护。

反例如下:

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {
        //print banner
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");

        //calculate totalAmount
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }

        //print details
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
        ......
    }
}

其实可以使用Extract Method,抽取功能单一的代码段,组成命名清晰的小函数,去解决长函数问题,正例如下:

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {

        //print banner
        printBanner();
        //calculate totalAmount
        double totalAmount = getTotalAmount();
        //print details
        printDetail(totalAmount);
    }

    void printBanner(){
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");
    }

    double getTotalAmount(){
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }
        return totalAmount;
    }

    void printDetail(double totalAmount){
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
    }   
}

11. 在finally块中对资源进行释放

应该大家都有过这样的经历,windows系统桌面如果打开太多文件或者系统软件,就会觉得电脑很卡。当然,我们linux服务器也一样,平时操作文件,或者数据库连接,IO资源流如果没关闭,那么这个IO资源就会被它占着,这样别人就没有办法用了,这就造成资源浪费。

我们操作完文件资源,需要在在finally块中对资源进行释放。

FileInputStream fdIn = null;
try {
    fdIn = new FileInputStream(new File("/捡田螺的小男孩.txt"));
catch (FileNotFoundException e) {
    log.error(e);
catch (IOException e) {
    log.error(e);
}finally {
    try {
        if (fdIn != null) {
            fdIn.close();
        }
    } catch (IOException e) {
        log.error(e);
    }
}

12.把日志打印好

日常开发中,一定需要把日志打印好。比如:你实现转账业务,转个几百万,然后转失败了,接着客户投诉,然后你还没有打印到日志,想想那种水深火热的困境下,你却毫无办法。。。

一般情况,方法入参、出参需要打印日志,异常的时候,也要打印日志等等,如下:

public void transfer(TransferDTO transferDTO){
    log.info("invoke tranfer begin");
    //打印入参
    log.info("invoke tranfer,paramters:{}",transferDTO);
    try {
      res=  transferService.transfer(transferDTO);
    }catch(Exception e){
     log.error("transfer fail,account:{}",
     transferDTO.getAccount())
     log.error("transfer fail,exception:{}",e);
    }
    log.info("invoke tranfer end");
    }

13. 考虑异常,处理好异常

优秀的后端开发,应当考虑到异常,并做好异常处理。田螺哥给大家提了10个异常处理的建议:

  • 尽量不要使用e.printStackTrace(),而是使用log打印。因为e.printStackTrace()语句可能会导致内存占满。
  • catch住异常时,建议打印出具体的exception,利于更好定位问题
  • 不要用一个Exception捕捉所有可能的异常
  • 记得使用finally关闭流资源或者直接使用try-with-resource
  • 捕获异常与抛出异常必须是完全匹配,或者捕获异常是抛异常的父类
  • 捕获到的异常,不能忽略它,至少打点日志吧
  • 注意异常对你的代码层次结构的侵染
  • 自定义封装异常,不要丢弃原始异常的信息Throwable cause
  • 运行时异常RuntimeException ,不应该通过catch的方式来处理,而是先预检查,比如:NullPointerException处理
  • 注意异常匹配的顺序,优先捕获具体的异常

14. 考虑系统、接口的兼容性

优秀的后端开发,会考虑系统、接口的兼容性。

如果修改了对外旧接口,但是却不做兼容。这个问题可能比较严重,甚至会直接导致系统发版失败的。新手程序员很容易犯这个错误哦~

因此,如果你的需求是在原来接口上修改,尤其这个接口是对外提供服务的话,一定要考虑接口兼容。举个例子吧,比如dubbo接口,原本是只接收A,B参数,现在你加了一个参数C,就可以考虑这样处理:

//老接口
void oldService(A,B){
  //兼容新接口,传个null代替C
  newService(A,B,null);
}

//新接口,暂时不能删掉老接口,需要做兼容。
void newService(A,B,C){
  ...
}

15. 采取措施避免运行时错误

优秀的后端开发,应该在编写代码阶段,就采取措施,避免运行时错误 ,如数组边界溢出,被零整除,空指针等运行时错误。类似代码比较常见:

String name = list.get(1).getName(); //list可能越界,因为不一定有2个元素哈

所以,应该采取措施,预防一下数组边界溢出,正例如下:

if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){
        String name = list.get(1).getName();
        }


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
瞧瞧人家写的API接口代码,那叫一个优雅!年度总结|盘点新消费品牌的2022年,我们总结了10大风向iPad 10 首发体验:我们搜集了 700 多条提问,总结出了这十点看完李佳琦的《所有女生的OFFER2》后,我总结了4大今年双11营销关键词三线建设对子孙的生活息息相关查尔斯三世登基失仪态,想起英女王。。。Redpanda:用C++重写的Kafka到底有多牛逼。。。在美国过了十几个万圣节,我总结出了九个真相一家3口转阴后,总结了16个很有用的经验小基数不挨饿减肥,2个月瘦10磅,半年无反弹。我总结了这几点经验分享给大家刘亦菲首部现代剧《去有风的地方》海报好治愈,我用PS也调出了同款滤镜,美哭了!合规师取证,方法越简单越好…我1个月取证的经验分享发现了比商学院Casebook好用10倍的面试真经,我1个月上岸BCG刷完上千本海外言情,我总结了全球女性的情爱幻想发现了商学院Casebook好用10倍的面试真经,我1个月上岸BCG大爷投1万炒股忘掉密码,15年后发现已经变成50万!又一股市致富故事是因为忘记密码,长期投资+优质公司才是复利的意义...【趣事】买入几十套新加坡数学教材,我总结出了这些要点那位用Rust重写数据库的创始人来复盘了:删除27万行C++代码,值吗?教育随笔(111)高考只讲淘汰率,不讲合格率抗癌10年,我总结了10条「救命」原则......这么烂的代码,谁写的?!庆祝结婚35周年高效能人士,如何用15条法则进行时间管理? |【经纬低调分享】扒了同事的购物车,找出了她们买得最不后悔的战利品!《一点声明》+《黛玉与宝钗的诗才比拼》【分享】澳洲华裔妈妈倾心分享:我走了很多弯路,总结出这5点教育启蒙经验男子赌球输了1000多万,骗了同事60万后失联!曾是500强外贸公司中层领导…起拍价20亿,1.2万人围观,盖了10年还没盖好的烂尾楼流拍:土地使用权只剩27年如何穿越商业周期?这里有一份2100万个企业组织的经验总结《代码英雄》第五季(2):写代码的地方 | Linux 中国节前聊天 | 花了快一辆车的钱护肤,我总结下来最有用的3部分一家3口转阴后,总结了16条很有用的经验重写 50 万行代码,从 0 自研的云原生 HSTAP 能否成为数据库的未来?| Q推荐“儿子大量阅读也没逃过「三年级现象」,血泪总结我们的经验教训”生活与时代的相逢|个体叙事写作营招募
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。