06.過時類、屬性、接口的正確處理姿式

1. 前言

《手冊》第 7 頁對於過期類有這樣一句描述:java

接口過期必須加 @Deprecated 註解,並清晰地說明採用的新接口或者新服務是什麼。
接口提供方既然明確是過期接口,那麼有義務同時提供新的接口;做爲調用方來講,有義務去考證過期方法的新實現是什麼。

那麼咱們要思考爲何要這麼作呢?這個指導原則如何更好地落地呢?git

2. 爲何要這麼作

若是有機會進入一個大一點的公司,並且你是一個有追求的人,你可能會遇到下面幾種狀況。github

  • 當你接手一個服務,看到某個類、屬性、函數被標註爲 @Deprecated 可是沒有註釋的時候,心裏是崩潰的;
  • 當你對接二方服務,升級 jar 包後發現使用的接口被標記爲廢棄可是沒註釋時,心裏也是崩潰的;
  • 當你看到同事封裝的一些工具類使用了一些被廢棄的類時,你的心裏一樣一樣是崩潰的。不改放在那看着難受,改又無端得耗費本身的時間,並且還怕改出 BUG。

試想一下,若是你接手一個服務裏面的類、屬性和函數要被廢棄了連 @Deprecated 都不加,是否是很容易 「放心」 使用進而被坑?spring

若是被標註爲@Deprecated,給出註釋說明爲何被廢棄,新的接口是什麼,內心會不會更踏實?框架

若是對接的二方服務 jar 包升級之後發現,使用的接口被廢棄且給出詳細的告訴你改用哪一個新接口,是否是內心更有底?maven

試想一下若是咱們每一個人都能遵照這種規約,封裝工具類時遇到過期的類,主動去學習並使用新的替換類,是否是就不會好不少?函數

3. 如何落實

那麼,說了這麼多,究竟該如何落地呢?
我認爲:最好的學習方式之一就是找一些優秀的源碼相關的示例進行學習。工具

3.1 JDK 的類或常見三方庫

咱們以 JDK 中的URLEncoderURLDecoder爲例介紹如何寫過時函數的註釋和如何替換該過時函數:單元測試

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 安裝插件,使用更方便)看常見類庫的常見函數的用法,甚至能夠看到某些函數的使用機率:

image.png
搜索咱們想要的類和方法:URLEncoder.encode,便可獲得 github 優秀的開源框架或 stackoverflow 中相關優秀範例。根據相關的優秀代碼範例進行修改。

image.png

咱們改用新的函數:

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-lang3guava等開源工具庫中隨處可見,值得咱們學習。

隨手選取一個例子,你們感覺一下:

/**
 * <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 上拉取其源代碼,而後找到某個類對應的單元測試類中,在單元測試模塊能夠找到對應的參考用法。還能夠在源碼中打斷點,進行深刻研究。但願你們能夠親自實踐,會有更加深入的體會。

3.2 二方庫

做爲接口的使用者,若是使用二方庫,發現使用的功能被標註爲廢棄。

若是是 maven 項目能夠經過 maven 命令拉取其源碼和 javadoc。

mvn dependency:sources -DdownloadSources=true -DdownloadJavadocs=true

若是是 gradle 項目,也可使用插件下載源碼,查看其將被廢棄的緣由。

若是沒有標註緣由並給出替代方案,或給出的註釋不夠詳細,建議直接和二方包的提供者聯繫,及早替換。

二方庫的工具類替換成新的接口也必需要經過單測,並對涉及的功能進行迴歸。

3.3 本身庫

做爲接口或對象的提供者,廢棄的類、屬性、函數加上廢棄的緣由和替代方案。

如 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「 等來查看添加廢棄註解的人員與之聯繫。避免本身錯代碼,若是搞明白問題且仍然不能廢棄,最好可以主動將廢棄的緣由和替代的代碼補充到註釋中。

若是是三方或二方庫,因爲做者責任性不強或者職業素養不高,對某個接口標記廢棄且沒有任何註釋時,咱們優先在本類中尋找函數簽名類似的函數。若是是開源項目或者公司內部能夠拉取的項目,能夠拉取該項目代碼,找到該類查看提交記錄,從中尋找線索。

不論是三方、二方仍是本身的項目,對替換廢棄的類、屬性和方法等進行修改後,必定要經過單元測試去驗證功能而且對接口使用的功能進行功能測試。

若是要刪除廢棄的屬性或接口,通常先提供新的方案通知使用方修改,此時能夠在將廢棄的接口上加上日誌,新舊接口同時運行一段時間後確認無調用再下一個版本中考慮刪除接口。

若是咱們能快速找到替代的方案,就能夠節省不少時間;若是咱們可以充分地測試,就能夠平穩替換;若是咱們可以介紹清楚廢棄的緣由,提供新的替代方案,並給出快捷的跳轉方式,咱們的專業程度就會提升。

4. 總結

本節的主要介紹過時類、屬性、接口的正確處理姿式,包括添加廢棄註解,添加廢棄的緣由,添加新接口的跳轉等方式,還要在替換後對新接口進行測試測試。本小節還介紹了經過查看相關的優秀開源代碼、使用 codota 工具來學習相關知識的方法。

下節咱們將學習開發中常常碰到的又愛又恨的空指針,瞭解其產生的主要緣由,學習如何儘量地避免。

5. 思考題

從 github 上拉取 Spring 源碼,找到org.springframework.util.Assert#doesNotContain(java.lang.String, java.lang.String, java.lang.String)方法的單元測試代碼並運行。

參考資料


  1. 阿里巴巴與 Java 社區開發者.《 Java 開發手冊 1.5.0:華山版》:2019:7
相關文章
相關標籤/搜索