為什麼學習一門新技術時,所能找到的API文件往往無法滿足我們?所以,此時閱讀程式原始碼會是必要的工作?而這是不是意謂著,程式庫或框架發生抽象滲漏了呢?

文件是一層抽象

在接觸一門新技術時,我多半會從一些簡易的入門文件開始,如果這門技術引起了我的興趣,也許會進一步透過書籍的閱讀,有系統地進行認識,然而,有時候會遇上既有的書籍不多,或內容不夠深入的狀況,在那當下,官方的API文件就會是重要的參考來源。

所謂的「不夠深入」是什麼意思呢?舉個實際的例子,在接觸Flutter時,入門文件為了呈現Flutter的易用性,多半會是簡單的元件組合介紹,然而,我想知道元件間必須遵循什麼規則,才能組合在一起。這就像積木,照著成品的組裝圖你也許可以組合出暴龍,然而,若不明白積木間的銜接方式,收起組裝圖之後,就什麼也組不起來。

而在官方的API文件中,往往會描述元件在組裝時,彼此之間要有的結構或行為關係,具體來說就是型態、繼承、回呼、生命週期之類的關係,在最理想的情況下,官方文件的閱讀,應該可以讓開發者在不查看原始碼的情況下,理解程式庫或框架做了什麼事。

也就是說,官方的API文件本身是一種契約,描述了API公開的介面背後做了什麼事情!「等等!你這種說法是在說官方文件揭露實作細節嗎?」不是的!我只是在說,官方的API文件是一層抽象,對具體實作的抽象。

「抽象?那不是API公開的介面在做的事嗎?」

不!「介面不是抽象」。我在先前專欄〈測試與抽象滲漏〉中就談過,介面只是個溝通的管道,以公開函式來說,就是你給它相關的引數,它傳回結果,而在理想的情況下,會希望函式名稱就能說明它做了什麼,不過,事情往往沒那麼簡單,在函式名稱、參數、傳回值無法表達的細節部份,就需要API文件來加以說明。

以Python的asyncio.run為例好了,函式名稱與引數能說明什麼嗎?不行吧!我們要查看API文件,才能知道它做了什麼,然而API文件並不是在描述程式流程的實作細節,它只是在描述會有個事件迴圈,能幫開發者管理協程,可以使用單執行緒運行之類的,讓開發者能掌握這個函式運作時「大致的輪廓」,從而決定是否信賴asyncio.run這個API。

原始碼中的各種抽象

最近我在看文件時,有時會就原文解釋或單字的字源去理解它,「抽象」是abstract的譯文,劍橋詞典線上英英解釋是「existing as an idea, feeling, or quality, not as a material object」,也就是非實物的想法、感受、特性,abstract來自拉丁文的abstractus,是abs-與trahō的組合,字面上是離開、脫離之義。

從這點來說,有些語言的抽象元件,是從某些概念抽離的不具體特性,想理解這些元件,必須知道它是從哪些概念抽離出來,例如,想理解抽象類別(abstract class),必須知道它從哪些概念抽離而得,如果越能掌握那些來源概念,在運用API提供的抽象時,才越能得心應手。

而對於程式庫或框架學習者為何要有探究原始碼的能力與習慣,這也提出了解答。因為API文件提供了一層抽象,然而,有時候僅知道這層抽象,不足以讓開發者有信心能掌握程式庫或框架,這時就必須親自探究一下原始碼了。

「這不是在介入細節嗎?」不!這只是在試圖理解另一層抽象,好的框架或程式庫,在原始碼上應該也有著適當的抽象階層,能讓開發者依需求逐層探查,而不是馬上進入深層的瑣碎細節,例如,在看asyncio.run的原始碼時,也只是進一步看到如何操作事件迴圈實例,然而看不到事件迴圈的運作或實作等細節,你只是站在事件迴圈實例這一層上,試著去理解asyncio.run做了什麼。

另一方面,我們要記得,程式庫或框架提供了一層抽象,然而它從來不是為了隱藏細節。就如《約耳趣談軟體》中〈抽象滲漏法則〉中,所談到的:「抽象可以節省我們工作的時間,然而並沒有節省我們學習的時間」。

在API文件之後,若是接著探查原始碼,同時,也就是付出學習時間的過程。如果原始碼有適當的抽象階層(其實文件上也需要如此),能讓開發者只探討必要的抽象階層,就不算是抽象滲漏。為了掌握程式庫或框架,適當地選擇、認識抽象階層是必要的。

「你是在說可以依賴在實作嗎?」不!理解原始碼中必要的抽象層,是為了更放心地信任API文件上的規範(或者釐清上頭的誤區,相信我,有時API文件不完全是正確的),開發者仍必須依賴在公開的規範,API文件上沒談到的,就算原始碼中有,就不應該依賴。

當然,會有必要介入細節之時,像是生命週期管理,這是程式庫或框架的責任,若要能讓開發者介入這類細節,應該提供掛勾(hook),並在API文件上公開規範,就文件而言,這類公開規範,會是屬於文件中更低層次的抽象階層。

透過寫作建立抽象層

其實真實世界中人的溝通,本身就是個不斷抽象的過程,不同人看到、聽到、接收到的資訊,就是在對現實世界進行抽象,然後在傳達給另一個人時,又會經過一番抽象,也因此人與人與間的資訊傳遞,往往會產生認知落差的原因。

API文件本身是層抽象,是API的寫手在理解程式庫或框架的規範、看過相關原始碼或者透過其他開發者教導之後,抽象出來的成品,因此API文件本身就會有良莠之別,另一方面,當API文件的抽象不足以說服開發者時,進一步探索其他文件或原始碼,就會是必要的。

因為,這時就是在自行建立一個抽象層,一個專屬於開發者,真正能被開發者理解的抽象層。或許不該說一層,因為就個人理解來說,理解的對象有層次,自身就會在理解上建立不同的階層。

這時,寫作就是整理階層時很好用的一個工具了,由於寫作本身就是個線性的過程,也就可以適當地安排自身理解的抽象層次,這是真正專屬於開發者,在理解程式庫、框架,甚至語言時的抽象。

抽象滲漏的層次是否適當?

「那麼,實際上沒有抽象滲漏這回事囉?」不能這麼說!先前談到,抽象是某些概念抽離的不具體特性,如果程式庫或框架在應用的情境超越了抽離時設定的情境,無論如何都會滲漏。舉個最極端的例子,若在一個具有垃圾收集器的語言中,需要在意垃圾收集器的演算法時,你也可以說這是一種滲漏了。

撇開這個極端的例子而言,若要評估抽象是否滲漏,最好的方式是分層,也就是為了進一步理解一個特性,必須深入的層次要有多少。如果老是要突破至最底層才能理解,無疑地就是很嚴重的滲漏;若是依需求不同,只需進到適當的層次就能理解,這就不會是滲漏。根本來說,「開發者要掌握底層」就是這麼一回事。

抽象分層的概念對文件或原始碼都是一樣的道理,或許軟體開發上,抽象滲漏本來就是必要的,只不過滲漏點是否在適當的層次罷了!

專欄作者

熱門新聞

Advertisement