6 年專業Clojure經驗分享 - Erez

解道jdon 2021-08-15 12:16:18 阅读数:224

本文一共[544]字,预计阅读时长:1分钟~
clojure 分享 erez

  • Clojure 是一種很棒的編程語言,因為它具有函數性、缺乏對象/對原始值的關注以及通過其無縫 Java 互操作提供的龐大 JVM 生態系統
  • 與其他編程語言相比,Clojure 工程師的招聘和構建工程團隊具有挑戰性,因為它不受歡迎,並且缺乏大量經驗豐富的工程師

在主要與 Ruby 合作多年後,我來到了Nanit。那時我並不真正了解 Clojure,所以在我的第一階段,我主要做 Ruby 工作以提供快速價值。從那以後 6 年多過去了,今天 Clojure 是我工具箱中最强大的工具之一,也是我覺得最高效的語言。

在這些年裏,Nanit 的後端團隊變得越來越大,關於選擇 Clojure 作為我們主要編程語言的問題一再出現,這主要是因為缺乏經驗豐富的 Clojure 工程師。

當我試圖為這個問題提供答案時,我總是覺得我必須回憶我的想法並將它們組織成連貫的論點,盡管 Clojure 的優勢一直對我來說非常清楚。我决定有一天我會在一篇博客文章中錶達我對 Clojure 的看法。這一天到了:)

我總是喜歡說,盡管我已經與 Clojure 一起工作了五年多,但我絕不是“Clojure 專家”。因為我認為自己是一個傾向於深入研究主題的人,所以我認為這更多地反映了 Clojure 作為一種語言而不是作為軟件工程師的我。只是與其他編程語言相比,Clojure 相當簡單,簡化一個主題,使專業知識變得深奧。換句話說,Clojure 允許您以很少的知識實現很多,因為要知道的並不多,這真的很棒。

不應將簡單與軟弱混淆。相反,Clojure 的簡單性是它的主要優勢,因為您可以實現使用其他語言(如 Ruby、Java 或 Python)所能實現的一切,而代碼中的開銷和意外複雜性更少。

我想盡量避免“語言戰爭”,得出一個絕對的結論,即 Clojure 是地球上最好的語言。Clojure 是我工具箱中的另一個工具,可能比其他用例更適合某些用例。相反,我將嘗試列出使我在使用 Clojure 時更輕松的客觀參數,以及一些我在使用 Clojure 作為語言在技術上遇到困難的主題,以及構建一個主要將 Clojure 作為他們的工具實踐的軟件工程師團隊。

 

函數式編程

Clojure 是一種函數式編程 (FP) 語言。對我來說,作為一個軟件開發者,FPs 最大的優勢就是大部分的代碼庫都是由“純函數”組成的。純函數有兩個特性,使它們更容易測試、重構和組合成更複雜的函數:

  1. 它們沒有副作用。副作用包括網絡 IO、磁盤交互或改變系統狀態。
  2. 他們的輸出完全依賴於他們的論點。它們不依賴於外部狀態來計算它們的返回值。

我創建軟件分為4個主要活動:

  • 我讀了現有的代碼,並試圖了解它
  • 我重構,需要重構代碼
  • 在寫新的代碼之前設計
  • 我寫新代碼的測試— 此代碼可能重複使用現有代碼

上述兩個特征的組合使我更容易列出任何列出的活動:

  1. 純函數使代碼設計更容易:事實上,當您的代碼庫主要由純函數組成時,幾乎不需要進行任何設計。您不必使用接口、擴展和實現來構建類層次結構。不需要像繼承上的組合或訪問者模式這樣的高級設計技巧。您不必為多重繼承問題或可怕的菱形圖找到創造性的解决方案。在過去的 6 年裏,我沒有處理過任何這些問題,但我編寫了精心制作、經過測試、可維護、可讀、可擴展的生產級代碼(或者至少這是我願意相信的 :))。
  2. 純函數更易於重用:我可以根據需要多次使用純函數,而無需考慮它如何影響系統,因為沒有任何副作用。這就像計算機編程的 WYSIWYG——函數遵循它的主體而不是其他任何東西。無需考慮任何隱藏的考慮因素。純函數通過消除必須調查我要重用的代碼是否會影響系統以及如果是的話會產生什麼影響的額外開銷來鼓勵代碼重用。
  3. 純函數更易於閱讀 和 理解:每個純函數都是一段孤立的、一致的和可預測的代碼,僅依賴於其參數。您不需要熟悉數據庫模式或 RabbitMQ 架構來推理代碼——這完全是關於在函數體中完成的參數和數據轉換。
  4. 純函數更容易測試:由於它們不依賴於外部狀態,因此您測試函數所需要做的就是將其應用於其參數。無需在數據庫上創建夾具或模擬 HTTP 請求。此外,由於純函數不會對系統應用任何更改,因此您只需測試返回值。
  5. 純函數更容易重構:它們缺乏外部依賴和無狀態,將它們變成了一個獨立的構建塊,易於替換和組合。

 

只有值,只有基元

Clojure 沒有“對象”。我的意思是,確實如此,但大多數時候你不會覺得需要這些。相反,Clojure 依賴於原始值和它們的集合(數組、字典、集合等)。我在 Clojure 中所做的 99% 都是使用包含原始值的數組和字典。

作為一名軟件工程師,處理原始值對我來說更容易:

  1. 我的代碼側重於業務邏輯和數據轉換,而不是描述域及其關系。每行代碼都在執行業務邏輯,因此業務邏輯在整個代碼庫中非常突出。
  2. 我不必熟悉數百個獨特的對象和編碼到它們中的行為才能有效:傳入的 HTTP 請求?它是一個普通的 Clojure 字典。你想形成一個 SQL 查詢?構建一個字典並將其傳遞給 SQL 庫進行格式化。您想返回 HTTP 響應嗎?您返回一個包含狀態碼、標題和正文鍵的字典。想要從 RabbitMQ 隊列中讀取消息?是的,你猜對了——你得到了一本字典。如果您熟悉 Clojure 對其基本數據結構(如字典)的操作,您將在 HTTP、SQL、RabbitMQ 和系統的每個其他特定領域部分中變得有效。它將域中所需的複雜性和熟悉程度降低到最低要求,因為從軟件方面來看,您所做的只是重複構建、轉換和移動字典從一個功能到另一個功能。
  3. 測試變得更加容易,因為我不必模擬複雜的對象或創建所需接口的特定於測試的實現來允許代碼運行。我所要做的就是創建數據這始終是一個Clojure的或多個圖元的集合。

 

最少的語法

Clojure 的語法建立在它自己的數據類型之外。此屬性稱為同質性。起初聽起來很奇怪,但我會嘗試證明:

Clojure 向量(其他語言中的數組)如下所示[1 2 3 4]Clojure 列錶如下所示:(1 2 3 4)

要定義一個函數,你會寫:

(defn my-sum [arg1 arg2] (+ arg1 arg2))

如您所見,代碼是一個 Clojure 列錶,其中包含符號defn、函數名稱和參數向量。主體是一個列錶,其中函數作為第一個成員 (+),後面是參數。

為什麼這是一件好事,你可能會問自己?好問題!

  1. 通過宏生成代碼感覺很自然。由於我們在 Clojure 中所做的大部分工作都是為了有利於業務邏輯而轉換和生成數據結構,因此使用相同的數據結構執行相同的操作來生成代碼幾乎不會引起注意。
  2. 它將您必須熟悉的特殊符號和字符的數量减少到最少。代碼和數據合二為一,因為它們共享相同的數據結構、行為和語法。

 

並發

使用 Clojure 時,並發感覺不是問題,主要有兩個原因:

  1. Clojure 的大部分值都是不可變的,這可以防止競爭條件並允許代碼不受互斥鎖和鎖等共享訪問控制的影響。那些不是一成不變的(例如原子)提供了操作它們存儲的數據的安全方法。
  2. Clojure 有大量用於並發編程的工具,稱為clojure.async。至少從我的經驗來看,這些工具的亮點是 Channels,它允許在一組通道上進行安全的線程間通信和選擇,就像Golang 的 select 指令一樣

 

Java互操作

Clojure 不是一種廣泛使用的編程語言,因此,常見用例缺少許多庫。幸運的是,Clojure 與 Java 的互操作是無縫的,因此在實踐中,Java 的龐大生態系統觸手可及。通過這種方式,您可以享受使用 Clojure 的樂趣,但不會受到其缺乏流行度和庫的影響。

 

缺點

是的,Clojure 很棒,但就像我們在生活中做出的大多數决定一樣,使用 Clojure 做出的决定也是權衡取舍。

Clojure 的第一個方面讓我過得很艱難是 JVM,原因有以下 3 個:

  1. JVM 是一個眾所周知的內存吞噬者,很難預測您的應用程序內存需求。此外,它似乎總是需要比運行應用程序所需的更多內存。我確信相同的應用程序在其他運行時會占用更少的內存(盡管我從未花時間證明這一點)。
  2. 調試遠程服務器中的內存泄漏和堆大小非常困難。我們嘗試了VisualVM,但由於 Clojure 內存主要由原語(字符串、整數等)組成,因此很難理解正在累積應用程序的哪些數據以及原因。我假設在基於 Java 的常見應用程序中,大部分內存由 Java 對象組成,因此內存分析會更容易。
  3. 隨著項目規模的增長,Clojure 項目的啟動時間可能會變得很長。盡管有GraalVM 之類的解决方案,我還沒有機會在生產中體驗它們以證明它們的成熟度和健壯性。

總而言之,我不是 JVM 的粉絲,但我確實理解將 Clojure 的運行時定比特到 JVM 的决定背後的原因。

在大型的、不熟悉的 Clojure 代碼庫中工作時,我發現困難的第二個主題是Typing.

Clojure 是一種動態語言,它有它的優點,但當我偶然發現一個接收字典參數的函數時,我發現自己花了很多時間來找出它擁有哪些鍵。有時我不得不在我們的集成環境中放置一個日志,以查看它接收到什麼消息以及該消息中有哪些字段可供我使用。

有時我會去測試該函數並查找我們在測試中使用的示例參數值,但這可能還不够,因為該字典中可能存在其他字段並且只是未在函數中使用時刻,因此它們也可能從測試值中丟失。有時我會查看函數的調用站點以了解傳遞了什麼參數以及它是如何構建的。

也有解决方案,例如core.typed,但我自己從未體驗過它們,我不確定它們的全面性和可用性。

使用 Clojure 的最後一件難事是招聘和入職,我已經在這篇文章的前面提到過。招聘很難,因為現有的 Clojure 工程師人數很少,而且一些工程師出於職業發展的考慮故意避免使用不受歡迎的語言。其他工程師獲得了特定語言的專業知識,並希望繼續使用這些語言,因此 Clojure 不是他們的選擇。

 

結論

我認為每個軟件工程師至少需要讓他們自己熟悉一種函數式編程語言,才能敞開心扉,看看 OOP 範式之外的東西。學習 Clojure 讓我懷疑我以前作為軟件工程師實踐過的一切,並就我如何將精力花在正確的方向上,為我工作的公司提供有價值的基礎上提出問題。

我認為 Clojure 作為一種成熟的、可用於生產的、簡單的編程語言,非常適合進行這種探索。您可以選擇專業地使用它,用於業餘項目或根本不使用它,但是讓自己接觸這種語言的經驗肯定會豐富您對編程的看法並使您成為更好的開發人員。

 

版权声明:本文为[解道jdon]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815121613067u.html