编程规范定义

简介:

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

参考了《程序员为什么那么累》这篇文章,该文章链接为:https://www.imooc.com/article/27569

针对这篇文章,我再详细的归纳总结,同时也联系到我的实际开发上面。

下面进入正题,谈谈我对编程规范定义的想法和实践。

今天主要就如下几个方面详细说并讲解实践方式和思路。

用思维导图可划分为如下几个方面?
image

一、接口定义常见问题

常见问题为如下几个方面?
image

1.返回格式统一

目前安卓开发与后台交互方式通常为JSON,微信支付比较特殊使用XML方式。

这个格式不仅仅包含返回数据类型一致,同时也包含对应的格式,示例如下:
image

像返回这种格式的数据,如果按照传统的方式的话,可能是这样,用代码表示:

Map returnMap = new HashMap();
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 {

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中,如下所示:
image

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);
    }
}

}

完成上述后,当用户输入不合法时,返回的数据格式和数据类型应该是这样
image

这样也充分表明对数据的异常处理

关于Spring的异常处理,可以参考我的这篇文章:https://www.cnblogs.com/youcong/p/9462715.html

3.不要出现与业务无关的输入参数

问题代码如图:
image

Hotel是一个实体,一个实体是由很多属性组成的,不说远了,这段代码完成的这个功能仅仅只是需要该实体的五到六个字段,而这个实体有十多个字段,这就显得有些冗余了。

像这段代码就做的比较好:
image

它的LoginForm代码也仅仅只是包含所需要的属性,你可以理解LoginForm仅仅只是的dto,负责参数传递,过滤掉无用的参数。

4.不要出现复杂的参数

你可以这样理解?最理想的情况下,是参数列表中的参数一般保持在三个作用,且参数类型相同,如果是参数五个以上(通常,参数列表如果超过三个以上的参数,强烈建议使用对象表示)且参数类型不一致,不仅仅会使得前端方面传递数据和取数据出现异常,而且也会增加复杂性。

一般情况下,参数三个或者三个以下,可以像如下这段代码:
image

高于三个以上,直接用dto,即方便有快捷,同时易于维护。

5.返回应该返回的数据

针对这个,确实也没有什么好说的,不过既然说了,还是要说的,特别是安卓与后台这边,最好的情况是根据接口文档上面的安卓那边需要的数据,返回对应的数据,不要多一个或者是少一个,多一个意味着增加数据的传输量,有些时候看似没多大影响,一旦项目用户多了,数据量大了,带宽也大了,你就懂了。当然了,返回应该返回的数据,也是为了规范起见。

二、Controller规范

思维导图归纳如下:
image

1.统一返回ResultBean

这个我想在接口规范中已经说了,这里不再赘述,不过有一点要说的是,这个统一返回ResultBean对象,我的确没有做好,以至于目前的代码像前面一样:

image

即便可以将返回值改为Object,但是有一点不得不承认的是,还有需要编写JSONObject,最好的办法还是像前面那样R作为返回值,有对应R作为ResultBean统一返回对应的对象。

2.ResultBean不允许往后传

就好像之前我们常用Map接收参数和返回结果集那样,同时兼任两个。看似没问题,实际很大的问题,问题就是职能不明确,可理解为分工不明确,你插手我的,我插手你的。

最好的做法就是你好好做你的,他好好做他的。

3.Controller只做参数传递

同时这里也提到一个要特别注意的问题:

不允许把json,map这类对象传到services去,也不允许services返回json、map

问题代码一(service返回json):
image

问题代码二(map居然作为service的接收参数):
image

3.参数不允许出现Request,Response 这些对象

问题代码:

image

这样看,用request接收参数,导致的可能是可读性差。

怎么解决呢?

还是那句话,三个以下或者等于三个在参数列表中指定,大于三个使用对象作为dto,负责参数传递。

4.关于打印日志

日志在AOP里面会打印,而且我的建议是大部分日志在Services这层打印。

因为业务逻辑也就是service代码,基本上是可以复用的,日志还是在这里打好些。

controller是不能复用的,所以controller那边应该要有其单独的日志打印。

三、AOP实现

思维导图如下:
image

1.ResultBean定义

通用的定义无非是响应码、返回信息、实体数据或者集合数据就没有了。

2.区分异常,返回码定义

这个定义不能太细,最好还是200或者500,不然像我现在定义000000、111111、222222等,感觉有太累,这也是因为service当初没有搞好,以及controller嵌套太多导致的。

这个问题,我将在后续解决。

四、日志打印
image

思维导图如下:

1.日志要求

(1)能找到机器

如果不知道那台机器,出现问题请问怎么排查?如果是一台两台服务器还好,要是想腾讯阿里那样成千上万台,就算累死都很难排查问题

(2)知道用户在做什么

最好的是当用户操作某些功能时,服务器上面的控制台会打印对应的日志,说明其正在操作某某,比如他正在登陆或者是他正在新增菜单等等

2.开发人员日志

很多时候开发人员在遇到问题时,想办法解决的过程中,浪费了很多时间,包括我自己也一样,就是因为不打印日志或者日志打印一些无关紧要的东西

五、异常处理

思维导图如下:

image

异常类型要分清,这里可以借鉴重要且紧急、重要且不紧急、不重要不紧急的这种套路来划分。

特别是对于运维人员来说,对服务器监控是十分必要的,当出现一些问题的苗头时,可以将其扼杀在摇篮中。

六、参数校验和国际化

思维导图如下:
image

1.参数校验通用工具类封装调用,可以引用第三库

这里我建议可以使用开源项目hutool

地址为:http://hutool.mydoc.io/

这个地址有关于如何引用和使用的,十分详细。

我个人建议,通用的工具类(特别是开源的,尽量使用一种,而不是你引用这个,我引用那个,如果真的这么做,你会很痛苦的,我联想到开发前端的时候,极不规范的做法,引用这个插件,那个框架,到最后,面临的很严重问题,是该如何维护它。简直是太混乱糟糕透顶了。

所以建议朋友们,工具类统一。

当然了,自己编写也得统一,最好是看引用的第三库能不能实现,如果不能实现再考虑自己写。

2.参数校验不应该放在controller中,应该由service处理

正面示例:
image

controller代码如此简洁:
image

如此简洁的controller,让人十分喜爱,再看service,换做其他业务代码,我就会很轻松的复用。

再来看反面示例:

service代码(和dao没区别,被称做很low的写法)
image

controller代码(全部放在controller处理,代码不简洁,也不利于复用,随着业务的扩展,代码只会越来越多,如果controller如此杂乱,以后新招人来维护,估计人家要么硬着头皮忍着,要么第二天不来了):

image

七、函数编写建议

思维导图如下:
image

引用我的哥哥对我说的那两句话:

1.一个函数只办一件事;

2.某段代码块需要多处引用,可考虑将其封装调用;

这两句话可以回答上面的部分问题

不过需要补充的是,正如小时候看武打片那样,特别是用剑的高手,真正的高手是不需要剑的,正如软件开发工程师,代码的可读性并不是靠注释,而是本身代码就已经告诉人家它是做什么的。

尽管目前我还不能做到这些,但是我一直也在努力的做。

编写能测试的函数,在此我之前在一篇关于单元测试的重要性重已经说明了,但是今天看来还不够全面。最全面的就是,编写的测试函数,短而精悍,可单独测试。

八、应对需求变更

思维导图如下:
image

1.把代码写到最简单

其实往往复杂的东西就是最简单的

像前面说的那样,其实controller里面的好几个,其实不必冗长,短而精即可。

2.把可能变化的封装成函数

这个需要思考,每次在编写代码的时候,要想,这段代码是否会在其他地方引用,如果引用的话,复制过来复制过去,影响可读性,同时也很冗余,这就需要考虑封装成一个函数调用。

3.解耦

解耦是编程里面重要的思想,解耦的关键在于:多引入“第三者”,不要直接发生关系

“高内聚,低耦合”的程序,是每个程序员的追求,如何写一手优雅的代码,关键在于此。

如何解耦呢?

观察者模式可以借鉴

或者可以联系到模块设计方面

如何隔离app应用和后台应用

如何确保改这段代码不会影响那段代码

看看人人开源的这个
image

admin和api是独立的

common作为工具类是可以复用的

generator作为代码生成器也是隔离的

4.数据结构要考虑扩展

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

九、配置规范
image

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
编译器 C++
在C++语言中函数的定义
在C++语言中函数的定义
9 0
|
8月前
|
自然语言处理 数据库连接
编译原理(五) 语言的定义
编译原理(五) 语言的定义
|
8月前
|
并行计算 Java 编译器
教你精通Java语法之第十五章、Lambda表达式
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。1. 代码简洁,开发迅速2. 方便函数式编程3. 非常容易进行并行计算4. Java 引入 Lambda,改善了集合操作1. 代码可读性变差2. 在非并行计算中,很多计算未必有传统的 for 性能要高3. 不容易进行调试。
32 0
|
Java
Java接口概念和语法例子(功能性方法)
比如有三个类。兔子、狗、青蛙这三个类。要定义一个公共游泳方法出来。但是兔子不会这个游泳,那么就不使用这个接口,另外的狗和青蛙会游泳,就会使用这个游泳接口。简单来说,就是谁需要功能接口谁就使用这个功能接口就好了
89 0
|
Python
python编程-12:类的定义-面向对象
python编程-12:类的定义-面向对象
|
存储 缓存 网络协议
阿里Java编程规约【二】常量定义
1. 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。 反例:
389 0
|
存储 Java API
反模式的接口常量
在实际开发过程中,经常会需要定义一个文件,用于存储一些常量,这些常量设计为静态公共常量(使用 public static final 修饰)。这个时候就出现两种选择
132 0