1. 程式人生 > >【工程實踐】服務器數據解析

【工程實踐】服務器數據解析

something 時間比較 數據訪問 shu 成員 字段值 ear 計時 日誌

本文來自網易雲社區

作者:孫建順


在客戶端開發過程中一個重點內容就是解析服務器數據,關於這個話題也許大家首先會去思考的問題是用哪個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+款雲產品!

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


相關文章:
【推薦】 Question|網站被黑客掃描撞庫該怎麽應對防範?
【推薦】 當我們在談論multidex65535時,我們在談論什麽
【推薦】 21分鐘學會寫編譯器

【工程實踐】服務器數據解析