1. 程式人生 > >【工程實踐】伺服器資料解析

【工程實踐】伺服器資料解析

本文來自網易雲社群

作者:孫建順

在客戶端開發過程中一個重點內容就是解析伺服器資料,關於這個話題也許大家首先會去思考的問題是用哪個json解析庫。是的,目前通過json格式進行資料傳輸是主流的方式,確實不同的json解析庫在效能方面也有一些差異。以上問題固然重要,但是在開發過程中經常遇到的往往並不是效能相關的問題,本文從資料使用的角度給大家分享一些經驗。

  • 資料成員設為private,預設不提供set方法

解析得到的資料在產品中被修改的機率極低,資料的修改很容易導致bug的產生。另外,資料物件解析的過程中往往是通過反射直接構建物件,基於以上兩點思考,我們提倡對資料的修改遵循關閉原則,即預設不提供set方法,只有極少數情況允許對資料進行修改。

  • 提供友好的資料訪問介面

讀取資料時往往是帶著具體的場景使用的目的而來,最原始的資料通常並不能夠直接滿足使用,經常需要做一些簡單處理。因此,通過get方法提供更友好的資料獲取方式。在get方法中不只是返回最原始的資料,而是疊加上一些簡單的處理或者資料的拼裝。例如:

public class LearnInfo {    // 原始資料
    private int learnStatus;    // 簡單封裝後的方法
    public boolean isLearned() {        return learnStatus == 2;
    }
}public class Example {    private void doSomething1(LearnInfo learnInfo) {        // 通過獲取原始資料做業務處理
        if (learnInfo.getLearnStatus() == 2) {            // dosomething
        }
    }    private void doSomething2(LearnInfo learnInfo) {        // 通過簡單封裝過的介面獲取狀態資訊
        if (learnInfo.isLearned()) {            // do something
        }
    }
}
  • 隔離json字串與程式碼呼叫之間的耦合

資料解析庫往往通過反射呼叫成員欄位,導致成員變數的命名與json串中的字元存在嚴格的對應關係,通過get方法可以遮蔽掉這種強耦合依賴。例如:版本1.0

public class Course {    private description;    public String getDescription() {        if (description == null) {            return "";
        }        return description;
    }
}public class Example {    private void doSomething(Course course) {
        mTextView.setText(course.getDescription());
    }
}

版本1.1,由於某些原因在版本迭代過程中後端欄位可能會發生更改

public class Course {    // 此處被修改了
    private desc;    // 介面不變
    public String getDescription() {        if (desc == null) {            return "";
        }        return desc;
    }
}public class Example {    // 呼叫方無需修改
    private void doSomething(Course course) {
        mTextView.setText(course.getDescription());
    }
}
  • 除錯與日誌

Android Studio中針對成員變數可以通過Field Watchpoint進行斷點除錯,而引入get方法之後,增加了常規斷點除錯的方式。在有必要的情況下,還可以在get方法中增加日誌。

  • 引入資料合法性檢查機制

一旦非法資料進入到系統以後,會帶來很多異常的情況,在程式碼設計時異常邏輯分支處理不夠充分的話,很容易導致系統異常,甚至應用程式崩潰。因此,比較建議儘量在源頭堵截住異常資料的進入,越早處理對後期的影響越小。一般來說從伺服器獲取資料在將其解析為系統中真正有含義的物件時,視為檢查資料合法性的第一時間比較恰當。常見的非法資料。

  1. 不允許空的欄位返回了空值

java語言對空指標的處理並不友好,NullPointerException是導致崩潰的主要原因之一。在系統迭代過程中經常出現數據結構中該返回值的地方卻返回了空值。針對這一問題有多種解決方案,有一種做法是做系統全域性的異常捕獲,這種做法簡單粗暴,沒有從根源上解決問題,不夠優雅。另一種做法在用到資料的地方全部加上判空,這種做法繁瑣,容易遺漏,開發人員比較痛苦。筆者在專案開發過程中嘗試了從框架層面解決這一問題的技術方案。引入Nullable與NotNull註解的支援,在定義欄位時通過註解註明,在解析時校驗欄位值是否符合註解描述。註解方式一般來說只適合解決與後端約定不允許空的值的校驗。事實上為了滿足更好的使用者體驗,更多的欄位在設計時會傾向於允許空值。因此在允許空值進入的情況下,在設計get方法時要求不能返回空值,做第二層防護。例如:

public class Course {    @NotNull
    private long id; // id 不允許空

    private List<Unit> units; // 列表可以為空
    private String description; // 描述資訊可以為空

    // 不允許直接返回null值,降低呼叫方的使用成本
    public List<Unit> getUnits() {        if (units == null) {            return new ArrayList();
        }        return units;
    }    // 不允許返回null值,降低呼叫方的使用成本
    public String getDescription() {        if (description == null) {            return "";
        }        return description;
    }
}
  1. 返回的資料不符合邏輯

在某些情況下,解析得到的資料不符合邏輯,例如:

public class PageInfo {
    private totalCount;    private currentPage;
}totalCount : 0, currentPage : 11

由於伺服器上異常處理不當,客戶端解析得到totalCount為0,currentPage為11,顯然不符合邏輯。若將此資料繼續往下傳遞很有可能引發陣列越界的異常,從而導致應用程式崩潰。 因此,我們還引入了第三層防護機制,定義LegalModel介面,程式碼如下:

public interface LegalModel {    boolean check();
}public class PageInfo implements LeagalModel {    private totalCount;    private currentPage;    @Override
    public boolean check() {        return totalCount > currentPage;
    }
}public class Example {    private void doSomething(PageInfo pageInfo) {        if (!pageinfo.check()) {            // 判斷資料不合法
        }
    }
}
  • 關於混淆

當json解析庫通過反射構建資料物件時,資料物件類不能參與混淆,否則就無法找到對應的欄位名。避免混淆通常的方法是將資料模型類集中放置在相同的包名下面,在混淆配置中通過配置路徑來避免混淆。這種方式有一個比較明顯的侷限性,即當檔案換一個路徑以後,則要在配置檔案中追加相應的路徑。這種限制對開發人員操作過程中非常不友好,比較繁瑣,而且特別容易遺漏。因此,推薦大家用一種通過空介面的方式,來解決避免混淆問題。程式碼如下:

// 定義防止混淆空介面public interface NoProguard {

}// 不需要混淆的類去實現空介面public class Course implements NoProguard {    private String description;    // 內部類同樣適用
    public static class LearnInfo implements NoProguard {        private int learnStatus;    
    }
}// 配置程式碼-keep interface com.example.NoProguard {*;}
-keep class * implements com.example.NoProguard {*;}
  • 建議使用基本型別取代包裝類

包裝類的預設值為null,非常容易引起空指標異常。而基本型別自帶預設值,省去了大量的判空程式碼。以int與Integer為例,某些情況下對於int預設值0會有一定的含義,此種場景的機率不高,一般來說在約定時可以儘量避免使用0值來簡單規避。

網易雲免費體驗館,0成本體驗20+款雲產品!

更多網易研發、產品、運營經驗分享請訪問網易雲社群