date: 2017-12-19 14:07:34
title: code S: code style & code standard
description: 关于代码风格与代码规范的二三事
先解释一下标题, code S 既可以是 code style, 也可以是 code standard, 大部分情况是兼而有之. 无论是 style 来表达 编程之美 或者 编程即艺术, 还是用 standard 来 约束, 都表达了 code 应该是 有迹可循 的.
关于 code S 这个话题, 一个相关的是最近很火的 阿里巴巴 Java 开发手册. 作为 phper, 自然是 PSR. 另一个值得推荐的是 程序员DD - 翟永超的公众号 中的专题文章 -- 程序员你为什么那么累.
本篇参考了大部分 程序员DD - 翟永超的公众号 中的文章, 推荐移步一观.
首先是需要考虑的几个问题:
是不是觉得很矛盾, 一方面工作不复杂, 一方面却累成狗
大部分人的大部分时间都是在 定位问题 + 改代码, 真正开发的时间并不多
simple is not easy
对于个人来说, 技术很重要. 但是对于工作来说, 编码的习惯比技术更加主要
不管接手任何项目组, 第一步就是制定代码框架, 制定项目组的开发规范, 把代码量减下去
对比:
这样做的思想也很简单: 实际上干活的就只有一行, 其他都和业务代码没有一毛钱关系.
实际效果也很明显: 代码量减少 -> 加班减少 -> 我愿意
PS: 技术精简使用的 AOP, 其中 Lang 会使用 ThreadLocal 处理掉.
详细议题:
- 接口定义规范
- controller规范
- 日志规范
- 异常处理规范
- 国际化规范
- 参数校验规范
- 工具类规范
- 函数编写建议
- 配置建议
规范里面大部分是 不要做的项多, 要做的比较少, 落地比较容易
代码最主要的不是性能,而是可读性, 有了可读性才有维护性
我觉得我的编码习惯比技术更有价值
傻瓜都能写出计算机可以读懂的代码, 只有优秀的程序员才能写出人能读懂的代码
程序猿不招妹子们喜爱的根本原因在于追求了错误的目标:更短、更小、更快。
接口定义规范
- 返回格式统一, 不要使用 map,json,object 等类型, 统一使用 ResultBean / PageResultBean
- 考虑失败情况: 被 ResultBean 封装掉
- 出现业务无关输入参数, 比如上面的 Lang 使用 ThreadLocal 优化掉
- 出现复杂输入参数, 比如 json, 可读性极差, 应该定义为对应的 bean
- 没有返回应该返回的数据, 这需要编程经验, 不返回或者返回True 都是不应该的. 别人要不要是别人的事情, 你该返回的还是应该返回
ResultBean: code(状态) + msg, 并处理 Exception
统一的好处:
- 避免很多无用返工和可能出现的问题
- 代码可读性
- 利于利用 AOP/自动化测试这些额外工作
controller规范
上面的规范要继承, 还需要注意以下几点
- ResultBean/PageResultBean 是 controller 专用的, 不允许往后传
- Controller 做参数格式的转换, 不允许把 json/map 这类对象传到 services 去, 也不允许 services 返回 json/map
- 参数中一般情况不允许出现 Request/Response 这些对象
- 不需要打印日志 -> AOP, 大部分日志 Service 层打印
json/map, 格式灵活, 但是可读性差 -> bean, 看着工作量多了, 但代码清晰多了
AOP:
- 打印日志
- 捕获异常, 异常分为已知异常和未知异常, 未知异常添加邮件通知之类的, 不同异常返回不同返回码
- 配置 or 代码 -> 配置优先
日志规范
日志落地问题:
- 没有日志功能一样跑 -> 出了问题没日志, 只能再加
- 节点多 -> 日志要一个一个翻
日志至少要求:
- 能找到机器
- 能找到用户做了什么: log4j 使用 MDC 使日志记录是带上 user 标识
// nginx -> 简单配置返回节点
add_header X-Slave $upstream_addr;
打印日志要求:
- 修改操作必须打上日志
- 条件分支必须打上条件值, 重要参数必须打印
- 数据量大必须打印数据数量
培养日志习惯:
- 不要依赖 debug, 多依赖日志
- 代码提交前先跑一遍看看日志是否看得懂
日志等级 -> 不是很关注 -> 简单规则, 更好落实
异常处理规范
出了异常不知道 -> 闹大了被投诉
出了问题无法定位原因
任何规定都无法保证一定不会发生错误 -> 我只相信代码
- 开发组长定义好异常, 其实没几种, 太细了很难落地
- 绝大部分场景, 不允许捕获异常, 不要乱加空判断 -> 自以为写了 「健壮」 的代码 -> 尽早让错误抛出来
- 后台(任务队列)异常一定要加通知机制, 要第一时间知道异常
国际化规范 & 参数校验规范
- 业务代码里面不要出现和业务无关的东西, 如 local/MessageSource -> ThreadLocal
- 国际化信息放哪: url vs cookie -> url 比较 low 还容易出问题
- 静态方法注入实现参数校验
- 参数非法时打印 key/value 出来
PS: 代码写得不够好, 才需要多加注释来凑
工具类规范 - 动态代理
- 定义注解 -> 需要常驻内存支持, 不然每次运行都需要消耗性能来解析注解
- 注解解析 -> Reflections -> 生成动态代理
- 动态代理 -> 注册到 spring 容器
- 定义默认的 RestTemplate 处理请求类 -> 注册到 spring 容器 -> RestTemplate 可以添加认证
- JUNIT 测试
函数编写建议
- 不要出现业务无关参数
- 避免使用 Map/Json 等复杂数据结构对象作为参数和结果
- 有明确的输入输出和方法名
- 把可能变化的地方封装成函数 -> 需求老变
- 编写能测试的函数
配置规范
- 先定义 bean -> 确定包含关系
- 使用代码生成 + 测试
- 业务开发 / 功能测试完毕 -> 定义配置文件, 格式推荐 JSON
禁忌:
- 读取配置和业务代码耦合
- 开发初期就定配置文件
应对需求变更
说的好像别人的需求就不会变动似的
正是因为需求变更不可避免, 所以我们才更应该把代码写简单, 以对付各种各样的需求变化
- 把代码写到最简单
- 把可能变化的封装成函数
- 多个功能中先做不会变的功能, 一个功能中先做不会变的部分
- 设计上多采取解耦的设计, 多引入「第三者」, 不要直接发生关系 -> IOC
- 数据结构上要考虑功能将来的扩展
要主动思考, 多走一步, 不要被动接受看到的需求, 要对需求的将来变化做好心中有数
工具类规范
定义自己的工具类, 尽量不要在业务代码里面直接调用第三方的工具类
- 字符串处理: 判断字符串为空
- 复制对象的属性和方法
抽象: 使用父类/接口
使用重载编写衍生函数组 -> 多参数数据类型
静态引入: 约定项目组使用的方法名
独立 / 框架级
抽象封装 / 参数优化