Beancount 历史与致谢

Martin Blais,2014 年 7 月

http://furius.ca/beancount/doc/history

Beancount 的开发历史及贡献者致谢。

Beancount 的历史

John Wiegley 的 Ledger 是 Beancount 第一版的灵感来源。该系统中的许多原始理念都源自于此。当我首次了解复式记账法,并意识到它能完美解决我在追踪公司各项分期付款时遇到的诸多问题后,我对当时可用的解决方案(包括 GnuCash,我很容易就能让它崩溃)感到失望,随即转向了 Ledger 网站。在那里,John 阐述了他对基于文本系统的构想,特别是摒弃借方和贷方、仅用符号表示,以及一种便捷的输入语法——允许省略其中一条分录的金额。我深受启发,与他展开了多次热情的讨论,探讨 Ledger 以及我如何希望使用它。我们之间产生了思想的交融,John 对新增功能的建议也十分开放。

我对记账产生了强烈的好奇心,于是开始为 Ledger 编写 Python 接口。但最终,我发现自己完全用 Python 重写了整个系统——并非因为不喜欢 Ledger,而是因为它足够简单,我能在短时间内完成大部分工作,并立即添加一些我认为有用的特性。其中一个原因是,我不再希望每次解析输入文件后都生成一份控制台报告,而是希望只解析一次,然后通过网页请求,从内存中的交易数据库中提供各种报告。因此,我不再需要高性能,也就无需再使用 C++,转而选择了一种动态语言,这让我能快速添加许多功能。这便是 Beancount 第一版的诞生,它独立发展并演化出了一套实验性功能。

我的梦想是能够快速简便地操作这些交易对象,以获得数据的各种视图和细分。然而,我认为第一版实现并未充分突破极限;坦白说,代码质量很差——我写得非常仓促,修改系统也十分不便。特别是我最初实现资本利得追踪的方式很不优雅,需要手动计算。我对这一点不满意,但它确实能工作。此外,为了保持与 Ledger 语法的合理兼容性,我使用了丑陋的临时解析代码——我原本希望共享某种通用的输入语法,以便在两个系统间进行验证,甚至可能相互转换,这让我对修改语法以发展新功能心存顾虑,因此系统在几年内趋于稳定,我也逐渐失去了添加新功能的兴趣。

但它是正确的,而且基本能正常工作,因此我从 2008 年到 2012 年持续使用该系统来管理我的个人财务、公司财务以及与妻子的共有财产,记录了详细的交易;这非常棒。在此期间,我学到了大量记账的知识(这本食谱文档正是为了帮助你做到这一点)。2013 年夏天,我突然顿悟,找到了一种正确且可推广的方法来实现资本利得计算,如何合并成本计价头寸与普通头寸的追踪,以及一套合理操作这些头寸的简单规则(即库存工作机制的设计)。我还发现了一种更好的方式来抽象内部数据结构,于是决定摆脱与 Ledger 的兼容性约束,重新设计输入语法,以便使用 lex/yacc 生成器解析输入语言,这样我就能轻松演进输入语法,而无需处理解析问题,也更容易移植到其他语言。在此过程中,一种更简单、更一致的语法应运而生。在数个紧张的周末和大量汗水之后,我从零开始完全重写了整个系统,甚至没有参考旧版本,实现了纯重新开发。Beancount 第二版由此诞生,比前一版更好、更模块化,且易于通过插件扩展。

最终成果是我认为一种优雅的设计,它仅涉及少量对象,完全可以作为其他编程语言重新实现的基础。这一设计在随附的设计文档中有详细说明,供对此感兴趣的人参考(这将受到欢迎,我也预期这种情况会发生)。尽管 Ledger 仍然是一个充满记账问题表达创意的有趣项目,但 Beancount 第二版提出了一种更简单的设计,剔除了非必需的功能,旨在通过简洁的网页界面和极少的命令行选项实现最大可用性。由于我对第一版拥有五年以上的实际使用经验,我给自己设定了目标:移除所有我认为无用且引入了不必要的复杂性的功能(如虚拟交易、允许不属于五种账户类型之外的账户等),并在不损害可用性的前提下尽可能简化系统。最终的语言和软件变得更容易使用和理解,产生的数据结构也更加简洁,处理过程更具“函数式”特征,Beancount 的内部结构高度模块化。我通过一些非常简单的 Python 脚本处理了全部六年的输入数据,并开始 exclusively 使用新版本。目前它已处于完全可用状态。

Ledger 的语法实现了许多会触发大量隐式行为的特性;我觉得这令人困惑,其中一些原因已在众多改进提案中有所记录。我不喜欢命令行选项。相比之下,Beancount 的设计提供了一种表达力较低但更底层的语法,它更贴近生成的内存数据结构,并且在这方面更加明确。我认为这两个项目各有优劣。尽管 Ledger 很受欢迎,但在我的看法中,其最新版本仍存在若干缺陷。特别是,仓位的减少并不会在发生时立即记账,而是似乎仅累积起来,直到显示时才根据不同的命令行选项采用不同方法进行匹配。因此,在 Ledger 中,一个账户可以同时持有同一商品的正数和负数仓位。我认为这可能导致交易仓位的记账混乱,甚至出现错误会计。我相信 Ledger 的仿制社区仍在探索这一问题,最终他们将趋同于我所采用的解决方案,或者可能发现更好的方案。

在重新设计过程中,我将原本用于导入的配置指令分离出来,并经过多次迭代,最终找到了一种优雅的方式,基本实现了导入的自动化,能够自动检测各种输入文件并将其转换为我的输入语法。结果便是LedgerHub 设计文档。目前 LedgerHub 已实现,我已完全使用它,但由于测试不足,我尚未发布公开版本 [2014 年 7 月]。欢迎您自行尝试。

2014 年 6 月和 7 月,我决定将七年来关于命令行记账的思考整理成一系列 Google 文档,这些内容如今构成了 Beancount 当前文档的基础。我希望从此开始逐步完善它。

时间线

致谢

到目前为止,Beancount 的所有代码均由我本人贡献。一些用户以其他方式做出了重要贡献:

  • Daniel Clemente 在使用 Beancount 管理公司和个人财务时,报告了他遇到的所有问题。他坚持不懈的专注与细致入微的态度,帮助我聚焦于修复那些我本会回避的 Beancount 粗糙之处。

  • 经过多年的敦促,我的老朋友Filippo Tampieri终于决定将他的交易历史转换为 Beancount 格式。他对我文档提出了多项深入的审阅意见,并正在开发多种评估资产回报的方法。