在OCaml中devise大型项目
在OCaml中编写大型软件项目的最佳实践是什么?
你如何构build你的项目?
OCaml的哪些特性不应该被用来简化代码pipe理? 例外? 一stream的模块? GADTs? 对象types?
build立系统? testing框架? 宬?
我发现了对Haskell的很好的build议 ,我认为对于OCaml有一些类似的东西是很好的。
我将在我熟悉的条件下,即在100K到1M的源代码行和10个开发人员的条件下回答一个中等规模的项目。 这是我们现在正在使用的项目,为2013年8月两个月前开始的一个项目。
构build系统和代码组织:
- 一个源代码可用的shell脚本为我们的项目定义了PATH和其他variables
- 在我们项目的根目录下的一个.ocamlinit文件在启动一个顶级会话时会加载一堆库
- omake,这是快速的(与平行构build-j选项); 但我们避免制作疯狂的定制omake插件
- 一个根Makefile包含所有必要的目标(设置,构build,testing,清理等)
- 一个级别的子目录,而不是两个
- 大多数子目录build立到一个OCaml库
- 一些子目录包含其他东西(设置,脚本等)
- OCAMLPATH包含项目的根源; 每个库子目录都会生成一个META文件,使用#require从顶层访问项目的所有OCaml部分。
- 整个项目只build立一个OCaml可执行文件(节省了大量的连接时间;仍然不知道为什么)
- 库是通过使用opam的安装脚本安装的
- 本地opam软件包是为不在官方opam存储库中的软件而制作的
- 我们使用一个opam开关,它是我们的项目命名的别名,避免与同一台机器上的其他项目发生冲突
源代码编辑:
- emacs与opam软件包ocp-indent和ocp-index
源头控制和pipe理:
- 我们使用git和github
- 所有新代码都通过github pull请求进行同行评审
- 非opam非github库的tarball存储在一个单独的git仓库中(如果历史logging变得太大,可能会被吹掉)
- github上存在的stream血边缘库被分成我们的github帐户,并通过我们自己的本地opam软件包进行安装
使用OCaml:
- OCaml不会弥补糟糕的编程习惯; 教好口味超出了这个答案的范围。 http://ocaml.org/learn/tutorials/guidelines.html是一个很好的起点。;
- OCaml 4.01.0使得它比以前更容易重用logging字段标签和变体构造函数(即
type t1 = {x:int} type t2 = {x:int;y:int} let t1_of_t2 ({x}:t2) : t1 = {x}
现在工作) - 我们尝试在我们自己的代码中不使用camlp4语法扩展
- 我们不使用类和对象,除非有一些外部库的要求
- 理论上,自OCaml 4.01.0以来,我们应该更喜欢经典变种,而不是多态变种
- 我们使用exception来指示错误,并让他们愉快地度过,直到我们的主服务器循环捕获它们,并将它们解释为“内部错误”(缺省),“错误请求”或其他内容
- 例如Exit或Not_found可以在本地使用,但是在模块接口中我们更愿意使用选项。
图书馆,协议,框架:
- 我们使用电池来处理OCaml标准库中缺失的所有商品function; 其余的我们有一个“util”库
- 我们使用Lwt进行asynchronous编程,没有语法扩展,绑定运算符(>> =)是我们使用的唯一运算符(如果您必须知道,我们不情愿使用camlp4预处理来更好地在绑定点上进行exception跟踪)。
- 我们使用HTTP和JSON与第三方软件进行通信,我们期望每个现代服务都提供这样的API
- 为了服务HTTP,我们在nginx后面运行我们自己的SCGI服务器(ocaml-scgi)
- 作为一个HTTP客户端,我们使用Cohttp
- 对于JSON序列化,我们使用atdgen
“云”服务:
- 我们使用相当多的,因为它们通常便宜,易于交互,并为我们解决可伸缩性和维护问题。
testing:
- 我们有一个用于快速testing的make / omake目标,另一个用于慢速testing
- 快速testing是unit testing; 每个模块可以提供“testing”function; 一个test.ml文件运行testing列表
- 慢testing是那些涉及运行多个服务的testing; 这些都是专门为我们的项目制作的,但是它们尽可能地覆盖了生产服务。 一切运行在Linux或MacOS本地,除了云服务,我们想办法不干扰生产。
设置这一切是相当多的工作,特别是对不熟悉OCaml的人。 目前还没有任何框架可以处理,但至less你可以select这些工具。
绿洲
要添加到Pavel答案:
免责声明:我是OASIS的作者。
OASIS也有oasis2opam,可以帮助快速创buildOPAM包和oasis2debian来创buildDebian包。 如果您想要创build一个“发布”目标,使大部分任务自动上传一个包,这是非常有用的。
OASIS还附带一个名为oasis-dist.ml的脚本,可以自动创buildtarball以供上传。
看看这一切在https://github.com/ocaml.org 。
testing
我使用OUnit来做我所有的testing。 如果您习惯于xUnittesting,这是简单而高效的。
资源控制/pipe理
免责声明:我是forge.ocamlcore.org(又名forge.oo)的拥有者/维护者,
如果你想使用git,我推荐使用github。 这对审查非常有效。
如果您使用darcs或颠覆,您可以在forge.oo上创build一个帐户
在这两种情况下,都有一个公共邮件列表,您必须发送所有提交通知,以便每个人都可以看到并检查它们。 您可以在forge.oo上使用Google群组或邮寄名单
我build议有一个很好的网页(github或forge.oo),并在每次提交时创buildOCamldoc文档。 如果您拥有庞大的代码库,这将帮助您从头开始使用OCamldoc生成的文档(并快速修复)。
当你进入一个稳定的阶段时,我build议创buildtarball。 不要只是检查出最新的git / svn版本。 这个技巧在过去节省了我几个小时的工作。 正如马丁所说的,将所有tarball存储在一个中心位置(一个git仓库是一个好主意)。
这一个可能不完全回答你的问题,但这是我关于构build环境的经验:
我真的很欣赏OASIS 。 它有一个很好的function,不仅帮助build立项目,而且还编写文档和支持testing环境。
build立系统
- OASIS从规范(
_oasis
文件)生成setup.ml
文件,该文件基本上作为构build脚本工作。 它接受-configure
,-build
,-distclean
,-distclean
标志。 我在使用不同的GNU和其他通常使用Makefiles的项目时习惯了他们,我发现可以在这里自动使用所有的Makefile。 - Makefile文件。 不用生成
setup.ml
,也可以用上面提到的所有选项生成Makefile。
结构体
通常,由OASIS构build的项目至less有三个目录: src
, _build
, scripts
和tests
。
- 在前一个目录中,所有源文件都存储在一个目录中:source(.ml)和interface(.mli)文件一起存储。 可能如果项目太大,值得引入更多的子目录。
-
_build
目录受OASIS_build
系统的影响。 它存储源文件和目标文件,我喜欢这些构build文件不会受到源文件的干扰,所以我可以很容易地删除它,以防出现问题。 - 我将多个shell脚本存储在
scripts
目录中。 其中一些用于testing执行和接口文件生成。 - 所有的testinginput和输出文件存储在一个单独的目录中。
接口/文档
接口文件(.mli)的使用对我来说既有优点也有缺点。 它确实有助于查找types错误,但是如果您拥有这些错误,则在对代码进行更改或改进时,也必须对其进行编辑。 有时忘记这会导致令人讨厌的错误。
但我喜欢接口文件的主要原因是文档。 我使用ocamldoc自动生成(OASIS支持此function与-doc
标志)的HTML页面与文档。 在我看来,在接口中编写描述每个函数的注释就足够了,而不是在代码中插入注释。 在OCaml中,函数通常是简短的,如果有必要在那里插入额外的注释,可能会更好地分割函数。
还要注意ocamlc
的-i
标志。 编译器可以自动生成一个模块的接口文件。
testing
我没有find支持testing的合理解决scheme(我想有一些ocamltest
应用程序),这就是为什么我使用自己的脚本来执行和validation用例。 幸运的是,当setup.ml
标志运行时,OASIS支持执行自定义命令。
我不会长时间使用OASIS,如果有人知道其他很酷的function,我也想知道它们。
另外,你不知道OPAM ,这绝对值得一看。 没有它,安装和pipe理新的软件包是一个噩梦。