编程规范定义
任何事情都是有规律可循,同时也有其对应的守则(可理解为规范)。各行各业如此,联系到计算机行业里面的软件开发,也是如此。
参考了《程序员为什么那么累》这篇文章,该文章链接为:https://www.imooc.com/article/27569
针对这篇文章,我再详细的归纳总结,同时也联系到我的实际开发上面。
下面进入正题,谈谈我对编程规范定义的想法和实践。
今天主要就如下几个方面详细说并讲解实践方式和思路。
用思维导图可划分为如下几个方面?
一、接口定义常见问题
常见问题为如下几个方面?
1.返回格式统一
目前安卓开发与后台交互方式通常为JSON,微信支付比较特殊使用XML方式。
这个格式不仅仅包含返回数据类型一致,同时也包含对应的格式,示例如下:
像返回这种格式的数据,如果按照传统的方式的话,可能是这样,用代码表示:
Map<String,Object> returnMap = new HashMap<String,Object>(); returnMap.put("msg":"success"): ...... ......
这样一来的话 controller的代码行会增加,同时的话,这是一个非常差的体验
最好的方式是变相一个通用ResultBean
这里我觉得renren-security的可以借鉴:
package io.renren.common.utils; import java.util.HashMap; import java.util.Map; public class R extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public R() { put("code", 0); put("msg", "success"); } public static R error() { return error(500, "未知异常,请联系管理员"); } public static R error(String msg) { return error(500, msg); } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Map<String, Object> map) { R r = new R(); r.putAll(map); return r; } public static R ok() { return new R(); } @Override public R put(String key, Object value) { super.put(key, value); return this; } }
这是通用的ResultBean
放到Controller中,如下所示:
2.考虑失败情况
在安卓与后台接口交互的时候,总会出现这个或者是那个的突发意外,有些意外处理不好不仅仅会影响系统的运行,同时也会影响用户体验。
例如:
我之前开发的一个智能酒店后台系统,主要是以MVC模式为主的web应用开发,当视图有问题时,总不可能直接给用户报个500吧或者是404那种很不友好的错误界面吧
最好的办法是异常处理,如果是以jsp作为视图层,可以考虑在web.xml配置个全局500或者404。
像安卓对后台接口,如果是服务器端有问题,可以给用户弹出个友好的提示,比如未知异常,请联系管理员等。
还是以renren-security为例
它的异常处理代码如下:
@RestControllerAdvice public class RRExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 处理自定义异常 */ @ExceptionHandler(RRException.class) public R handleRRException(RRException e){ R r = new R(); r.put("code", e.getCode()); r.put("msg", e.getMessage()); return r; } @ExceptionHandler(DuplicateKeyException.class) public R handleDuplicateKeyException(DuplicateKeyException e){ logger.error(e.getMessage(), e); return R.error("数据库中已存在该记录"); } @ExceptionHandler(Exception.class) public R handleException(Exception e){ logger.error(e.getMessage(), e); return R.error(); } }
加上数据校验类
public class ValidatorUtils { private static Validator validator; static { validator = Validation.buildDefaultValidatorFactory().getValidator(); } /** * 校验对象 * @param object 待校验对象 * @param groups 待校验的组 * @throws RRException 校验不通过,则报RRException异常 */ public static void validateEntity(Object object, Class<?>... groups) throws RRException { Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); if (!constraintViolations.isEmpty()) { ConstraintViolation<Object> constraint = (ConstraintViolation<Object>)constraintViolations.iterator().next(); throw new RRException(constraint.getMessage()); } } }
/** * 数据校验 * @author chenshun * @email sunlightcs@gmail.com * @date 2017-03-23 15:50 */ public abstract class Assert { public static void isBlank(String str, String message) { if (StringUtils.isBlank(str)) { throw new RRException(message); } } public static void isNull(Object object, String message) { if (object == null) { throw new RRException(message); } } }
完成上述后,当用户输入不合法时,返回的数据格式和数据类型应该是这样
这样也充分表明对数据的异常处理
关于Spring的异常处理,可以参考我的这篇文章:https://www.cnblogs.com/youcong/p/9462715.html
3.不要出现与业务无关的输入参数
问题代码如图:
Hotel是一个实体,一个实体是由很多属性组成的,不说远了,这段代码完成的这个功能仅仅只是需要该实体的五到六个字段,而这个实体有十多个字段,这就显得有些冗余了。
像这段代码就做的比较好:
它的LoginForm代码也仅仅只是包含所需要的属性,你可以理解LoginForm仅仅只是的dto,负责参数传递,过滤掉无用的参数。
4.不要出现复杂的参数
你可以这样理解?最理想的情况下,是参数列表中的参数一般保持在三个作用,且参数类型相同,如果是参数五个以上(通常,参数列表如果超过三个以上的参数,强烈建议使用对象表示)且参数类型不一致,不仅仅会使得前端方面传递数据和取数据出现异常,而且也会增加复杂性。
一般情况下,参数三个或者三个以下,可以像如下这段代码:
高于三个以上,直接用dto,即方便有快捷,同时易于维护。
5.返回应该返回的数据
针对这个,确实也没有什么好说的,不过既然说了,还是要说的,特别是安卓与后台这边,最好的情况是根据接口文档上面的安卓那边需要的数据,返回对应的数据,不要多一个或者是少一个,多一个意味着增加数据的传输量,有些时候看似没多大影响,一旦项目用户多了,数据量大了,带宽也大了,你就懂了。当然了,返回应该返回的数据,也是为了规范起见。
二、Controller规范
思维导图归纳如下:
1.统一返回ResultBean
这个我想在接口规范中已经说了,这里不再赘述,不过有一点要说的是,这个统一返回ResultBean对象,我的确没有做好,以至于目前的代码像前面一样:
即便可以将返回值改为Object,但是有一点不得不承认的是,还有需要编写JSONObject,最好的办法还是像前面那样R作为返回值,有对应R作为ResultBean统一返回对应的对象。
2.ResultBean不允许往后传
就好像之前我们常用Map接收参数和返回结果集那样,同时兼任两个。看似没问题,实际很大的问题,问题就是职能不明确,可理解为分工不明确,你插手我的,我插手你的。
最好的做法就是你好好做你的,他好好做他的。
3.Controller只做参数传递
同时这里也提到一个要特别注意的问题:
不允许把json,map这类对象传到services去,也不允许services返回json、map
问题代码一(service返回json):
问题代码二(map居然作为service的接收参数):
3.参数不允许出现Request,Response 这些对象
问题代码:
这样看,用request接收参数,导致的可能是可读性差。
怎么解决呢?
还是那句话,三个以下或者等于三个在参数列表中指定,大于三个使用对象作为dto,负责参数传递。
4.关于打印日志
日志在AOP里面会打印,而且我的建议是大部分日志在Services这层打印。
因为业务逻辑也就是service代码,基本上是可以复用的,日志还是在这里打好些。
controller是不能复用的,所以controller那边应该要有其单独的日志打印。
三、AOP实现
思维导图如下:
1.ResultBean定义
通用的定义无非是响应码、返回信息、实体数据或者集合数据就没有了。
2.区分异常,返回码定义
这个定义不能太细,最好还是200或者500,不然像我现在定义000000、111111、222222等,感觉有太累,这也是因为service当初没有搞好,以及controller嵌套太多导致的。
这个问题,我将在后续解决。
四、日志打印
思维导图如下:
1.日志要求
(1)能找到机器
如果不知道那台机器,出现问题请问怎么排查?如果是一台两台服务器还好,要是想腾讯阿里那样成千上万台,就算累死都很难排查问题
(2)知道用户在做什么
最好的是当用户操作某些功能时,服务器上面的控制台会打印对应的日志,说明其正在操作某某,比如他正在登陆或者是他正在新增菜单等等
2.开发人员日志
很多时候开发人员在遇到问题时,想办法解决的过程中,浪费了很多时间,包括我自己也一样,就是因为不打印日志或者日志打印一些无关紧要的东西
五、异常处理
思维导图如下:
异常类型要分清,这里可以借鉴重要且紧急、重要且不紧急、不重要不紧急的这种套路来划分。
特别是对于运维人员来说,对服务器监控是十分必要的,当出现一些问题的苗头时,可以将其扼杀在摇篮中。
六、参数校验和国际化
思维导图如下:
1.参数校验通用工具类封装调用,可以引用第三库
这里我建议可以使用开源项目hutool
地址为:http://hutool.mydoc.io/
这个地址有关于如何引用和使用的,十分详细。
我个人建议,通用的工具类(特别是开源的,尽量使用一种,而不是你引用这个,我引用那个,如果真的这么做,你会很痛苦的,我联想到开发前端的时候,极不规范的做法,引用这个插件,那个框架,到最后,面临的很严重问题,是该如何维护它。简直是太混乱糟糕透顶了。
所以建议朋友们,工具类统一。
当然了,自己编写也得统一,最好是看引用的第三库能不能实现,如果不能实现再考虑自己写。
2.参数校验不应该放在controller中,应该由service处理
正面示例:
controller代码如此简洁:
如此简洁的controller,让人十分喜爱,再看service,换做其他业务代码,我就会很轻松的复用。
再来看反面示例:
service代码(和dao没区别,被称做很low的写法)
controller代码(全部放在controller处理,代码不简洁,也不利于复用,随着业务的扩展,代码只会越来越多,如果controller如此杂乱,以后新招人来维护,估计人家要么硬着头皮忍着,要么第二天不来了):
七、函数编写建议
思维导图如下:
引用我的哥哥对我说的那两句话:
1.一个函数只办一件事;
2.某段代码块需要多处引用,可考虑将其封装调用;
这两句话可以回答上面的部分问题
不过需要补充的是,正如小时候看武打片那样,特别是用剑的高手,真正的高手是不需要剑的,正如软件开发工程师,代码的可读性并不是靠注释,而是本身代码就已经告诉人家它是做什么的。
尽管目前我还不能做到这些,但是我一直也在努力的做。
编写能测试的函数,在此我之前在一篇关于单元测试的重要性重已经说明了,但是今天看来还不够全面。最全面的就是,编写的测试函数,短而精悍,可单独测试。
八、应对需求变更
思维导图如下:
1.把代码写到最简单
其实往往复杂的东西就是最简单的
像前面说的那样,其实controller里面的好几个,其实不必冗长,短而精即可。
2.把可能变化的封装成函数
这个需要思考,每次在编写代码的时候,要想,这段代码是否会在其他地方引用,如果引用的话,复制过来复制过去,影响可读性,同时也很冗余,这就需要考虑封装成一个函数调用。
3.解耦
解耦是编程里面重要的思想,解耦的关键在于:多引入“第三者”,不要直接发生关系
“高内聚,低耦合”的程序,是每个程序员的追求,如何写一手优雅的代码,关键在于此。
如何解耦呢?
观察者模式可以借鉴
或者可以联系到模块设计方面
如何隔离app应用和后台应用
如何确保改这段代码不会影响那段代码
看看人人开源的这个
admin和api是独立的
common作为工具类是可以复用的
generator作为代码生成器也是隔离的
4.数据结构要考虑扩展
关于数据结构方面,后期我会专门讲的,同时也包含算法
九、配置规范