工作半年的思考

记录一下工作半年之后发现的现象和对应的思考。

  • 项目最终一定会成为“屎山”
  • 百分之八十的代码是特殊或异常情况处理
  • 参数校验是一把双刃剑

项目最终一定会成为“屎山“

大学毕业之前就知道程序员最头疼的事是维护老项目,尤其是代码质量很差的”屎山”。很幸运的是,工作后遇到的第一个项目就是维护一个屎山,这个项目是庞大复杂系统中的一个模块,由于大版本的更新迭代导致现有功能已无法使用,必须进行重新适配。更幸运的是,领导给了我极大的自由度和很长时间自由发挥。

刚接手时,整个项目真是一团乱麻,定义最主要业务逻辑的类文件有四千行、其中还有一个九百多行的 run 函数,另外还有一半的类在迭代过程中已经不再使用但是没有删除。截止到第三轮系统测试、整个项目持续了两个月,全程由我一个人完成,经历了从日志文件和主要代码中倒推出业务逻辑状态机、通读所有源码删除无用部分、调整混杂在一起的各层代码、优化代码等过程。下方两张图分别是刚接手是代码扫描结果和目前的代码扫描结果,可以看出来改进了很多。

和无法违背的热力学第二定律一样,我认为只要一个项目还有维护的价值、还在迭代中,它的代码质量就一定会越来越差。有以下两个主要原因:

  • 有维护需要的项目,很可能会不断的加入新的功能,而再有经验的架构师在最初设计时也不可能考虑到所有的情况。从这个角度来说, 设计上的缺陷是一定存在的,没有人敢笃定自己的设计万无一失 ,设计之初只能尽可能的提高设计质量。
  • 软件开发团队自身就是复杂的,每个人水平参差不齐、会有人离开有人加入,老员工写下一段代码时经历了什么样的思考和辩证,可能永远也没机会让后来加入的新人了解。这种情况下,维护老项目的新员工就可能破坏原本清晰的代码结构。

百分之八十的代码是特殊或异常情况处理

理想情况下,用户会用最主流的浏览器、把正确账号密码填在正确的位置然后登陆。实际情况中,用户可能会用 IE 浏览器、可能会把用户米密码填反中,甚至访问应用的都不是活生生的用户。

于是,前端需要做表单验证以验证用户的输入,做多浏览器适配以方便用户;而后端需要做入参校验防止后台程序被前端同事和爬虫搞崩。下面是作为一个 Java 语言后端开发者对于一些可能的异常情况的总结。

  • Java 的基础数据类型含有默认值,因此 DAO 类中的基本数据类型要使用包装类,避免数据库中相关值实际为 null,查询结果却为 0 的情况发生。
  • 除了考虑数据类型自身的范围限制避免溢出的情况以外,在特定的业务场景中,数据类型的值可能含有特定范围,例如用于计数时 int(或 long)不可能为复数等。
  • 字符串类型的特殊值有 null 和空字符串,很多情况下这两个特殊值会触发 Bug,但有些特殊情况时又是业务逻辑息息相关的,需要谨慎处理。
  • 字符串中的转义字符处理:前后端数据交互时,如果使用 JSON 格式需要对用户输入的 {}", 等特殊符号进行转义;如果使用 XML 格式需要对用户输入的 <>" 等特殊符号进行转义。

参数校验是一把双刃剑

在维护前文提到的项目时,有一个后端接口查询经常超时,导致前端总是弹出“连接超时“的弹窗警告。这是一个统计服务器资源使用量的功能,前端调用接口查询到结果后会将数据渲染为一个折线图,展示过去一段时间内相关资源的占用情况。经过分析,发现该接口调用最耗时的过程是和主控节点进行 MQ 通讯进行参数校验。与产品经理沟通后认为这个接口具有 调用频繁入参固定需要快速返回 的特点,而用户使用时关心的是某一时间段的资源占用的变化情况、非特定时间点的瞬时数据。后来解决方案是删除了一大段的条件判断,使用 try-catch 捕获了一个通用异常,发生异常时直接返回上一次的统计结果。

由此可见,参数校验也是一把双刃剑,提高程序可靠性的代价就是执行效率变慢,很多时候需要根据实际情况在性能和安全性之间做出取舍。下面关于参数校验的规范摘自《阿里巴巴 Java 开发手册》。

【参考】下列情形,需要进行参数校验:

  1. 调用频次低的方法。
  2. 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退、或者错误,那得不偿失。
  3. 需要极高稳定性和可用性的方法。
  4. 对外提供的开放接口,不管是RPC/API/HTTP接口。
  5. 敏感权限入口。

【参考】下列情形,不需要进行参数校验:

  1. 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
  2. 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
  3. 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。