zoukankan      html  css  js  c++  java
  • 编程规范定义

    任何事情都是有规律可循,同时也有其对应的守则(可理解为规范)。各行各业如此,联系到计算机行业里面的软件开发,也是如此。

    参考了《程序员为什么那么累》这篇文章,该文章链接为: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.数据结构要考虑扩展

    关于数据结构方面,后期我会专门讲的,同时也包含算法

    九、配置规范

  • 相关阅读:
    springboot使用MockMvc测试controller
    MySQL5.6版本之后设置DATETIME类型自动更新
    MAVEN打包时跳过Junit测试
    没看这篇干货,别和我说你会IDEA Debug
    java通过HtmlUnit工具和J4L实现模拟带验证码登录
    Vue+Java实现在页面树形展示文件目录
    exceptions: django2.2/ mysql ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3
    linux 软连接的使用
    Linux pip命令报错 -bash: pip: command not found
    MySQL使用命令导出/导入数据
  • 原文地址:https://www.cnblogs.com/youcong/p/9813575.html
Copyright © 2011-2022 走看看