JAVA接口设置基本原则

Posted 沛沛老爹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA接口设置基本原则相关的知识,希望对你有一定的参考价值。

背景

昨天测试提醒有个功能报错了,我看了下,发现接口有过更新。

然后顺便问了下开发同学,确实有更新。

他们的更新是把现有的接口进行了修改。

导致返回的数据结构变更了,以至于接口直接报错。

其实负责后端开发的同学,开发时间也有4-5年了。

基于这个情况,我觉得,可以和大家分享下接口设计的几个点

设计原则

说明

类的设计原则不在本次讨论范围之类,如果对类设计的六大原则感兴趣的话,可以自行百度。

此处说明的接口设计,如无特别说明,泛指接口类和类中的方法设计。

常用的接口设计原则

1、如果使用了设计模式,在命名时需体现出具体模式。

说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:public Inteface OrderFactory;

2、接口类中的方法和属性尽量保持简洁性

接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。

尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
例:接口方法签名 void commit();
接口基础常量 String FORMATE= "utf-8"; 
说明:JDK8 +后接口允许有默认实现,那么这个 default 方法,是对所有实现类都默认实现该默认方法。

例如下面的接口的默认方法,所有实现该接口的方法都将会继承serviceName方法

 public interface UserService 
     public default void serviceName()
         System.out.println("这是UserService ");
     
 

3、规范接口和实现类的命名

常用命名规则:

命名尽量清晰,不要为了省下几个字符而做一些没有意义的事情。

(1)对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部
的实现类用 Impl 的后缀与接口区别。

例:UserServiceImpl 实现 UserService 接口。

说明:一般这种接口都是基础的业务类接口比较居多。基本上业务代码都是这种格式命名的。

public interface UserService
    //TODO
public Class UserServiceImpl implements  UserService
    //TODO

(2)如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形式)。

例:AbstractTranslator 实现 Translatable 接口。

说明:这种接口一般是公共类的接口。放在common里面的一般都是这种命名模式。

4、已经使用的接口,不要去修改方法签名

外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生
影响。

接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。

接口提供方既然明确是过时接口,那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。

public class TestController 
    /**
     * 测试接口
     * 代替接口:test01
     * @author admin
     * @since 2020-06-03
     */
    @ApiOperation(value = "测试")
    @Deprecated(since = "1.2.0",forRemoval = true)
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public CommonResult<Map<String,String>> test() 
        return CommonResult.success();
    

@Deprecated注解(JDK1.9+)中可以标识过期版本,和是否在以后的版本中删除的标志

@Deprecated元素:since 和 forRemoval。

since: 元素指定已注解的API元素已被弃用的版本。

forRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。

5、泛型通配符使用需要注意对应的使用场景

泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
PECS(Producer Extends Consumer Super)原则:

第一、频繁往外读取内容的,适合用<? extends T>。

第二、经常往里插入的,适合用<? super T>。

一般情况,大家适用的都是往外读取内容的情况比较多。

例如下面,这个是调用了第三方百度,往外读的情况

public interface IBaiduRequest<T extends BaseBaiduResponse> 
    //TODO
public abstract class BaseBaiduResponse implements Serializable 
    //TODO

6、接口入参保护

这种场景常见的是用作批量操作的接口。

就是如果一次性客户端来个大批量操作,到时候服务器就会出现类似DDOS的攻击情况了。例如像批量导入数据,一般的情况下都会是1000作为一个批次处理。因为Mybatis批量插入的sql还是有长度限制的。

7、对外提供的开放接口,需要进行参数校验。

社会用大量的写实电影来告诉你一个现实:除了自己,其他人都靠不住。

对外提供的开放接口,不管是 RPC/API/HTTP 接口,需要进行参数校验。

除了前端校验外,有的时候可能会绕过前端校验。

8、所有的接口方法必须要用 Javadoc 注释

所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
格式说明:对子类的实现要求,或者调用注意事项,请一并说明。这个不写,你可以问下前端同学的感受。

9、接口实现里的特殊注释标记,请注明标记人与标记时间

TODO等特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,
经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
 待办事宜(TODO):( 标记人,标记时间,[预计处理时间])
表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc
还没有实现,但已经被广泛使用。只能应用于类,接口和方法。

public interface IBaiduRequest<T extends BaseBaiduResponse> 
    //TODO 张阿三 2022-04-25 

10、对外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;

跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简
短信息”。
说明:关于 RPC 方法返回方式使用 Result 方式的理由:

(1)不要以为对方就是用你以为的方式来编码。调用你接口的开发,能力和理解力都是一个考验,使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。

(2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用
端来讲,他不一定看得懂。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输
的性能损耗也是个问题。

(3)如果你不小心把数据库操作的一些错误抛出去了,看得懂和看不懂都是一场灾难,具体原有你懂的。

11、不要写一个大而全的数据更新接口。

不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字
段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。

执行 SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。

12、二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象。


13、系统设计时,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。

低层次模块依赖于高层次模块的抽象,方便系统间的解耦。

14、接口更新,只增加,不修改

你不清楚原来公布出去的接口,到底有多少设备在请求,调用端有没有做异常处理。如果没有的话,一旦你修改了返回的参数属性,导致原来调取的时候报错,就有可能导致APP闪退等情况存在,所以,对于接口的设计,宁愿返回多无用的字段,也不要删除原来的字段。

如果那个接口实在不用了,就另外启用一个新的接口,把原来的接口直接标为废弃。

例如,如果你把参数b直接修改为c。那么很有可能前端就悲剧了。

public class TestController 
    /**
     * 测试接口
     * 代替接口:test01
     * @author admin
     * @since 2020-06-03
     */
    @ApiOperation(value = "测试")
    @Deprecated(since = "1.2.0",forRemoval = true)
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public CommonResult<Map<String,String>> test() 
        Map<String,String> map = new HashMap<String,String>();
        map.put("a","1");
        map.put("b","2");
        return CommonResult.success(map);
    

正常的写法一般是在后面增加返回参数c,让APP的新版本调用新参数c;

如果原来参数不要了,设置值为空。结果如下

  public CommonResult<Map<String,String>> test() 
        Map<String,String> map = new HashMap<String,String>();
        map.put("a","1");
        map.put("b","");
        map.put("c","2");
        return CommonResult.success(map);
    

总结

基本上,接口的设计要求就上面几条了。

总结来说,就以下几点:

1、接口清晰,调用端看得懂,后面自己人修改也不麻烦;

2、外面的坏人很多,最好的办法是自己保护好自己,也就是说鲁棒性要杠杠的;

3、捡芝麻,也不要丢西瓜。修改接口不是重建,而是糊裱匠,顶级的糊裱匠,那是李合肥那种级别的,劝你就别想了,一般人也做不来。

4、...嗯,我还没想到,如果你想到了,就在评论里面加吧。

以上是关于JAVA接口设置基本原则的主要内容,如果未能解决你的问题,请参考以下文章

Java面试自用简洁版

JAVA接口设置基本原则

在每个新字符上触发 WPF TextBox 绑定?

JAVA代码,接口示例

Java入门系列之类继承抽象类接口

Unix编程艺术——摘录一