在OCaml设计大型项目

时间:2013-10-13 21:38:30

标签: functional-programming ocaml

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

您如何构建项目?

应该和不应该使用OCaml的哪些功能来简化代码管理?例外?一流的模块? GADTs?对象类型?

构建系统?测试框架?图书馆堆栈?

我发现haskell很棒recommendations,我认为为OCaml提供类似的东西会很好。

3 个答案:

答案 0 :(得分:59)

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

构建系统和代码组织:

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

源代码编辑:

  • 使用opam包的emacs ocp-indent和ocp-index

来源控制和管理:

  • 我们使用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与第三方软件进行通信,我们希望每个现代服务都提供此类API
  • 用于提供HTTP,我们在nginx后面运行我们自己的SCGI服务器(ocaml-scgi)
  • 作为HTTP客户端,我们使用Cohttp
  • 用于JSON序列化我们使用atdgen

“云”服务:

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

测试:

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

设置这一切是相当多的工作,特别是对于不熟悉OCaml的人。目前还没有框架可以处理所有这些,但至少你可以选择工具。

答案 1 :(得分:10)

OASIS

添加到Pavel回答:

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

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

OASIS还附带了一个名为oasis-dist.ml的脚本,可自动创建tarball以供上传。

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

测试

我使用OUnit来完成所有测试。如果您习惯于xUnit测试,这很简单且非常有效。

来源控制/管理

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

如果你想使用git,我建议使用github。这对于审核非常有效。

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

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

我建议每次提交时都有一个很好的web(github或forge.o.o)页面,其中包含OCamldoc文档构建。如果你有一个庞大的代码库,这将帮助你从一开始就使用OCamldoc生成的文档(并快速修复)。

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

答案 2 :(得分:5)

这个可能不完全回答你的问题,但这是我对构建环境的经验:

我非常感谢OASIS。它有一套很好的功能,不仅有助于构建项目,还有助于编写文档和支持测试环境。

构建系统

  • OASIS从规范(setup.ml文件)生成_oasis文件,该文件基本上用作构建脚本。它接受-configure-build-test-distclean个标记。我在使用不同的GNU和其他通常使用Makefiles的项目时非常习惯它们,我觉得很方便可以在这里自动使用所有这些。
  • 生成文件。除了生成setup.ml之外,还可以生成包含上述所有选项的Makefile。

<强>结构

通常我的OASIS构建的项目至少有三个目录:src_buildscriptstests

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

<强>接口/文档

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

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

另请注意-i的{​​{1}}标记。编译器可以自动为模块生成接口文件。

<强>测试

我没有找到支持测试的合理解决方案(我希望有一些ocamlc应用程序),这就是我使用自己的脚本来执行和验证用例的原因。幸运的是,当ocamltestsetup.ml标志运行时,OASIS支持执行自定义命令。

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

另外,你不知道OPAM,绝对值得一看。没有它,安装和管理新包装就是一场噩梦。