面向資料的程式設計 · Laurent

banq發表於2022-02-24

我學習了DDD領域驅動設計、六邊形架構面向資料的程式設計。我在 Airbnb與Daniel Low在Krispr的工作中使用了前兩個。當我們的需求發生變化時,它可以更輕鬆地擴充套件專案、重新設計其依賴項和交付機制。然而,今天,我正在寫我最近的興趣:面向資料的程式設計。
 

面向資料的程式設計

Yehonathan Sharvit推廣該術語Data-Oriented Programming以包含一組原則,使開發程式碼庫變得更容易。雖然這些原則都不是新穎的,但它們在行業中並不是主流,它們確實使使用程式碼變得容易得多。
多年來閱讀 Yehonathan 的部落格文章後,我買了他的書,我很喜歡!當談到技術寫作風格時,這是一股清新的空氣。這本書講述了兩個開發人員一起學習面向資料的程式設計原理的故事。它使用了一種引人入勝的對話風格,讓你想更多地瞭解他們的故事以及他們正在解決的下一個問題。
在這篇文章中,我將解釋我從書中得到的 3 個關鍵思想:

  1. OOP 迫使您一次性同時做出太多限制設計並導致重寫的決定
  2. 使用通用和開放的資料結構表示所有資料,更容易探索和擴充套件系統的狀態
  3. 不可變的資料結構是必須的,它使推理程式更容易

 

1. OOP迫使你同時做出太多限制設計和導致重寫的決定
在做一個副業的時候,直到最近,我預設的選擇是使用Python的OOP。我注意到,隨著專案的發展,我傾向於重寫很大一部分程式碼,以更好地表達我的想法,選擇更好的名字,更好的類層次,以不同的方式表示資料或副作用。OOP吸引了我的完美主義傾向,有時會讓我想重寫系統中沒有必要重寫的部分。它刺激了我的智力,看我如何能在資料上提出最好的抽象,最容易使用的模式。

在讀完面向資料程式設計的OOP章節後,我改變了主意。我覺得有必要嘗試用Clojure構建我所有的副業,並從一開始就實踐程式碼和資料的分離。這就是我在最近的專案中採用的風格:一個跟蹤我的預算並自動對進入的交易進行分類的工具(就像Mint,但在對交易進行分類方面做得更好)。我只寫了一次專案,還沒有感覺到重做它的核心部分的衝動。最近,我也開始重構程式碼,把所有的資料從Clojure檔案中提取出來,放到一個配置檔案中,這非常容易,只需要10分鐘。

{
 :start "01/01/2021"
 :end "12/31/2021"
 :sources [{:type "folder-recursive" :value "/Users/laurent/Downloads"}]
 :input-folder "/Users/laurent/Downloads"
 :report-name "2021"
 :output-folder "/Users/laurent/Documents/budget"
 :exclusion-rules '[("description%" #{"AUTOPAY PAYMENT"})]
 :categories-rules
 '[
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   ;; High priority rules                             ;;
   ;; These entries categorize specific transactions  ;;
   ;; with a specific date, they take precedence over ;;
   ;; the other rules                                 ;;
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


   ("venmo"                   "laurent/haircut" {:date "03/25/2021" :amount 40.0 :note "Tried a new hair style, it turned out great, go back there" :rating "A"})
   ...
   ]'
 }


簡而言之,我將繼續分離資料和程式碼,並從一開始就為我的副專案採用函式式風格。它只是讓我更有效率,並消除了我重構為更好抽象的傾向。
 

2. 不可變的資料結構是必須的,它使推理程式更容易
閱讀程式碼時,您必須牢記很多上下文和抽象概念。我在與其他人的工作中發現,與閱讀散文不同,閱讀一段複雜的程式碼對於最有經驗的程式設計師來說也是一項挑戰。在使用不具有不可變資料結構的語言(大多數語言)時,我們要牢記的一個特別考慮因素是操作可能會發生in place或return a new copy.
例如在 Python 中,對列表進行排序:
list.sort() 對列表進行排序並替換原始列表,而 sorted(list) 返回列表的排序副本,而不更改原始列表。
一些語言和社群已經找到了區分這兩者的方法,例如 Ruby 社群已經對!字元進行了標準化,以指示發生變化的操作。然而,記住約定並應用它取決於各個程式設計師。這就是為什麼,即使我閱讀 ruby​​ 程式碼,有時我也必須檢查我使用的是哪種操作風格。
面向資料的程式設計中,Yehonathan 提倡不可變和持久的資料結構。首先是一些定義:

  • 不可變資料結構

一旦有了值就不能改變的資料結構。它也被稱為“凍結”資料結構。這是一個抽象的概念。
  • 持久資料結構

使用結構共享實現高效的不可改變的資料結構的方法
  • 兩個資料結構之間的結構共享

當資料結構引用同一塊記憶體時,即使它們彼此不同。一個例子說明了這一點:對列表 ["a", "b"] 和列表 ["a", "b", "c"] 進行映像。後者可以建立在前者之上,重用記憶體佈局並在其末尾新增“c”。

不可變資料結構是主流,我已經看到它們在業界的應用,特別是在Java的Guava中,但永續性的資料結構不是,這是一種恥辱,因為它們是表示不可變資料結構的有效方法, Clojure是我所知道的唯一一門語言,該語言中的所有資料結構預設都是永續性的。
 
建議:如果你在面試中提到持久化資料結構作為解決問題的一種方式,請確保你知道如何實現它們(閱讀一些論文,例如這篇)。

當你使用不可變資料結構(無論是否持久化)時,一整類bug就會消失,你不必一直考慮操作是否會突變底層資料,它們根本不會這樣做。

我喜歡Yehonathan在《面向資料的程式設計》中解釋所有這些概念,並連結到使用這種資料結構的常見語言的庫。它為我澄清了這些術語,並使我更加確信,不可變資料結構幾乎總是要走的路。唯一的例外是對效能高度敏感的程式碼,但我的工作中很少寫這種程式碼。
 

3. 使用通用和開放的資料結構表示所有的資料,這使得探索一個系統的狀態和擴充套件它變得更加容易。
早些時候,我提到我如何配置我的預算分析器。在配置中,你可能已經注意到這一行。

("venmo" "laurent/haircut" {:date "03/25/2021"
                            :amount 40.0
                            :note "Tried a new hair style, it turned out great, go back there"
                            :rating "A"})


專案的程式碼中沒有任何東西知道:note和:rating,我只是在寫規則時加入了它們,以對交易進行分類,記錄我喜歡那個髮型的事實。這之所以可能,只是因為我使用通用和開放的資料結構來表示我的所有資料。在Clojure中,我使用列表、向量、地圖和原始型別來表示我程式中的所有資料。如果你習慣於OOP,這聽起來可能很可怕,Yehonathan在他的書中再一次很好地駁斥了為什麼它一點也不可怕的原因!
由於方法上的這一小小的改變,我能夠在資料上使用豐富的通用操作(例如由Clojure標準庫提供),我也可以輕鬆地進行序列化和反序列化,檢查系統的執行狀態併為資料提供時間旅行機制。
  

結語
如果你喜歡我上面介紹的一些想法,我鼓勵你閱讀《面向資料的程式設計》,看看你如何以不同於你所習慣的角度來處理程式設計任務。它已經成為我在副業和不受制於OOP程式碼庫時的新工作方式。

相關文章