初次接觸某程式庫時,有範例豐富的書籍或文件非常重要,然而若要活用甚至評估程式庫優缺點的話,就不能僅止於範例的臨摩。

從範例程式碼著手
無論如何,能有範例程式碼絕對是個好的起點,不過一開始並非指書本的範例程式碼,而是指程式庫官方網站提供的範例程式碼。

如果程式庫本身希望獲得關注,多半都會提供最具特色的範例程式碼,就像是程式語言的Hello!World!範例。程式庫本身的範例程式碼,呈現出本身最基本的風格與結構,也可從中看出程式庫創建者或參與成員,想要傳達的設計理念及意義。

不過並非每個程式庫的創建者或參與者都有合適的文筆,有能力清楚描述程式庫的行為與運作方式,有時文件的撰寫者也不一定是程式庫參與成員,而可能是另一組人馬,像是社群成員貢獻的文件,程式庫文件也可能過期。

諸如此類,文件描述模糊不清甚至是誤導方向是有可能的,適當保持懷疑心態,親手跟著文件做些範例驗證,以確認正確性,甚至照文件想法,自行模仿實作簡易版本程式庫,以驗證文件中的觀念。

通常官方提供的範例程式碼不多,透過網路搜尋更多範例程式碼是必要的,官方文件基本上會列出程式庫本身的優點,然而網路上會有各種範例呈現出程式庫的正反面,特別是在像stackoverflow.com這樣的地方,官方網站的文件呈現出程式庫參與者希望你採用的方式,然而往往在搜尋網路更多的意見後,才可看出實際上程式庫該有的使用樣貌。

摸清API架構
在大致看過一些範例程式碼後,下一步就是透過API文件勾勒出程式庫的主要API架構,這是為了在沒有任何範例程式碼可以觀看的情況下,也能夠自行活用、組合程式庫中的相關API。

以Java本身提供的標準程式庫為例,想活用群集(Collection)框架,首先就是按照Javadoc文件勾勒出主要的介面定義與實作類別,例如,想活用串流輸入輸出,就是瞭解InputStream、OutputStream繼承架構與相關裝飾器類別;若想活用JDBC、NIO2等,就得勾勒出標準介面與實作者之間的關係。

有時程式庫是另一程式庫的擴充或功能增強,此時除了瞭解架構,得連同舊程式庫架構一併瞭解。

例如想瞭解guava-libraries中對JDK群集框架的擴充,就得瞭解JDK群集框架中的介面定義,接著才瞭解那些ImmutableCollection、Multiset、Multimap、BiMap實際上的繼承與實作關係,如此你才會知道Multiset不是Set,而Multimap也不是Map。

有時,同性質的兩個程式庫,使用與組合時的範例程式碼會極為相似,通常這是為了有平滑的轉換曲線,但實際上兩者的API架構可能有很大的不同,日後想要活用,還是得瞭解兩者API架構的不同。

例如,JSR310的許多概念來自Joda-Time,然而Joda-Time將瞬時(Instant)、局部(Partial)等概念,實際對應至API的ReadableInstant、ReadablePartial等介面定義,也就是將它們視為行為,而不是具體概念;JSR310則是將瞬時對應為實際的Instant類別,也就是機器上具體的時間概念,因為JSR310沒有局部概念,有的是人類具體的時間概念,像是當地時間、年、月、時、分、秒等,可對應至實際的LocalDateTime、Year等類別。JSR310的介面定義是屬於框架等級的API,例如TemporalAccessor介面定義了時間基本讀取行為,Temporal介面增加了加、減與調整等時間讀寫行為。

由於JSR310原預計於JDK7發表,然而因為一些因素的考量,轉而在未來的JDK8發表,在這段間隔下,雖然JSR310核心概念不變,然而API架構有了許多調整,許多文件已經過時,直接從目前的實作版本中瞭解API架構,就更顯重要,在輔以核心概念的情況下,對於文件不足的程式庫,通常可藉由瞭解API架構,自行摸索出程式庫更多的使用方式。

閱讀原始碼

無論是範例程式碼或是API文件,揭示的都只是抽象化後的公開協定,若單單僅止於拼湊與使用,基本上無需追究程式碼實作,然而有時我們會想知道細節,以便進一步瞭解、善用或避免誤用程式庫。

舉例而言,guava-libraries中的ImmutableCollection提供不可變(Immutable)群集,官方Wiki中提到了copyOf方法雖然名義上是複製,但實際上可能不會真的進行複製,以在某些時候節省資源以增進效能,至於會複製,以及不複製的情況,API文件中並不明載(Undocumented),如果你在意這方面的效能問題,就得直接閱讀原始碼來得知。

另一方面,ImmutableCollection與其子介面,到底實作時分別運用了哪些資料結構,API文件上也不載明,這本意是要API使用者完全信任工廠方法,然而實際上很多情況需要知道實際資料結構,而這可從原始碼中得知,因為ImmutableCollecton物件都是不可變,所以內部實作時若僅包括單元素則直接包裹,多個元素則多半使用陣列,即使是ImmutableSet也不例外,以獲取更好的效能。

透過閱讀原始碼,可瞭解這類群集因為不可變特性,而可共用一些資料結構,開發者在使用上考量到效能問題時,才能更加善用程式庫提供的特性。

有些程式庫提供擴充用的介面,並會提供預設的實作品,透過閱讀這些實作品的原始碼,為必須自行擴充程式庫的場合提供了參考方向。

例如,guava-libraries的Range物件僅僅表示範圍,無法直接針對範圍進行迭代,如果要迭代範圍中的不連續元素,則必須實作DiscreteDomain定義範圍內不連續元素間的關係,DiscreteDomain提供可迭代int、long與BigInteger三種型態的預設實作,不過都是DiscreteDomain實作的私有內部類別,而透過閱讀IntegerDomain、LongDomain與BigIntegerDomain,就可以明瞭如何自行定義DiscreteDomain,從而瞭解DiscreteDomain本身扮演了產生器(Generator)的角色。

簡單來說,無論是透過API架構的勾勒,或者是原始碼的閱讀,都在於發掘沒有記載於文件的程式庫運用方式。多數的情況下,程式庫應提供良好的說明文件,然而有時因為程式庫發展快速,或者是程式庫維護組織或社群之風氣使然,而無法提供良好說明文件,透過API架構的勾勒或是原始碼閱讀,就是瞭解程式庫的有力手段。

認識創建者與演進歷史
程式庫會有創建者,也有演進歷史,瞭解建立程式庫的初始理念,並瞭解程式庫演化過程,有助於得知程式庫為何有今日樣貌,而能更進一步活用。

Java日期時間API的演進就是個例子,有關於Date、Calendar的問題,可參考我之前的專欄〈從JDK時間API演進看時間處理〉;有感於Date、Calendar定義的不完善與API的混亂,Stephen Colebourne創建了Joda-Time,並提出了瞬時、局部等解決Java日期時間API的幾個核心概念,而Stephen Colebourne後來帶著Joda-Time中得到的教訓與經驗,參與了JSR310的制訂,如前所述,JSR310與Joda-Time的API架構截然不同,然而若閱讀過Stephen Colebourne撰寫的〈Why JSR-310 isn't Joda-Time〉,就不難理解為何會有如此之差別。

從範例程式碼著手之後,摸清API架構、閱讀原始碼、認識創建者與演進歷史,就現今速食年代來說,確實是個笨方法,初期投入的時間成本雖高,對程式庫卻能夠有札實的認識,在後續就能靈活運用、擴充程式庫,在真正遇到問題時,也才能精準地自行尋求解答,或者是在必須發問討論時,能夠精準地描述問題,在最短時間內解決問題。

想掌握最新IT動態,歡迎按贊加入iThome粉絲團

專欄作者

熱門新聞

Advertisement