Duke的咆哮语录①:我求求你们读一下《代码整洁之道》吧!

命名是一门大学问。

最近接手一个别人开发的项目,让人无限黑人问号。其实类似行文风格的文章我早就想做了,但这一次应该说是最最强烈的。一个专业的程序员应该是负责任的、有远见的、善良的。应该嫉恶如仇,极度厌恶任何非秩序行为代码的。本着对同事进步的督促、对不爽导致健康问题的预防和发泄,现开启本系列文章,名曰咆哮语录,希望能以吾辈血泪之咆哮,咄咄逼人之气势,给同事同学留下丝毫之印象。
以下皆为工作时随手抓取的代码片段。经目测没有包含任何公司机密,也不会涉及知识产权敏感。但依然不允许任何人以任何形式用于任何项目。
本文最大的功能还是自己的不吐不快希望自己冷静,我并不是也没有权利批评任何人。请不要被我的咆哮影响心态,谢谢。
我们先来看Controller层的代码:

// 省略代码头部

@Controller
@RequestMapping("/file")
public class FileController {

    @Autowired <1>
    TrainPlanService planService; <2>

    /*上传*/
    @ResponseBody
    @RequestMapping(value = "/upload")
    public SimpleJSON upload(HttpServletRequest request,
                             MultipartFile file, TrainPlan plan) throws Exception { <3>

        try {
            Boolean flag = ReflectUtils.checkNotNull(plan);
            if (!flag) {
                return ResultUtils.error201();
            }
            if (file.isEmpty()) {
                return ResultUtils.customResult(500, "文件不能为空,请重新上传!");
            }
            //判断是否存在若存在删除该文件
            planService.delete(plan);

            String savedDir = ReadeGlobePa.getValueByProper("fileSaveDir");
            //上传文件路径
//            String savedDir = request.getSession().getServletContext().getRealPath(dir); //获取服务器指定文件存取路径 <4>
            //上传文件名
            String filename = file.getOriginalFilename();
            int pointIndex = filename.indexOf(".");      //点号的位置 <5>
            String fileSuffix = filename.substring(pointIndex);             //截取文件后缀 <5>
            UUID fileId = UUID.randomUUID();                        //生成文件的前缀包含连字符 <5>
            String savedFileName = fileId.toString().replace("-", "").concat(fileSuffix);       //文件存取名
            File savedFile = new File(savedDir, savedFileName);
            //判断路径是否存在,如果不存在就创建一个
            if (!savedFile.getParentFile().exists()) {
                savedFile.getParentFile().mkdirs();
            }
            //将上传文件保存到一个目标文件当中
            file.transferTo(savedFile);

            //保存数据库表
            //plan.setUrl(savedDir + savedFileName);
            plan.setUrl(savedFileName);
            plan.setFileName(filename);
            planService.save(plan);
            return ResultUtils.customResult(200, "上传成功!");
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*跳转上传页面*/
    @GetMapping("/toUploadPage")
    public String toUploadPage() throws Exception { <3>
        return "/file";
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findBranchList")
    public SimpleJSON findBranchList() throws Exception { <3>
        try {
            List<String> branchList = planService.findBranchList();
            return ResultUtils.getSuccess(branchList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findPlanList")
    public SimpleJSON findPlanList(@RequestParam Integer year, Integer quarter, @RequestParam String branchName) throws Exception {
        try {
            String branch = URLDecoder.decode(branchName, "UTF-8");
            List<TrainPlan> planList = planService.findPlanList(year, quarter, branch);
            return ResultUtils.getSuccess(planList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findList")
    public SimpleJSON findList(@RequestParam Integer year, Integer quarter) throws Exception {
        try {

            List<TrainPlan> planList = planService.findList(year, quarter);
            return ResultUtils.getSuccess(planList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }
}

先看标号的部分。

  1. @Autowired注解用于字段注入本身语法上没错,但field注入(字段注入)在目前的Java开发中是明显不受待见的。它存在诸如“容易违反单一原则”等诸多问题。可参阅这篇文章
  2. 纵然,使用字段注入就使用字段注入罢。但该成员变量很明显绝对不可能和外接发生接触,故可访问性应该为私有,应加private
    其实说来,关于Spring的注入,现在主流且推荐的方式就是setter注入和构造器注入。两种方法都很好,但我个人更推荐使用构造器注入。本例使用构造器注入改写的样子如下:
    @Autowired
    public FileController(TrainPlanService planService) {
        this.planService = planService;
    }
    private final TrainPlanService planService;

如果是setter注入的话,则应改写如下:

    private TrainPlanService planService;
    @Autowired
    public void setPlanService(TrainPlanService planService) {
        this.planService = planService;
    }

注意构造器注入的例子我们将planService声明为了final的,这也是使用构造器注入的一点附加好处,可以避免运行时对注入字段进行修改导致一些问题。

  1. 稍微有些Spring Web开发经验的人应该都知道,Controller层应该是一切异常的终点。如果在Controller层抛出异常,那么该异常就会被Spring或容器捕获,如果没有任何配置的话,就会显示出一张极丑无比的白标页。甚至还会暴露生产环境信息,被黑客利用。所以我多次强调:绝对不允许在Controller层抛出任何异常,也绝对不要在Controller层的方法上加入任何异常抛出声明。可惜,这段代码显然没有做到后一点。丑陋而又无用的throws声明,让人哭笑不得。关于异常的问题我会在下文详述。
  2. 第四点,参差不齐的行注释开始行位置。呵呵,总有一天我会被你们这群没有强迫症的人逼上绝路。您按一两下tab键对齐一下行吗?求求您了!还有就是很明显这行代码是遗弃代码,永远不可能再发挥作用了。求求您删了它吧!我很不明白,有些人,明明有些代码再也不会用到,非要当宝贝一样加个注释留在代码里随时光慢慢变老。抱歉那些不是宝贝,是屎啊!是尼玛恶臭不堪又毫无用处的屎啊!是毫无帮助又会误导人的屎啊!关于注释,下文我还会谈到。
  3. 好了,这次不用单行注释,改用行末注释了。我个人坚决抵制任何超过5个字的行末注释。它们不仅严重拖长了代码行宽,也在事实上不会比独行注释易读。OK即使我们抛开这一点不谈,我求求您了,您能不能让所有行末注释距离代码语句末尾的空格数一致?这既不对齐又随性的空格数,是蕴藏着什么常人难以发现的美丽吗?我建议,如果真的要使用行末注释,您也不用刻意保持对齐,就在代码行末后点一个空格,写两个/,再点一个空格开始写就可以了。换种说法麻烦//前后各加一个空格谢谢。您也方便,我们也舒服。先忽略这段代码中的逻辑问题,比如如果上传文件名本身没有.怎么办,以及删除没必要是个人都能看得懂的代码上的注释,以及适当添加空行,让我来改写一下:
    // 上传文件名
    String filename = file.getOriginalFilename();
    int pointIndex = filename.indexOf(".");
    // 文件后缀
    String fileSuffix = filename.substring(pointIndex);
    // 生成“UUID.后缀名”文件名
    String savedFileName = UUID.randomUUID().toString().replace("-", "").concat(fileSuffix);
    
    File savedFile = new File(savedDir, savedFileName);
    // 判断路径是否存在,如果不存在就创建一个
    if (!savedFile.getParentFile().exists()) {
        savedFile.getParentFile().mkdirs();
    }
    // 将上传文件保存到一个目标文件当中
    file.transferTo(savedFile);

如果您觉得我这样写不如原文舒服,那您可以直接点一下xx江湖再见咯。

  1. 我们来看最后一个标记,这里原作者很贴心地加了注释,告诉你该方法的功能是“分中心列表”。但实际上呢?只有第一个方法确实是用来返回分中心列表的。后两个要获取的东西压根不是分中心。这就是有害的注释。一些刚入坑一两年的小白白在评价项目的时候,最喜欢的说辞就是“这项目太垃圾了,连注释都没有,根本看不懂”。还有很多小白说励志给自己写的每一行代码都加上注释。其实都是片面的。注释要加,但不要加废话。注释不能不加,但如果是有害的有误导的注释,那我求求您不如不加。良好的软件项目代码是自文档化的,还真不见得需要添加太多注释。而有害的注释,则是软件项目的毒瘤,比屎还危险的东西,是坚决要烧死的。

我们再来看最后两个方法的命名,当然注释不指望了,毫无帮助。那么这两个方法的名字有没有问题呢?

public SimpleJSON findPlanList(@RequestParam Integer year, Integer quarter, @RequestParam String branchName) throws Exception
public SimpleJSON findList(@RequestParam Integer year, Integer quarter) throws Exception

看代码可以知道,这两个方法都返回“TrainPlan”的列表。也就是说两者的名字只是有无冗余的Plan而已,实质则等价。这是其一。其二,list本身即为动词“列举”,那何必再使用“find”动词呢?直接叫list岂不是更简单直接?
还有,两个方法的表面很像,返回值很像,但很明显调用的service方法不同,这也就意味着两者在逻辑上的含义是不同的。如此,也就意味着至少应该用明确的定语“ForXXX”、“ByXXX”、“WithXXX”等去描述该方法,可是并没有。后来了解到,实际上这两个方法调用的service方法功能上又是基本等价的,完全可以删掉其中一个。呵呵。
从命名的角度来说,该控制器名为“FileController”,那么从字面的意思理解,这段代码就应该是处理文件的。但事实上我们看其内容,发现大部分业务逻辑却是派发给TrainPlanService的实例去完成的。也就是说,从事实上来看,该控制器的命名本身就是非常糟糕的。应该直接命名为TrainPlanController,其映射的二级目录也应该为/trainPlan才对。
那么既然该控制器主要用于处理培训计划,那么主业务逻辑bean,planService,添加所谓的前缀就显得赘余了。不如直接改为service更加爽利。
好了,吐槽这么多,再来教一招,IDEA的重构功能,可以很简单地给标识符、局部变量重命名,也可以很简单地抽取大代码片段为私有方法。我们这里介绍重命名的做法。

  1. 光标移动至要改名的类名/变量名/方法名上;
  2. 按下Shift + F6
  3. 输入新的名字,回车。

您学会了吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值