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

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

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

 

九、配置规范

 

版权声明:本文为youcong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/youcong/p/9813575.html