《手册》第 7 页对于过期类有这样一句描述:java
接口过期必须加 @Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
接口提供方既然明确是过期接口,那么有义务同时提供新的接口;做为调用方来讲,有义务去考证过期方法的新实现是什么。
那么咱们要思考为何要这么作呢?这个指导原则如何更好地落地呢?git
若是有机会进入一个大一点的公司,并且你是一个有追求的人,你可能会遇到下面几种状况。github
试想一下,若是你接手一个服务里面的类、属性和函数要被废弃了连 @Deprecated 都不加,是否是很容易 “放心” 使用进而被坑?spring
若是被标注为@Deprecated
,给出注释说明为何被废弃,新的接口是什么,内心会不会更踏实?框架
若是对接的二方服务 jar 包升级之后发现,使用的接口被废弃且给出详细的告诉你改用哪一个新接口,是否是内心更有底?maven
试想一下若是咱们每一个人都能遵照这种规约,封装工具类时遇到过期的类,主动去学习并使用新的替换类,是否是就不会好不少?函数
那么,说了这么多,究竟该如何落地呢?
我认为:最好的学习方式之一就是找一些优秀的源码相关的示例进行学习。工具
咱们以 JDK 中的URLEncoder
和URLDecoder
为例介绍如何写过时函数的注释和如何替换该过时函数:单元测试
String url = "xxx"; String encode = URLEncoder.encode(url); log.debug("URL编码结果:" + encode); String decode = URLDecoder.decode(encode); log.debug("URL解码结果:" + decode);
在 IDEA 中编写如上代码时候,java.net.URLEncoder#encode(java.lang.String)
和java.net.URLDecoder#decode(java.lang.String)
会有删除的标志,便表示该函数已通过期。学习
那么如何找到新函数和修改呢?
咱们进到源码里查看:
/** * Decodes a {@code x-www-form-urlencoded} string. * The platform's default encoding is used to determine what characters * are represented by any consecutive sequences of the form * "<i>{@code %xy}</i>". * @param s the {@code String} to decode * @deprecated The resulting string may vary depending on the platform's * default encoding. Instead, use the decode(String,String) method * to specify the encoding. * @return the newly decoded {@code String} */ @Deprecated public static String decode(String s) { String str = null; try { str = decode(s, dfltEncName); } catch (UnsupportedEncodingException e) { // The system should always have the platform default } return str; }
在@deprecated
的注释里咱们找到了答案:“The resulting string may vary depending on the platform’s default encoding.(解析结果的字符串和系统的默认字符编码强关联)”,并给出了替代函数的说明 “Instead, use thedecode(String,String)
method to specify the encoding.(使用decode(String,String)
函数来指定字符串编码)”
所以咱们提供新的接口,就得接口要废弃时也能够参考这里写上废弃的缘由以及替代的新接口。
咱们还能够经过codota来搜索(建议在 IDEA 安装插件,使用更方便)看常见类库的常见函数的用法,甚至能够看到某些函数的使用几率:
搜索咱们想要的类和方法:URLEncoder.encode,便可获得 github 优秀的开源框架或 stackoverflow 中相关优秀范例。根据相关的优秀代码范例进行修改。
咱们改用新的函数:
String url = "xxx"; String encode = URLEncoder.encode(url, Charsets.UTF_8.name()); log.debug("URL编码结果:" + encode); String decode = URLDecoder.decode(encode, Charsets.UTF_8.name()); log.debug("URL解码结果:" + decode);
对相似废弃的接口的改动,最好要使用单元测试进行验证:
/** * 新旧两种接口对比 * * @throws UnsupportedEncodingException */ @Test public void testURLUtil() throws UnsupportedEncodingException { String url = "http://www.imooc.com/test?name=张三"; // 旧的函数 String encodeOrigin = URLEncoder.encode(url); String decodeOrigin = URLDecoder.decode(encodeOrigin); // 新的函数 String encodeNew = URLEncoder.encode(url, Charsets.UTF_8.name()); String decodeNew = URLDecoder.decode(encodeNew, Charsets.UTF_8.name()); // 结果对比 Assert.assertEquals(encodeOrigin, encodeNew); Assert.assertEquals(decodeOrigin, decodeNew); }
若是是常见的三方库,也能够采用相似的步骤,通常都很快解决问题。
如咱们发现下面的函数被废弃,进入到源码中查看:
org.springframework.util.Assert#doesNotContain(java.lang.String, java.lang.String)
/** * @deprecated as of 4.3.7, in favor of {@link #doesNotContain(String, String, String)} */ @Deprecated public static void doesNotContain(String textToSearch, String substring) { doesNotContain(textToSearch, substring, "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); }
直接经过点击{@link #doesNotContain(String, String, String)
能够快速进入新的替代函数去查看。
从这里例子咱们还学到了一个新的技巧,若是是二方库或者三方库,废弃的属性、函数在注释中除了能够写缘由和替代函数外,能够标注从哪一个版本被标注为废弃。替代函数可使用{@link}
方式,更便捷和优雅。
再回顾上面java.net.URLDecoder#decode(java.lang.String)
的注释就没有提供这种方式,跳转就不够方便。
另外你们还能够学习一下@see
的用法,以及@see
和{@link}
的区别,后面专栏也会对注释作专门的讲解。
咱们从这个例子还能够看到注释中并无说明废弃的缘由,做为读者你会发现有些摸不着头脑,内心嘀咕 “为啥被废弃?”。
经过替换函数以及注释咱们能够猜想废弃的缘由是:” 默认的提示文本不够优雅 “且即便断言经过,第三个参数字符串拼接仍然会执行,形成没必要要字符串链接操做。这点有点相似于日志中不建议使用字符串拼接当作日志内容(能够采用占位符的方式)。
新的替换函数的注释除了给出功能介绍外,也给出了使用的范例:
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
这里给咱们带来的启发是,写工具类时若是能再注释上添加一些范例和结果,则会极大方便使用者。
这点在commons-lang3
和guava
等开源工具库中随处可见,值得咱们学习。
随手选取一个例子,你们感觉一下:
/** * <p>Strips whitespace from the start and end of a String returning * {@code null} if the String is empty ("") after the strip.</p> * * <p>This is similar to {@link #trimToNull(String)} but removes whitespace. * Whitespace is defined by {@link Character#isWhitespace(char)}.</p> * * <pre> * StringUtils.stripToNull(null) = null * StringUtils.stripToNull("") = null * StringUtils.stripToNull(" ") = null * StringUtils.stripToNull("abc") = "abc" * StringUtils.stripToNull(" abc") = "abc" * StringUtils.stripToNull("abc ") = "abc" * StringUtils.stripToNull(" abc ") = "abc" * StringUtils.stripToNull(" ab c ") = "ab c" * </pre> * * @param str the String to be stripped, may be null * @return the stripped String, * {@code null} if whitespace, empty or null String input * @since 2.0 */ public static String stripToNull(String str) { if (str == null) { return null; } str = strip(str, null); return str.isEmpty() ? null : str; }
对于常见的三方库,还有一个不错的技巧:咱们能够从 github 上拉取其源代码,而后找到某个类对应的单元测试类中,在单元测试模块能够找到对应的参考用法。还能够在源码中打断点,进行深刻研究。但愿你们能够亲自实践,会有更加深入的体会。
做为接口的使用者,若是使用二方库,发现使用的功能被标注为废弃。
若是是 maven 项目能够经过 maven 命令拉取其源码和 javadoc。
mvn dependency:sources -DdownloadSources=true -DdownloadJavadocs=true
若是是 gradle 项目,也可使用插件下载源码,查看其将被废弃的缘由。
若是没有标注缘由并给出替代方案,或给出的注释不够详细,建议直接和二方包的提供者联系,及早替换。
二方库的工具类替换成新的接口也必需要经过单测,并对涉及的功能进行回归。
做为接口或对象的提供者,废弃的类、属性、函数加上废弃的缘由和替代方案。
如 RPC 订单常见接口的OrderCreateParam
参数类的 JSON 类型参数:orderItemDetail
要替换成列表orderItemParams
下面的属性类型进行替换:
public class OrderCreateParam { /** * 对象详情 * 参考示例:'[{"count":22,"name":"商品1"},{"count":33,"name":"商品2"}]' * <p> * 废弃缘由:订单详情由JSON传参,改成对象传参。 * 替代方案: {@link com.imooc.basic.deprecated.OrderCreateParam#orderItemParams} */ @Deprecated private String orderItemDetail; private List<OrderItemParam> orderItemParams; // 其余属性 }
本身类的变更要经过单元测试进行验证:
@Test public void testOriginAndNew() { OrderCreateParam orderCreateParamOrigin = new OrderCreateParam(); // 原始JSON属性 orderCreateParamOrigin.setOrderItemDetail("[{\"count\":22,\"name\":\"商品1\"},{\"count\":33,\"name\":\"商品2\"}]"); OrderCreateParam orderCreateParamNew = new OrderCreateParam(); // 新的对象属性 List<OrderItemParam> orderItemParamList = new ArrayList<>(2); OrderItemParam orderItemParam = new OrderItemParam(); orderItemParam.setName("商品1"); orderItemParam.setCount(22); orderItemParamList.add(orderItemParam); OrderItemParam orderItemParam2 = new OrderItemParam(); orderItemParam2.setName("商品2"); orderItemParam2.setCount(33); orderItemParamList.add(orderItemParam2); orderCreateParamNew.setOrderItemParams(orderItemParamList); Assert.assertEquals(JSON.toJSONString(orderCreateParamNew.getOrderItemParams()), orderCreateParamOrigin.getOrderItemDetail()); }
这里给出一个简单的模拟范例,实际业务代码中参数的接口还要进行 mock 单元测试(后续章节会有相关介绍),对应接口要根据变更传入不一样的参数进行功能测试。
如若是实际开发中本身须要改动的功能涉及到废弃的类、属性、函数等,且没有详细地注释,没法获知废弃的缘由和替代的方案。能够经过 IDEA 的 “annotate” 菜单,或者 “Git” - ”Show History for Selection“ 等来查看添加废弃注解的人员与之联系。避免本身错代码,若是搞明白问题且仍然不能废弃,最好可以主动将废弃的缘由和替代的代码补充到注释中。
若是是三方或二方库,因为做者责任性不强或者职业素养不高,对某个接口标记废弃且没有任何注释时,咱们优先在本类中寻找函数签名类似的函数。若是是开源项目或者公司内部能够拉取的项目,能够拉取该项目代码,找到该类查看提交记录,从中寻找线索。
不论是三方、二方仍是本身的项目,对替换废弃的类、属性和方法等进行修改后,必定要经过单元测试去验证功能而且对接口使用的功能进行功能测试。
若是要删除废弃的属性或接口,通常先提供新的方案通知使用方修改,此时能够在将废弃的接口上加上日志,新旧接口同时运行一段时间后确认无调用再下一个版本中考虑删除接口。
若是咱们能快速找到替代的方案,就能够节省不少时间;若是咱们可以充分地测试,就能够平稳替换;若是咱们可以介绍清楚废弃的缘由,提供新的替代方案,并给出快捷的跳转方式,咱们的专业程度就会提升。
本节的主要介绍过时类、属性、接口的正确处理姿式,包括添加废弃注解,添加废弃的缘由,添加新接口的跳转等方式,还要在替换后对新接口进行测试测试。本小节还介绍了经过查看相关的优秀开源代码、使用 codota 工具来学习相关知识的方法。
下节咱们将学习开发中常常碰到的又爱又恨的空指针,了解其产生的主要缘由,学习如何尽量地避免。
从 github 上拉取 Spring 源码,找到org.springframework.util.Assert#doesNotContain(java.lang.String, java.lang.String, java.lang.String)
方法的单元测试代码并运行。