49

在 OCaml 中编写大型软件项目的最佳实践是什么?

你如何组织你的项目?

OCaml 的哪些特性应该和不应该用于简化代码管理?例外?一流的模块?GADT?对象类型?

构建系统?测试框架?宬?

我为 haskell 找到了很好的建议,我认为为 OCaml 提供类似的东西会很好。

4

3 回答 3

63

我将在我熟悉的条件下回答一个中型项目,即源代码行数在 100K 到 1M 行之间,最多 10 个开发人员。这就是我们现在使用的,用于两个月前 2013 年 8 月开始的项目。

构建系统和代码组织:

  • 一个可提供源代码的 shell 脚本为我们的项目定义了 PATH 和其他变量
  • 启动顶级会话时,我们项目根目录的一个 .ocamlinit 文件会加载一堆库
  • omake,速度很快(使用 -j 选项进行并行构建);但我们避免制作疯狂的自定义 omake 插件
  • 一个根 Makefile 包含所有基本目标(设置、构建、测试、清理等)
  • 一级子目录,而不是二级
  • 大多数子目录构建到 OCaml 库中
  • 一些子目录包含其他内容(设置、脚本等)
  • OCAMLPATH 包含项目的根目录;每个库子目录都会生成一个 META 文件,使项目的所有 OCaml 部分都可以使用 #require 从顶层访问。
  • 整个项目只构建了一个 OCaml 可执行文件(节省了大量的链接时间;仍然不知道为什么)
  • 库是通过使用 opam 的安装脚本安装的
  • 本地 opam 包是为不在官方 opam 存储库中的软件制作的
  • 我们使用 opam 开关,它是以我们的项目命名的别名,避免与同一台机器上的其他项目发生冲突

源代码编辑:

  • 带有 opam 包 ocp-indent 和 ocp-index 的 emacs

源头控制和管理:

  • 我们使用 git 和 github
  • 所有新代码都通过 github pull requests 进行同行评审
  • 非 opam 非 github 库的 tarball 存储在单独的 git 存储库中(如果历史变得太大,可能会被吹走)
  • github 上现有的前沿库被分叉到我们的 github 帐户中,并通过我们自己的本地 opam 包安装

OCaml 的使用:

  • OCaml 不会弥补不良的编程习惯;教好品味超出了这个答案的范围。http://ocaml.org/learn/tutorials/guidelines.html是一个很好的起点。
  • OCaml 4.01.0 比以前更容易重用记录字段标签和变体构造函数(即type t1 = {x:int} type t2 = {x:int;y:int} let t1_of_t2 ({x}:t2) : t1 = {x}现在可以使用)
  • 我们尽量不在我们自己的代码中使用camlp4语法扩展
  • 除非某些外部库要求,否则我们不使用类和对象
  • 理论上,从 OCaml 4.01.0 开始,我们应该更喜欢经典变体而不是多态变体
  • 我们使用异常来指示错误并让它们愉快地通过,直到我们的主服务器循环捕获它们并将它们解释为“内部错误”(默认)、“错误请求”或其他东西
  • 在有意义的情况下,可以在本地使用 Exit 或 Not_found 等异常,但在模块接口中,我们更喜欢使用选项。

库、协议、框架:

  • 我们将电池用于 OCaml 标准库中缺少的所有商品功能;剩下的我们有一个“util”库
  • 我们使用 Lwt 进行异步编程,没有语法扩展,并且绑定运算符 (>>=) 是我们使用的唯一运算符(如果您必须知道,我们不情愿地使用 camlp4 预处理来更好地跟踪绑定点的异常)。
  • 我们使用 HTTP 和 JSON 与 3rd-party 软件进行通信,我们希望每个现代服务都提供这样的 API
  • 为了服务 HTTP,我们在 nginx 后面运行我们自己的 SCGI 服务器(ocaml-scgi)
  • 作为 HTTP 客户端,我们使用 Cohttp
  • 对于 JSON 序列化,我们使用 atdgen

“云”服务:

  • 我们使用了很多它们,因为它们通常很便宜,易于交互,并为我们解决了可扩展性和维护问题。

测试:

  • 我们有一个用于快速测试的 make/omake 目标和一个用于慢速测试的目标
  • 快速测试是单元测试;每个模块都可以提供一个“测试”功能;test.ml 文件运行测试列表
  • 慢速测试是那些涉及运行多个服务的测试;这些是专门为我们的项目制作的,但它们尽可能地涵盖了生产服务。一切都在 Linux 或 MacOS 上本地运行,除了我们想方设法不干扰生产的云服务。

设置这一切需要相当多的工作,尤其是对于不熟悉 OCaml 的人。目前还没有框架可以解决所有这些问题,但至少您可以选择工具。

于 2013-10-14T07:16:21.180 回答
10

绿洲

要添加到 Pavel 答案:

免责声明:我是 OASIS 的作者。

OASIS 也有 oasis2opam 可以帮助快速创建 OPAM 包和 oasis2debian 来创建 Debian 包。如果您想创建一个“发布”目标来自动执行大部分任务以上传包,这将非常有用。

OASIS 还附带一个名为 oasis-dist.ml 的脚本,该脚本会自动创建用于上传的 tarball。

在https://github.com/ocaml.org中查看所有这些内容。

测试

我使用OUnit进行所有测试。如果您习惯于 xUnit 测试,这将非常简单且非常有效。

源代码控制/管理

免责声明:我是 forge.ocamlcore.org(又名 forge.oo)的所有者/维护者

如果你想使用 git,我推荐使用 github。这对于审查非常有效。

如果您使用 darcs 或 subversion,您可以在 forge.oo 上创建一个帐户

在这两种情况下,必须有一个公共邮件列表,您可以在其中发送所有提交通知,以便每个人都可以看到它们并查看它们。您可以使用 Google 群组或 forge.oo 上的邮件列表

我建议每次提交时都构建一个带有 OCamldoc 文档的漂亮 Web(github 或 forge.oo)页面。如果您拥有庞大的代码库,这将帮助您从一开始就使用 OCamldoc 生成的文档(并快速修复它)。

我建议您在达到稳定阶段时创建 tarball。不要仅仅依靠查看最新的 git/svn 版本。这个技巧在过去为我节省了几个小时的工作时间。正如 Martin 所说,将所有 tarball 存储在一个中心位置(一个 git 存储库是一个好主意)。

于 2013-10-14T22:51:39.993 回答
5

这可能无法完全回答您的问题,但这是我关于构建环境的经验:

我非常感谢绿洲。它有一组很好的特性,不仅有助于构建项目,还有助于编写文档和支持测试环境。

构建系统

  • OASISsetup.ml从规范(_oasis文件)生成文件,它基本上作为构建脚本工作。它接受-configure, -build, -test,-distclean标志。在使用不同的 GNU 和其他通常使用 Makefile 的项目时,我已经习惯了它们,并且我发现可以在这里自动使用所有它们很方便。
  • 生成文件。除了setup.mlgenerate 之外,还可以使用上述所有可用选项生成 Makefile。

结构

通常我的 OASIS 构建的项目至少有 3 个目录:src_build和.scriptstests

  • 在前一个目录中,所有源文件都存储在一个目录中:源文件 (.ml) 和接口 (.mli) 文件存储在一起。可能如果项目太大,值得引入更多的子目录。
  • _build目录受OASIS 构建系统的影响。它将源文件和目标文件都存储在那里,我喜欢构建文件不受源文件的干扰,所以我可以很容易地删除它,以防出现问题。
  • scripts我在目录中存储了多个 shell 脚本。其中一些用于测试执行和接口文件生成。
  • 我将测试的所有输入和输出文件存储在单独的目录中。

接口/文档

使用接口文件 (.mli) 对我来说既有优点也有缺点。查找类型错误确实很有帮助,但如果你有它们,你在对代码进行更改或改进时也必须编辑它们。有时忘记这一点会导致严重的错误。

但我喜欢接口文件的主要原因是文档。我使用ocamldoc自动生成(OASIS 支持带有-doc标志的此功能)带有文档的 html 页面。在我看来,在界面中编写描述每个函数的注释就足够了,而不是在代码中间插入注释。在 OCaml 中,函数通常简短而简洁,如果需要在此处插入额外的注释,那么拆分函数可能会更好。

还要-i注意ocamlc. 编译器可以自动为模块生成接口文件。

测试

我没有找到支持测试的合理解决方案(我想要一些ocamltest应用程序),这就是为什么我使用自己的脚本来执行和验证用例。幸运的是,OASIS 支持setup.ml在使用-testflag 运行时执行自定义命令。

我很长时间没有使用 OASIS,如果有人知道任何其他很酷的功能,我也想知道它们。

此外,如果您不了解OPAM,它绝对值得一看。没有它,安装和管理新软件包就是一场噩梦。

于 2013-10-14T00:45:57.493 回答