輾轉(zhuǎn)多個公司,我從與數(shù)據(jù)打交道的工作中學到了什么?
本文轉(zhuǎn)載自公眾號“讀芯術(shù)”(ID:AI_Discovery)。
數(shù)據(jù)工程是本世紀發(fā)展最快的行業(yè)之一。工作以來,筆者在各個公司見識了許多確保數(shù)據(jù)和代碼質(zhì)量的方法。每個公司可能遵循不同的流程和標準,但是有一些通用的原則可用于提高開發(fā)速度,改進代碼維護,降低數(shù)據(jù)工作難度。
1. 函數(shù)式編程
筆者學習的第一種編程語言是Java。盡管深知面向?qū)ο缶幊膛c創(chuàng)建可重用類和模塊相關(guān)的好處,但其在處理數(shù)據(jù)時不便應用。兩年后,筆者偶然發(fā)現(xiàn)了R語言——一種函數(shù)式編程語言,筆者立刻就被它迷住了。
R語言能夠使用dplyr包并簡單地通過管道傳輸函數(shù)來轉(zhuǎn)換數(shù)據(jù)并快速查看結(jié)果,大大便利了工作。但現(xiàn)在,Python要求將二者結(jié)合,編寫面向?qū)ο蟮哪K化腳本,同時利用函數(shù)式編程,這種編程在與R語言中的數(shù)據(jù)交互時運行良好。
函數(shù)式編程極其適用于處理數(shù)據(jù),原因就在于幾乎任何數(shù)據(jù)工程任務都可以通過輸入數(shù)據(jù)實現(xiàn),應用一些函數(shù)(也就是說,實現(xiàn)ETL中的T環(huán)節(jié),即數(shù)據(jù)的轉(zhuǎn)換,清洗及加載),將其輸出加載到某個集中存儲庫,或者用于撰寫報告或數(shù)據(jù)科學用例。
函數(shù)式編程范型在數(shù)據(jù)工程中非常常見,許多博客都寫過關(guān)于它的文章。例如,下面鏈接的文章,是Apache氣流的創(chuàng)始人馬克西姆·博切寧在2018年初發(fā)表的:
https://medium.com/@maximebeauchemin/functional-data-engineering-a-modern-paradigm-for-batch-data-processing-2327ec32c42a
同樣地,人們已經(jīng)創(chuàng)建了許多數(shù)據(jù)工程工具來改善這一過程。函數(shù)式編程可讓創(chuàng)建的代碼重用在許多數(shù)據(jù)工程任務中。
2. 設計專用型函數(shù)
要使函數(shù)可重用,編寫專用型函數(shù)是一種很好的實踐??梢栽O計主要功能,并把不同的部分連接在一起??偟膩碚f,筆者發(fā)現(xiàn)通過使函數(shù)縮小應用范圍(即專門用于某項任務),可以更快地開發(fā)代碼,因為識別和修復單個元素的錯誤更為容易。
功能范圍更小也使得交換單個組件變得更容易,可以將它們像樂高積木一樣針對不同的用例組合在一起。
3. 正確的命名規(guī)則至關(guān)重要
將對象進行命名,這樣其他人查看代碼時就可以立即理解你的意圖,這種做法非常不錯。但有些縮寫可能不是每個人都能理解,所以最好避免使用,而是選擇寫出全名。我見過的大多數(shù)數(shù)據(jù)工程師傾向于使用以下協(xié)議:
- 使用動詞作為函數(shù)名,ex. get_dataframe_from_google_ads()可能比google_ads()更容易理解——較長的版本不僅能顯示源系統(tǒng),還可指出函數(shù)執(zhí)行的操作和它返回的對象類型(數(shù)據(jù)框架)。它看起來很冗長,但通常只需要編寫兩次:一次是在定義時編寫,一次是在調(diào)用時編寫。因此,筆者認為編寫那些較長的函數(shù)名是值得的。
- 大寫全局變量——與筆者工作過的大多數(shù)數(shù)據(jù)工程師都將全局變量定義為大寫,以區(qū)別于局部變量(例如主函數(shù)中的變量)。
- 許多人認為最好只在腳本的頂部定義導入——理論上,可以在函數(shù)或類中導入庫,但是如果所有的導入都在腳本的頂部,那么跟蹤包依賴關(guān)系可能會更容易。
理想情況下,命名可以使代碼自我記錄,這也可以實現(xiàn)高效編程。
4. 簡潔而高質(zhì)量的代碼更便于維護
通常,程序員讀代碼要比寫代碼更頻繁。因此,使代碼易于閱讀和理解是非常重要的。
通過進行恰當?shù)拿徒⒘己玫慕Y(jié)構(gòu),我們可以便利個人未來的工作,其他人在使用我們代碼時也會更容易。代碼簡潔好處多多:編寫的代碼越少,需要維護的代碼就越少。如果可以用更少的代碼完成任務,這也是一種潛在的勝利。
5. 文檔是關(guān)鍵,但前提是要做得正確
這聽起來可能違反直覺,但是我們不應該記錄代碼在做什么;相反,我們應該記錄為什么代碼要做它正在做的事情,很多代碼注釋老在說明一些顯而易見的事情。
例如,get_dataframe_from_google_ads()函數(shù)不必說明我們正在從谷歌廣告下載數(shù)據(jù),而應說明這樣做的原因,例如“下載廣告支出數(shù)據(jù)以供稍后的營銷成本歸因”。使用docstring或類型注釋來記錄函數(shù)的預期輸入和輸出非常有幫助,它能立馬讓你搖身一變成為更好的數(shù)據(jù)工程師。
6. 避免硬編碼值
許多與ETL相關(guān)的SQL查詢使用閾值但沒有解釋原因。例如,假設有一個腳本從某個表中提取數(shù)據(jù),但只針對發(fā)生在2020年9月30日之后的事件,而且絕對沒有文件證明為什么有人選擇了這個特定的日期。
在不解釋原因的情況下,人們要如何才能發(fā)現(xiàn)為什么這個值是硬編碼的?這可能是因為,在那天,公司轉(zhuǎn)向了一個新的源系統(tǒng),新的數(shù)據(jù)提供商,或者他們可能改變了一些商業(yè)策略。筆者并非指在代碼中這種業(yè)務邏輯是錯誤的,但如果不記錄為什么有人選擇了這樣一個任意的閾值,這個硬編碼的值在未來幾年里可能會一直是下一代數(shù)據(jù)工程師的一個謎。
7. 避免保留僵尸代碼
筆者經(jīng)常遇到的一種常見的反模式是,有人保留了已棄用但遺留在腳本注釋中的代碼。也許有人想測試一些新的行為,保留舊版本以防新版本不能運行,或者這個人想要保存歷史記錄。筆者認為最好避免這種情況,因為它可能會使之后的開發(fā)人員很難區(qū)分哪個才是真正正確的版本。
例如,筆者曾經(jīng)歷過這樣一種情況:被注釋的代碼片段比沒有被注釋的版本更有意義。但有時情況卻往往相反,因為他或她會認為,被注釋掉這個更合乎邏輯的版本是錯誤的。因此,保留僵尸代碼可能是危險的。
8. 正確實現(xiàn)模塊化:將業(yè)務邏輯與實用程序函數(shù)分離
將實用函數(shù)和業(yè)務邏輯混合在一起具有一定意義,但是將它們分開仍然有用。如果使用得當,公共功能可以被推到不同的包中,并在以后跨多項目重用。這種分離需要更多的前期工作(例如,為這樣的包構(gòu)建一個發(fā)布過程),但是從長遠來看,可重用性和只定義一個功能的好處是值得的。
9. 簡化代碼
Python的宗旨是“簡單比復雜好。”
許多數(shù)據(jù)工程師,特別是那些有計算機科學背景的工程師,可以創(chuàng)建復雜的解決方案,卻過于繁復無法不夠簡單明了。例如,如果某些東西可以表示為一個簡單的函數(shù),該函數(shù)接受一些數(shù)據(jù)作為輸入,并返回轉(zhuǎn)換后的版本作為輸出,那么為這種操作編寫自定義類對象可能被認為是一種設計過度的解決方案。
10. 放遠眼光
有時候我們需要在正確和快速之間做出權(quán)衡。創(chuàng)建通用型解決方案,以跨不同用例重用,從長期來看,這將使代碼編寫工作更為容易,開發(fā)更長。
例如,為跨項目共享的模塊建立一個發(fā)布過程和CI/CD管道可能會在前期花費大量時間,但是這種額外的努力通常會在后期得到回報?;〞r間創(chuàng)建持續(xù)驗證和監(jiān)控數(shù)據(jù)質(zhì)量的腳本同樣如此。
圖源:unsplash
本文討論了數(shù)據(jù)工程中確保數(shù)據(jù)的高質(zhì)量和代碼的可維護性的最佳方法。大多數(shù)數(shù)據(jù)工程任務可以表示為函數(shù),這些函數(shù)接受輸入數(shù)據(jù)并根據(jù)特定的任務需求對其進行轉(zhuǎn)換。理想情況下,這些函數(shù)應該被設計為專用型,并進行文檔記錄,以便任何讀代碼的人都知道函數(shù)的輸入項和輸出項是什么。希望這篇文章能對你有幫助。