请在2024年Clojure调查!中分享您的想法。

欢迎!请查看关于页面以了解此工作的更多信息。

+3
Clojure
编辑

我已经使用Clojure大约两年了,并且喜欢这门语言。在不将静态类型与动态类型之争引入这个话题的前提下,我在Clojure和Rust之间来回切换项目。在使用Clojure时,我最大的问题是函数接收和返回的关键词和数据形状的可发现性。

例如,当我有段时间没有接触到一个代码库时,再次回来查看一个函数,数据结构的结构不清楚。同样,对于映射,选择正确的关键字(例如,是:home-address还是:address-home)也必须花额外的时间在当前编写的代码之外进行搜索。我也最近尝试使用clj-fx,这是一个识别正确关键字的真实噩梦。(需要挖掘Java文档)。

在静态类型语言中,我发现能够立即看到参数和返回值的结构和类型,而无需(重新)研究函数体,这非常有用。我正在寻找如何在Clojure中解决这种程序员“问题”的最佳途径。我知道这不是一个新的问题,可以通过多种不同的方法解决

  • Specs:然而,为每个函数创建一个单独的spec def感觉有点麻烦。我也尝试过使用defn-spec和Ghostwheel/Orchestra,但似乎没有一个能干净地结合而不产生其他问题。(例如,代码检查)。
  • 表意的文档字符串:有效,但与注释脱节的问题相同。
  • 丰富的注释块。我喜欢这种方法并且已经大量使用。虽然它有点冗余,但至少它有多种用途。(文档化+通过评估探索)
  • 在另一个文件中的测试:不喜欢这种方式,因为它要求切换文件并搜索测试。
  • defn中的测试
  • Spec2的未来

我对人们在这方面的最佳实践很感兴趣。我在哪里遗漏了?显然,Clojure被分布式组织相对较大的代码库所使用。有任何展示这些实践的项目的好例子吗?

2 个答案

+1

我总是喜欢在函数中使用解构赋值,使用带修饰词的解构赋值可以使我跳转到该关键词的定义来查看它有哪些可用选项。

为了知道哪些关键词在函数中可用,我设置了一个作用域捕获,然后运行一个测试(最好是集成测试)

作用域捕获有很多方法,从库到内联定义,tap>也是一种作用域捕获。

有了捕获的作用域,我可以在repl中简单地请求(start key argm),它将打印出所有可用于该函数的关键词。

0

对我来说有两种情况,要么函数描述了其输入,调用者必须以正确的形状提供它们,要么函数利用现有的应用程序实体(数据模型),并且应该通过名称引用其定义。

在函数不操作实体的情况下,我确保有好的参数名称,以描述doc-string中的形状,并在获取map或tuple时使用解构来命名每个元素。有时我也可能有一个针对它的规范,尽管那通常只是当我想用生成测试或对其执行验证时。

在函数操作实体的情形下,我将参数名称与实体定义的名称设置为一致。

我使用构造函数定义实体,或者使用名为make-foo的函数创建实体实例,该函数将记录它创建的形状,或者从查看其代码中很容易看出。或者我用记录定义定义它们,其中记录定义描述了形状。或者我用规范来定义它们,规范名称就是实体名称。

(defn make-item
  "Makes an item which is a map of key
   :id UUID of the todo-list
   :name - the user readable string name of the item
   :content - the user provided string content of the item"
  [name content]
  {:id (java.util.UUID/randomUUID)
   :name name
   :content content})

(defn make-todo-list
  "Makes a todo-list which is a map of key
    :id - UUID of the todo-list
    :name - the user readable string name of the list
    :items - an ordered vector of item"
  [name & items]
  {:id (java.util.UUID/randomUUID)
   :name name
   :items (vec items)})

(defn insert-item
  "Inserts an item in a todo-list at given 0-based position"
  [todo-list item position]
  (apply conj
    (subvec todo-list 0 position)
    item
    (subvec todo-list position (count todo-list))))

(defn sanitize-input
  "Given an untrusted input string, return a sanitized version.
   Takes an optional options map with options:
     :extra-denylist - a seq of string words to delete on top of default sanitization."
  [input-string & {:keys [extra-denylist] :as options}]
  (some-lib/sanitize input-string extra-denylist)

所以,正如你在我简短的例子中看到的那样,《sanitize-input》是一个不操作应用程序域模型的函数的示例,它仅使用好的参数名称、解构和良好的doc-string来描述其输入。

另一方面,insert-item是一个在应用域模型上操作的功能,因此它只使用了它作为输入的域实体的同名,它的附加参数position则简单地通过一个好的名称和文档字符串进行描述。

最后,通过各自的make-函数描述域实体。我可以用记录代替它们,或者选择使用Spec。

...