请分享您的想法,参加2024年Clojure调查!

欢迎!更多关于这个平台如何工作的信息,请查看关于页面。

+3
Clojure
编辑

我使用Clojure大约两年了,非常喜欢这个语言。不将这个问题变成静态类型与动态类型之争,我在Clojure和Rust之间跳来跳去。我使用Clojure时最大的问题就是函数接收和返回的关键字和数据形状的可发现性。

例如,当我在一个项目中工作了一段时间后,我会重新回到项目中查看一个函数,这时不会立即清楚地知道接受的 数据结构。同样,对于map对象,选择合适的关键字(例如,是:home-address还是:address-home)也需要额外的时间在当前编写的代码之外进行搜索。我也最近尝试使用clj-fx,这是一个识别正确关键字的真正噩梦。(需要翻阅Java文档)。

在静态类型语言中,我发现在不重新学习函数体的前提下立即看到参数和返回值的结构/类型非常有用。我正在寻找如何在Clojure中解决这种“程序员问题”的最佳方法。我知道这不是一个新的问题,可以通过不同的方法来解决

  • Specs:尽管为每个函数创建单独的spec定义有些繁琐,但我也尝试使用了defn-spec和Ghostwheel/Orchestra,但它们似乎都没有很好地与其他问题(例如linting)整合。
  • 表意文档字符串:工作得好,但与注释脱节的 同样问题。
  • 丰富的注释块。我喜欢这种做法,并大量使用了它。虽然它有点重复,但至少它有多个用途(文档记录 + 通过评估进行探索)。
  • 在其他文件中的测试:不喜欢这种需要切换文件并搜索测试的方式。
  • defn中的测试
  • 未来的Spec2

我对人们在这个问题上发现的最佳实践很感兴趣?我在哪里遗漏了这些,因为 claro 是被分布式组织相对较大的代码库所使用的?任何展示这些实践的项目的好例子吗?

2 个回答

+1

我喜欢在函数中一直使用解构,通过使用带限定符的关键字解构,我可以直接查看该关键字的可用性。

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

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

有了捕获的作用域,我可以在repl中简单地要求给出(keys argm),它会打印出该函数的所有可用的关键字。

0

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

在函数不操作实体的情况下,我确保有良好的参数名,描述形状在文档字符串中,如果获取映射或元组则使用解构每个元素命名。有时我也可能有一个规范,但这只有在我想也使用它进行生成测试或对其进行验证时才是如此。

在函数操作实体的情况下,我将参数名与实体定义的名称相同。

我通过构造函数定义我的实体,一个名为make-foo的函数创建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是一个不操作应用程序领域模型的函数示例,所以它仅使用良好的参数名称、解构和优秀的文档字符串来描述其输入。

另一方面,insert-item是一个操作应用程序领域模型的函数,所以它仅使用其输入领域实体的相同名称,额外的参数位置由良好的名称和文档字符串描述。

最后,域实体通过各自名称的 make- 函数进行描述。我也可以使用记录或者规范(Spec)来代替。

...