來自懶惰的品質
有些懶惰,可以帶給客戶堅實的品質。
我在協助客戶導入現代資料棧時,示範了一個活用 select * 的資料轉換寫法。
比方說,原本要寫成:
select
column_1,
column_2,
column_3,
...,
column_55
from {{ ref('table_x') }}可以改成
select
{{ dbt_utils.star(from=ref('table_x'),
except=['column_56'] }}
from {{ ref('table_x') }}什麼時候很適合用這種寫法呢?
如果某個資料轉換並不會對多數的欄位 (column) 做操作,但是又需要將多數的欄位轉遞到下一層去處理時,就很適合這種寫法。在上頭的例子裡,多數的欄位是 column_1 到 column_55 ,可以改成用『不包含 column_56 的所有欄位』來表示。
我告訴客戶的工程師可以這樣子做時,工程師委婉地告訴我,他認為,在該公司的傳統,這種懶惰的寫法不會被接受。
真可惜。
不同的層、不同的抽象
在 A Philosophy of Software Design 一書裡,提到了一個概念,叫做『不同的層、不同的抽象』。意思是指,在上層的函數呼叫下層函數時,上下兩層的函數因為是屬於不同的層 (layer),應該要做不同的任務,也因此是不同的抽象。
書中也舉了下圖的例子來解釋不同層做不同事的重要性。在下圖中,從 main 函數開始,要到了 m3 才會需要 cert 的變數。如果我們把 cert 一路沿著 mian 函數傳遞下去,那 cert 就會曝露在 m1, m2 這兩層不需要應用 cert 變數的抽象層裡,因而增加了無謂的「認知負荷」。此外,日後如果我們需要增加其它要傳遞給 m3 的變數時,也要一併修改 m1, m2 這兩層,這是典型的「變更放大」。也因此,比較好的設計,是設計一個 Context Object來傳遞這種需要跨層傳遞的變數,如此可以明確地分割清楚不同層的任務。
在本文開頭的現代資料棧的例子裡,select * 裡的 * 就可以看成是上頭中的 Context Object。因為凡是這一層裡不需要去考慮的欄位,都可以一律放進 Context Object,如此一來,就做出了近似於跨層傳遞欄位的效果了。
不同的立場、不同的品質觀點
引述一小段對品質的論述來自創新與創業精神一書
產品或服務的品質不是廠商所賦與的,而是顧客所發掘並願意付錢購買的東西。如果某一項產品甚難製造,且成本高昂,製造廠商通常會認為這項產品具有品質。
…
一九五○年代時,美國電子產品的製造廠商們深信,它們花了三十年努力所製造出更複雜、更大和更昂貴的收音機,裡面裝的是完美的真空管,這些因素就構成所謂的「品質」。它們認為製造真空管必須具備複雜的技巧,因此真空管就是品質。至於電晶體收音機的構造很簡單,未受過訓練的工人亦可加入生產線工作,因此電晶體就不是品質;但在消費者的眼光中,電晶體收音機的品質要超過真空管甚多。由於電晶體收音機的重量甚輕,因此…
在軟體開發、資料處理之中,許多傳統的作法都需要軟體工程師鍛鍊複雜的技巧,如此才能處理巨大的認知負荷、變更放大。也因此,終於從媳婦熬成婆、變成工程師主管的主事者,有時候會認為『靜態型別』就是品質、『嚴格地照某些標準』去實作就是品質。
很可惜,決定「品質」兩個字的,不是製造廠商、不是工程師主管。
既然如此的話,我建議,多加考慮一下「來自懶惰的品質」,至少你確定可以享受懶惰。


