[譯] 如何在數據科學中寫出生產級別的代碼?

編寫生產級別的代碼的能力是數據科學家求之不得的技能之一 —— 不管職位要求上是否明確的要求。對於由軟件工程師轉型的數據科學家來講這可能沒什麼難度,畢竟他們也許已經在生產代碼的開發和部署上有着豐富的經驗。前端

這篇文章是針對那些剛開始編寫生產級代碼並有興趣學習它的人,好比大學的應屆畢業生或從事數據科學(和計劃轉型)的專業人員。對於他們來講,編寫生產級代碼看上去是一項艱鉅的任務。python

我會介紹幾個編寫生產級別代碼的技巧,請多加練習,此外這篇文章不須要用到任何數據科學方面的專業知識。android

1. 保持模塊化

這對於任何軟件工程師來講都是須要掌握的基本技巧。它的核心思想是把龐大的代碼塊基於其功能分割成一個個小型的獨立代碼段(函數)。它由兩部分組成。ios

(i) 將代碼拆成小塊,每一塊執行特定的功能(能夠包含子功能)。git

(ii) 將這些函數基於用途組合成模塊(或 Python 文件)。這也有助於保持代碼的有序性和可維護性。程序員

首先將龐大的代碼塊分解成許多簡單函數,每個都包含特定格式的輸入和輸出。如上所述,每一個函數應實現單一職責,如 清除數據中的離羣點、替換謬誤值、對模型進行評分、計算標準差(RMSE,又譯做均方根差) 等等。嘗試將這些函數繼續分解成執行更小單元的子任務的函數,直至沒法拆分。github

底層函數 —— 沒法再進一步分解的基本函數。好比,計算數據的標準差(RMSE)或標準分數(Z-score)。其中的某些函數能夠普遍應用於實現算法或訓練機器學習模型。面試

中間層函數 —— 使用一個或多個底層函數和/或其餘中間層函數來實現功能。舉個例子,清除數據中的離羣點 函數會使用 計算標準分數 函數來清除離羣點,只保留特定邊界內的數據;偏差 函數會使用 計算標準差 函數來獲取標準差。算法

上層函數 —— 使用一個或多箇中間層函數以及底層函數來實現功能。打個比方,模型訓練函數使用了隨機獲取標本數據函數、模型評估函數和矩陣函數等多個函數。數據庫

最後,將全部可以複用的底層和中間層函數分到一個 Python 文件中(能夠做爲模塊導入),將全部其它的專用的底層和中間層函數分到另外一個 Python 文件中。全部高級函數應該歸到同一個單獨的 Python 文件中。這個 Python 文件爲算法開發中的每一步提供指引 —— 從組合多源數據到機器學習模型的構建。

儘管無須墨守成規,但我仍是推薦你按這個流程,一步一個腳印,直至培養出本身的代碼風格。

2. 日誌和監測工具

日誌和監測工具(LI)就像飛機上的黑匣子,負責記錄駕駛艙中的一切。LI 的主要目的是記錄代碼運行時的有效信息,以便開發者在錯誤發生時調試和提高代碼性能(好比減小運行時間)。

那麼日誌和監測工具備什麼區別呢?

(i) 日誌記錄 —— 只記錄可操做的信息,如運行期間的關鍵故障和諸如代碼自己稍後將用到的中間結果之類的結構化數據。在開發和測試階段可使用多種日誌級別,如 debug、info、warn 和 error。然而,在生產過程當中須要不惜一切代價來避免這麼作。

日誌記錄應儘可能簡潔,只包含須要引發維護者注意的和須要當即處理的信息。

(ii) 監測工具 —— 記錄全部日誌中遺漏的其它信息,這將幫助咱們驗證代碼執行的步驟,並在必要時爲改進性能提供幫助。數據越多,監測工具能給的信息就越多。

驗證代碼執行步驟 —— 咱們應該記錄諸如任務名稱、中間結果、步驟通過等信息,這將有助於咱們驗證結果,並確認算法是否遵循預期的步驟。無效的結果或奇怪的執行算法可能不會引起足以被日誌記錄的嚴重錯誤。所以,記錄這些信息勢在必行。

提高性能  ——  咱們應該記錄每一個任務/子任務使用的時間和每一個變量佔用的內存。這將有助於咱們改進代碼,進行必要的更改,優化代碼以更快地運行,並限制內存消耗(或發現 Python 中常見的內存泄漏)。

監測工具記錄全部留存在日誌記錄中的其它信息,這將幫助咱們驗證代碼執行的步驟,並在必要時爲改進性能提供幫助。對此,數據越多越好。

3. 代碼優化

代碼優化包含減小時間複雜度(運行時間)和減小空間複雜度(內存佔用)兩方面。時間/空間複雜度一般表示成 O(x),其中 x 是關於時間或空間的多項式,這也被稱爲 大 O 表示法。時間和空間複雜度被用來衡量 算法效率

例如,假設咱們有一個大小爲 n 的嵌套的 for 循環,每次運行大約須要 2 秒,接着是一個簡單的 for 循環,每次運行須要 4 秒。那麼,時間消耗方程能夠寫成

時間消耗 ≈ 2n²+4n = O(n²+n) = O(n²)

當使用大 O 表示法時,咱們應該去掉常數項(由於 n 趨向於無窮時它能夠忽略不計)以及係數。係數或縮放因子之因此被忽略,是由於咱們在優化時能對其形成的影響很小。請注意,在絕對時間消耗的表達式中的係數指的是 for 環數的次數和每次運行所花費的時間的乘積,而 O(n²+n) 中的係數表明了 for 循環的數目(1 個雙層 for 循環和 1 個單層 for 循環)。一樣地,咱們能夠去掉方程中的低階項。所以,上述過程的時間複雜度爲 O(n²)。

如今,咱們的目標是用時間複雜度較低的方案替換代碼的低效部分。例如,O(n) 優於 O(n²)。代碼中最多見的時間消耗部分是 for 循環,最不常見但比 for 循環更差的是遞歸函數(時間複雜度爲 O(分支^深度))。儘可能用 Python 的模塊或函數替換儘量多的 for 循環,這些函數一般用 C 而不是 Python ,並進行過深度優化,以實現較短的運行時間。

強烈推薦你閱讀 Gayle McDowell 的程序員面試金典一書中的「大 O 算法」章節。事實上,讀完整本書可以提高你的編碼技巧。

4. 單元測試

單元測試 —— 根據功能實現代碼測試自動化

在進入生產環境以前,你的代碼必須經過多個測試和調試階段。這一般分爲三個層次 —— 開發、預發和生產。在一些公司中,部署到生產環境以前有一個部署到真實生產系統模擬環境的階段。當代碼部署到生產環境時,它應該沒有任何明顯的問題,而且應該可以處理潛在的異常。

爲了可以發現可能出現的各類各樣的問題,咱們須要對不一樣的場景、不一樣的數據集、不一樣的邊界狀況等進行測試。當咱們對代碼作了重大更改時,每次須要手動執行測試代碼的效率是很低的。所以選擇包含一組測試用例的單元測試,而且只要咱們想要測試代碼就能夠執行它。

咱們必須添加具備預期結果的不一樣測試用例來測試咱們的代碼。單元測試模塊逐個遍歷測試用例,並將代碼的輸出與指望值進行比較。若是未達到預期結果,則測試失敗 —— 這預示着若是部署到生產環境中,你的代碼可能會報錯。咱們須要調試代碼而後重複該過程,直到全部測試用例都能經過。

人生苦短,所以 Python 有一個名爲 unittest 的模塊來實現單元測試。

5. 兼容性

實際生產中頗有可能的是,你的代碼並非獨立的函數或模塊。它將被集成到公司的代碼生態系統中,你的代碼必須與生態系統的其餘部分同步運行,而不會出現任何缺陷/故障。

例如,假設你已經開發了一種推薦算法。整個流程一般包括從數據庫獲取最新數據、更新/生成推薦和將其存儲在數據庫中,該數據庫將被前端框架(如網頁,經過 API)讀取,來向用戶顯示推薦項目。這很簡單!這個過程就像一根鏈條,新的連接應該與前一個和後一個連接閉合,不然推薦過程就會失敗。一樣地,每一個流程都必須按預期運行。

每一個流程都有明確的輸入和輸出要求、預期的響應時間等等。當其餘模塊請求更新推薦(來自網頁)時,你的代碼應該在可接受的時間內以所需格式返回預期值。若是結果是不符合預期的值(在購買電子產品時推薦購買牛奶)、不但願的格式(推薦以文本而不是圖片的格式展現)或是不可接受的時間(時至今日,沒有人願意等待幾分鐘來得到推薦)—— 這暗示代碼與系統不一樣步。

要避免這種狀況,最佳的方法是在開始開發以前與相關團隊討論需求。若是行不通,請查看代碼文檔(極可能會在那裏找到大量信息),或在必要時本身編寫代碼文檔來理解需求。

6. 版本控制

Git —— 一個堪稱近年來源代碼管理中的最佳發明之一的版本控制系統,它會跟蹤計算機上代碼的更改。跟許多現存的版本控制/跟蹤系統相比,Git 是使用最爲普遍的。

這個過程簡單地說就是「修改和提交」。我可能講得過於輕描淡寫了。這個過程有不少步驟,好比爲開發建立分支、在本地提交更改、從遠程拉取文件、將文件推送到遠程分支,以及更多功能留待你深刻探索。

每次咱們對代碼進行更改,咱們不須要用不一樣的名稱保存文件,而是提交更改 —— 這意味着用新的更改覆寫舊文件,併爲此次提交賦予一個提交 ID。每當咱們對代碼進行更改時,咱們一般會添加提交註釋。假如,你不喜歡上次提交中所作的更改,並但願恢復到之前的版本,經過提交 ID 能夠輕鬆作到。Git 對於代碼開發和維護來講很是有用。

你可能已經理解了版本控制對於生產系統的重要性,以及學習 Git 的必要性。爲了預防新版本出現意外錯誤的狀況,咱們須要隨時可以回到穩定的舊版本。

7. 可讀性

你編寫的代碼一樣也應該易於他人理解,至少對於你的團隊成員而言。此外,若是不遵循正確的命名約定,即便你本身在編寫代碼的幾個月後理解本身的代碼也是頗有難度的。

(i) 合適的變量名和函數名

變量和函數名稱應該是自解釋的。當有人閱讀你的代碼時,應該很容易理解每一個變量包含的內容以及每一個函數的做用,至少在某種程度上如此。

給函數或變量賦一個長名稱是徹底能夠接受的,這個名稱要可以明確說明其功能/角色,而不像 x、y、z 等短無心義的名稱。而且變量名稱儘可能不要超過 30 個字符,函數名稱儘可能不要超過 50-60 個字符。

之前,基於 IBM 標準的代碼寬度爲 80 個字符,這已經徹底過期了。如今,根據 GitHub 標準大約是 120 個字符。取頁面寬度的 1/4,咱們獲得 30 這個足夠長可是又不會填滿頁面的變量名稱長度。函數名稱能夠稍長一些,但一樣不該該填充整個頁面。所以,取頁面寬度的 1/2,咱們獲得 60。

例如,樣本數據中亞洲男性平均年齡的變量能夠寫成 mean_age_men_Asia 而不是 agex 。相似的規則也適用於函數名稱。

(ii) 文檔字符串和註釋

除了合適的變量和函數名稱以外,必須在必要時提供註釋,以幫助讀者理解代碼。

文檔字符串  ——  適用於函數/類/模塊。函數定義中的前幾行文字描述了函數的做用及其輸入和輸出。這段文字須要用 3 個雙引號包裹起來。

def <function_name>:

"""<docstring>"""

return <output>
複製代碼

註釋 —— 能夠放在代碼中的任何位置,以告知讀者特定行或代碼段的做用。若是咱們給變量和函數賦予合適的名稱,註釋的需求將大大減小 —— 大部分代碼都能自我解釋。

代碼審查:

雖然這不是編寫符合生產質量的代碼的直接步驟,可是同行的代碼審查將有助於提升您的編碼技巧。

沒有人能寫出完美的代碼,除非那人有超過 10 年的經驗。代碼總有改進的餘地。我見過有多年經驗的專業人士寫出了糟糕的代碼,也見過正在攻讀學士學位的菜鳥擁有出色的編碼技巧 —— 你總能找到比你更優秀的人。這一切都取決於投入多少時間學習和練習,最重要的是熟能生巧。

我知道比你更優秀的人老是存在但你的團隊中不必定有。也許你是團隊中最厲害的。在這種狀況下,讓團隊中的其餘人測試你的代碼並提供反饋依然可行。儘管他們並不像你那麼出色,但他們能發現一些被你忽略的東西。

當你處於職業生涯的早期階段時,代碼審查尤其重要。它會大大提升你的編碼技巧。遵循如下步驟,來成功檢查你的代碼。

(i) 完成全部開發、測試和調試的代碼編寫。確保不要犯任何低級的錯誤。而後請你的夥伴幫忙進行代碼審查。

(ii) 把你的代碼連接轉發給他們。一個接一個發給他們,而不要讓他們一次性審閱多個腳本。他們爲第一個腳本提供的意見也可能適用於其餘腳本。在發送第二個腳本以供審閱以前,請確保在其餘腳本上應用這些更改(若是適用)。

(iii) 給他們一兩個星期來閱讀和測試每次迭代的代碼。同時還需提供測試代碼所需的全部信息,如樣本輸入、限制條件等。

(iv) 與他們每一個人面談並聽取他們的建議。請記住,你沒必要在代碼中採納全部建議,自行選擇你認爲能夠改進你的代碼的建議。

(v) 一直重複,直到你和你的團隊滿意爲止。嘗試在前幾回迭代中修復或改進您的代碼(最多 3-4 次),不然可能會留下編碼能力不足的壞印象。

但願這篇文章能對你有所幫助。

期待您的反饋。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索