Phone:
(701)814-6992

Physical address:
​6296 Donnelly Plaza
Ratkeville, ​Bahamas.

Beancount复式记账

Beancount 复式记账

一 、为什么

我在在 Google 的这四年(三)这篇文章中提到了一个「秘密武器」Beancount 以及「复式记账」的概念。我说过我要专门写一篇文章来分享我的经验,这篇文章就是我承诺的兑现了。

如何实现财务自由

这个章节标题有点吸引人眼球,像是成功学教程。但是我保证我下面说的内容都是严肃,而且有明确定义和方法的。

为什么要记账,我来用一句话总结就是为了提升对自我的认识。记账是个人理财的基础,更是通往财务自由的必经之路。财务自由这件事情固然是根本需要赚钱来实现,但并不是只有一夜暴富才能实现的。财务自由其实并不是一件虚无缥缈的事情,而是每个人都可以努力达到的一种状态,换句话说就是「退休」,只不是有人能在三十多岁退休,有人要六十岁,有人则要到八十岁。

财务自由的一般定义是由资产产生的收入不少于生活开销。如果不知道自己有多少开销,甚至不知道自己有多少资产、收入,即便是一夜暴富,财务自由是一件不可能的事情。

接下来我来解释为什么财务自由是可以实现的,而且应该是每个人的目标。首先需要强调的概念是资产(Assets)净资产(Net Assets)的区别,虽然我之后还会详细解释,但是这里需要记住,财务自由并非对个人净资产的要求,这也就是为什么不需要暴富就能实现。像所有投资都有期限一样,个人的寿命也是有期限的。每当我想到我总有一天会死,都会觉得有些忧伤,但这是无法改变的自然规律。只要在预期的寿命之内资产(而不是净资产)产生现金流能够满足生活所需的开销,这就是财务自由。我对资产产生现金流的定义较为宽泛,不仅包括利息、分红、租金、版税这类收入,还包括了直接通过资产折现的收入,即净资产减少。

要想达到财务自由,需要三点要求:对支出的预期、对资产和收入的了解、对寿命的期望。这三点都是说起来容易,做起来难的,但是记账可以帮助你很大程度上解决至少前两个问题。至于第三个问题,才是财务自由的根本困难所在,不过这个问题的解决方案已经超出了本文探讨的内容了。

为什么要复式记账

记账这件事是那种容易让人因为一时冲动开始,但是很快就放弃的事情。每个记过账人都有不同的原因和契机,但能坚持下来的则凤毛麟角。记账的好处是提升对自己的了解,解决那种不知道自己赚的钱都到哪里去了的问题。但记账的困难也显而易见,那就是麻烦,还容易遗漏、错误。一般来说普通人对记账的理解就是每笔消费都干什么了,所以就是每一笔消费都有一个金额和类别就可以了。这种记账方式一开始简单,但能带来的价值有限,只是开支记录而已,长期看来难以说服自己为了这些价值而忍受麻烦的记账过程。

相比普通的流水账,复式记账的核心理念是账户之间的进出关系,要求所有的记录全部入账,它可以保证账目的完整性和一致性。复式记账可以提供除了开支记录之外的损益表、资产负债表、现金流量表、试算平衡表等报表。复式记账还可以把投资和消费轻易区分,譬如购入电脑、手机,可以作为资产项目入账并定期折旧。同理对各种代金券、点数积分的购入一样要算入资产而不是消费。

虽然有各种各样的手机应用号称可以简化记账,它们把用户交互做得更加简单友善,有的还可以从银行账户、信用卡公司直接抓取数据,降低心理障碍,但与此同时它们却带来了另一个问题,就是数据所有权、安全性和持久性的疑虑。这些工具大多都是把数据存储在云端的,泄漏隐私的可能性不言而喻。更麻烦的是,它们几乎都是自己的专有格式,无法导出保存,或者哪怕可以导出也难以使用。这也意味着你不得不一直用一个产品,直到有一天倒闭或者服务关闭为止。这对我来说是不可接受的,因为在互联网时代能活过十年的产品非常稀少,无论是像 Google 这样随意关闭服务的大公司,还是随时有可能倒闭的小公司。复式记账的价值在于数据的完整性,我对其数据寿命的要求是二十年以上。

复式记账是一种划时代的发明,这个发明被认为是源于中世纪的地中海城邦(意大利或者埃及犹太人)。复式记账技术成为中世纪至大航海时代复杂贸易的支柱性工具,使得复杂的合约、信贷成为可能。后世会计学、金融学的许多概念都来自于复式记账,可以说体系化的资本主义是从复式记账的实践中诞生的。

如何复式记账

网上对于复式记账的文章也是汗牛充栋了,但各种晦涩难懂的名词可能会吓跑人。我不打算在这里介绍什么是复式记账,因为很难一句话说清楚,说得复杂了又没有意义。其实你并不需要懂很多才能开始复式记账,只需要一些最基本的概念就可以了。这个概念就是会计恒等式。

会计恒等式

作为会计学的核心,复式记账是一种实践中诞生的技术。除了最基本的会计恒等式之外,复式记账只有规范,没有对错。那么什么是会计恒等式呢?最基本的形式就是:

资产 = 负债 + 权益(净资产)

这个等式对于没有接触过会计学的人来说可能不太容易理解,但是记账实践过马上会明白的。理解的难点在于,会计学上的「资产(Assets)」和一般人对一个人富有程度的理解不太一样,因为资产是负债和权益的总和,负债(Liabilities)也是资产的一部分。

更容易理解的部分其实是权益(Equity),在个人理财的上下文中,又和净资产(Net Assets)是等价的,也就是经常说的「个人净值」。净资产需要由总资产排除负债,一个人到底是否富有,看的是净资产,而不是资产

举例说明,一个人首付 20 万美元,贷款 80 万美元,买了价格为 100 万美元的房产。假设这个人没有别的资产和负债,那么他的资产就是 100 万美元,负债是 80 万美元,而净资产是 20 万美元。在房价没有变化的情况下,买房对一个人的净资产没有什么影响(忽略手续费的话),而他的资产和负债都暴增,也就是所谓的「扩大资产负债表」。

如果你理解了会计恒等式,那么复式记账的一切知识障碍已经扫除了。

使用 Beancount

再说一遍,复式记账在于实践,在开始实践之前学很多艰深的会计学概念没有任何意义。要开始实践,就要有工具上手了。在众多工具中我推荐使用 Beancount,原因如下:

  1. Beancount 是一个开源工具,用 Python 实现的,可以本地运行。
  2. 账本是一套基于文本的语法,方便存储和管理,个人拥有全部的数据,还可以使用 Git 管理。
  3. 账本的语法很规范,也具备灵活度,像编程语言一样可以嵌套引入,也有语法高亮和代码检查工具。
  4. 有完整的命令行工具链和可视化工具 fava,还有基于 SQL 的查询和报表生成。
  5. 没有预先定义的类别、货币等现实世界概念,可以轻松实现多币种记账,包括各种点数、虚拟货币。

除了 Beancount,Plain Text Accounting网站还列出了其他的开源工具比较,类似的具有竞争力的开源工具还有 Ledger 和 hledger,其中 Ledger 是这类工具的开创者。无论使用里面介绍的哪个工具,其基本理念都差不多,即记录账户之间的资金流动。最重要的是,你是账本的所有者,如果不喜欢一个工具了,可以轻易转换到另一个工具。虽然它们语法有些许区别,但写一个脚本来转换不难。

使用 Beancount 之前首先需要 Python 3 运行环境。Beancount 可以轻易从 PyPI 获得,使用以下命令:

pip install beancount fava

上面的命令其中beancount是核心包,包括了命令行工具,fava是网页可视化工具。这里是一个示例账本:链接。示例文件可以在 Beancount 的Bitbucket 上下载

下载之后可以在命令行中运行:

fava example.beancount

即可看到:

Running Fava on http://localhost:5000

Beancount

至此为止 Beancount 环境就已经设置好了。

账户类别

复式记账的最基本的特点就是以账户为核心,Beancount 的系统整体上就是围绕账户来实现的。之前提到的会计恒等式中有资产、负债和权益三大部分,现在我们再增加两个类别,分别是收入和支出。Beancount 系统中预定义了五个分类:

  • Assets 资产
  • Liabilities 负债
  • Equity 权益(净资产)
  • Expenses 支出
  • Income 收入

这五类是 Beancount 的约定,除此了 Equity 之下一些特殊的账户外,没有任何预先定义的账户。用户可以定义各种各样的账户,Beancount 对账户的组织是树形的,譬如我分别有这些资产账户:

Assets:Cash:JPY
Assets:Cash:USD
Assets:Bank:CH:UBS
Assets:Bank:CN:BoC
Assets:Bank:US:Chase:Checking
Assets:Bank:US:Chase:Saving
Assets:Bank:JP:SMBC:JPY
Assets:Bank:JP:SMBC:USD
Assets:Broker:US:IB
Assets:Points:Airline:JAL
Assets:Points:Airline:United

Beancount 对这些账户的组织形式如下图:

账户

接下来是声明账户的语法。Beancount 要求每个使用的账户必须声明开户时间,格式是YYYY-mm-dd。之后是关键词open,表示在这个日期开户(或者开始记账)。接下来是账户名称,格式用:隔开的树形语法,最后是(可以省略的)账户的货币种类。货币种类不需要事先定义,也没有系统内部的定义,一般来说我们使用三字母的货币代码,但其实可以用任何的名字(惟一的限制是大写字母和下划线)。我在这个例子中使用了USDJPYCNYCHF四个货币,以及我自定义的P_JALP_UA表示不同航空公司的里程。

2019-01-01 open Assets:Cash:JPY JPY
2019-01-01 open Assets:Cash:USD USD
2019-01-01 open Assets:Bank:CH:UBS CHF
2019-01-01 open Assets:Bank:CN:BoC CNY
2019-01-01 open Assets:Bank:US:Chase:Checking USD
2019-01-01 open Assets:Bank:US:Chase:Saving USD
2019-01-01 open Assets:Bank:JP:SMBC:JPY JPY
2019-01-01 open Assets:Bank:JP:SMBC:USD USD
2019-01-01 open Assets:Broker:US:IB USD, JPY, CHF
2019-01-01 open Assets:Points:Airline:JAL P_JAL
2019-01-01 open Assets:Points:Airline:United P_UA

账户如何组织分类完全看个人需求和喜好,譬如我先分账户类型,再分国家,然后是金融机构名,最后是具体账户。分类的作用是可视化的时候,可以看某个非字节点下面所有账户的汇总。

接下来是负债类别的账户,最常见的就是信用卡,组织方式和使用方法和资产账户没有什么区别,譬如:

2019-01-01 open Liabilities:CreditCard:US:Discover USD
2019-01-01 open Liabilities:CreditCard:JP:Rakuten JPY

在开始记账之前,还差最后一步,就是收入和支出类别的定义。Beancount 把收入支出的类别也想象成了一个账户,在语法上和资产、负债类账户没有区别。譬如下面例子:

2019-01-01 open Expenses:Clothing
2019-01-01 open Expenses:Food:Dinner
2019-01-01 open Expenses:Transport:Airline
2019-01-01 open Expenses:Transport:Railway
2019-01-01 open Income:Salary
2019-01-01 open Income:Rebate

基本借贷记账

有了以上定义的账户以后,我们终于可以开始实践记账了。复式记账又叫作「借贷记账」。之所以这么叫,是因为每一条记录都至少有一条借记(Debit)和一条贷记(Credit)。可以看下面这个例子:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD
  Liabilities:CreditCard:US:Discover -1000 USD

这个例子表示我在日本航空购买了纽约-东京的机票,消费 1000 美元(贷记),付款的信用卡 Discover 扣款 1000 美元(借记)。

Beancount 基本的语法如下所示:

YYYY-mm-dd * ["payee"] "description"
  posting 1
  posting 2
  ...

第一行要有日期,接下来是*。收款者payee是可选的,如果*后面只有一个字符串,那就是省略了payee。从第二行开始,每一行开头空两个缩进,然后是账户名以及金额、货币。这里有一个要点:要保证所有条目的总和是 0,否则就会出现Transaction does not balance: (xxx USD)这样的错误。这个要求很好理解,因为花出去的钱必须和账户上减少的钱一样,否则就是所谓的「账目不平」了。

Beancount 语法的灵活性在于每个记账单元可以有任意多个条目(借记和贷记),只要保证它们的总和是 0 就可以。于是我们还可以这样记录:

2019-01-01 * "Walmart" "在超市买两件衣服和晚餐"
  Expenses:Clothing 20 USD
  Expenses:Clothing 10 USD
  Expenses:Food:Dinner 10 USD
  Liabilities:CreditCard:US:Discover -40 USD

更加复杂的例子可能是这样的:

2016-01-01 * "Google" "工资"
  Assets:Bank:US:Chase:Checking 500 USD
  Assets:Bank:US:Chase:Saving 1839.35 USD
  Assets:Pension:US:401k:PreTax 419.23 USD
  Assets:Pension:US:401k:PreTax 209.62 USD
  Expenses:Health:Insurance:Dental 3.14 USD
  Expenses:Finance:Insurance:TermLife 7.67 USD
  Expenses:Health:Insurance:Vision 0.98 USD
  Expenses:Tax:US:Federal 763.26 USD
  Expenses:Tax:US:Medicare 60.84 USD
  Expenses:Tax:US:SocialSecurity 260.15 USD
  Expenses:Tax:US:State:NY 212.59 USD
  Expenses:Tax:US:City:NYC 131.57 USD
  Expenses:Tax:US:State:NYDisability 1.2 USD
  Income:Salary:Regular -4192.31 USD
  Income:Allowance:TermLife -7.67 USD
  Income:Salary:401kMatch -209.62 USD

实际的记账中,一进一出的两个账户占了绝大多数。这个时候把正负的金额写两遍未免有点罗嗦了,所以 Beancount 还提供了金额插值的功能。简单说就是假设总和一定是 0,在有 N 个账户的时候,只要求 N-1 个账户声明金额。于是最初的例子还可以写成:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD
  Liabilities:CreditCard:US:Discover

在我的实践中,我只在正好是两个账户的时候才使用这个功能,因为可以避免重复的数字。但有多个账户的时候,把每个金额都写出来有助于避免错误,和「防御性编程」的理念一样。

在上面的例子中,我们还可以看出来,所有Expenses类别的账户都是正数,所有Income类别的账户都是负数。这是 Beancount 及类似工具使用负数来表示借记(Debit),正数表示贷记(Credit)的结果。简单来说,借记就是把账户上的资金移除,贷记就是增加账户的资金。同理,通常Assets类是正数,Liabilities类是负数。本质上每个账户的数值只有绝对值有意义,正负号并没有实际含义。

货币转换

如果在去国外旅游,免不了要进行货币转换。事实上 Beancount 本身没有定义任何货币,这也意味着你可以定义任何货币(或商品)。所以哪怕不出国,只要是记录了非主要货币类资产,譬如投资品,代金券,航空公司里程,那么就需要货币转换了。

在使用多个货币之前,需要先定义「工作货币」。工作货币可以不止一个,例如:

option "operating_currency" "JPY"
option "operating_currency" "USD"

定义了工作货币以后,在 fava 界面中可以看到工作货币单独列出的栏目。

Beancount 货币转换的语法有两种,一种是使用@记录单位货币的转换价格,例如:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD @ 110 JPY
  Liabilities:CreditCard:JP:Rakuten -110000 JPY

另一种方式我更常用,使用@@记录转换后的总额:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD @@ 110000 JPY
  Liabilities:CreditCard:JP:Rakuten -110000 JPY

货币转换不一定只在一个账户上。下面的这个例子是以 2.5 日圆每点的价格,买了 10000 日本航空里程,但是付款的信用卡是以美元计价的,所以两遍都可以转换为 25000 日圆来平衡。

2019-01-01 * "日本航空" "购买里程"
  Assets:Points:Airline:JAL 10000 P_JAL @ 2.5 JPY
  Liabilities:CreditCard:US:Discover -220.0 USD @@ 25000 JPY

借贷管理

复式记账的强大之处是每个账户都有状态,而且每个操作都是原子的,这对复杂的资金进出记录非常有帮助。

生活中一个常见的例子是朋友之间的借钱和相互垫付,就拿我最近遇到一个例子来说吧,我和 X、Y 三人一起出游,从东京附近的横须贺坐船到猿岛,费用是每人 1300 日圆的船票和 200 日圆的登岛费,其中船票可以用信用卡支付,而登岛费只能付现金。我们一共需要付 4500 日圆,但是正好谁都没有这么多现金,于是决定我用信用卡付三人的船票,X 用现金付三人的登岛费,最后再结算。

2019-05-25 * "猿岛" "渡轮"
  Expenses:Transport:Ferry 1300 JPY ; 个人渡轮费用
  Assets:Receivables:X 1300 JPY ; 对X应收账款
  Assets:Receivables:Y 1300 JPY ; 对Y应收账款
  Liabilities:CreditCard:JP:Rakuten -3900 JPY

2019-05-25 * "猿岛" "登岛费"
  Expenses:Transport:Attraction 200 JPY ; 登岛费
  Liabilities:Payable:X -200 JPY ; 欠X的钱

第二天,三人结算完毕,X 付给我现金,Y 转账给我。

2019-05-26 * "猿岛" "费用结算"
  Assets:Receivables:X -1300 JPY ; X偿还债务
  Liabilities:Payable:X 200 JPY  ; 偿还对X的债务
  Assets:Cash:JPY 1100 JPY ; X实际付给我的钱到账
  Assets:Receivables:Y -1300 JPY ; Y偿还债务
  Assets:Bank:JP:SMBC:JPY 1300 JPY ; Y实际付给我的钱到账

最终可以看出来,我在Expenses:Transport:Ferry类别消费1300 JPY,在Expenses:Transport:Attraction类别消费200 JPY,信用卡扣款3900 JPY,收到了1100 JPY的现金,Assets:Bank:JP:SMBC:JPY收到了1300 JPY的转账。

以上这种记账方法可以让资金的流动一目了然,类别和金额也准确无误。记录对他人的债权(应收账款)和欠他人的债务(应付账款),我分别使用了Assets:ReceivablesLiabilities:Payable下面的账户。

应收账款和应付账款的另一个用途是区分付款和到货时间。一般来说交易是当场进行的,一个账户的借记和另一个账户的贷记同时发生,但是有些时候付款和到货并不是同时发生的,如果需要精确区分发生的时间的话,可以用这种方法把他们分成两笔记录。

下面这个例子是,用信用卡买日本航空里程,但是不知道为什么过了 1 个月才到账:

2019-01-01 * "日本航空" "购买里程"
  Assets:Receivables:JAL 10000 P_JAL @@ 25000 JPY
  Liabilities:CreditCard:US:Discover -220.0 USD @@ 25000 JPY

2019-02-01 * "日本航空" "里程到账"
  Assets:Points:Airline:JAL 10000 P_JAL
  Assets:Receivables:JAL

这篇文章到此为止,Beancount 最基本的用法已经介绍完毕了。用这些简单的语法,我们已经可以满足大部分的记账需求了,但 Beancount 的强大之处远远不止于此。

下一篇我会继续介绍 Beancount 的更多用法,包括如何高效对账和资产折旧。同时欢迎加入 Beancount 中文讨论:t.me/beancount_zh

二 、借贷记账法

上一篇文章介绍了为什么要复式记账,以及 Beancount 的基本特点。这篇言归正转,直接从实践开始。

账户类别

复式记账的最基本的特点就是以账户为核心,Beancount 的系统整体上就是围绕账户来实现的。之前提到的会计恒等式中有资产、负债和权益三大部分,现在我们再增加两个类别,分别是收入和支出。Beancount 系统中预定义了五个分类:

  • Assets 资产
  • Liabilities 负债
  • Equity 权益(净资产)
  • Expenses 支出
  • Income 收入

这五类是 Beancount 的约定,除此了 Equity 之下一些特殊的账户外,没有任何预先定义的账户。用户可以定义各种各样的账户,Beancount 对账户的组织是树形的,譬如我分别有这些资产账户:

Assets:Cash:JPY
Assets:Cash:USD
Assets:Bank:CH:UBS
Assets:Bank:CN:BoC
Assets:Bank:US:Chase:Checking
Assets:Bank:US:Chase:Saving
Assets:Bank:JP:SMBC:JPY
Assets:Bank:JP:SMBC:USD
Assets:Broker:US:IB
Assets:Points:Airline:JAL
Assets:Points:Airline:United

Beancount 对这些账户的组织形式如下图:

账户

接下来是声明账户的语法。Beancount 要求每个使用的账户必须声明开户时间,格式是YYYY-mm-dd。之后是关键词open,表示在这个日期开户(或者开始记账)。接下来是账户名称,格式用:隔开的树形语法,最后是(可以省略的)账户的货币种类。货币种类不需要事先定义,也没有系统内部的定义,一般来说我们使用三字母的货币代码,但其实可以用任何的名字(惟一的限制是大写字母和下划线)。我在这个例子中使用了USDJPYCNYCHF四个货币,以及我自定义的P_JALP_UA表示不同航空公司的里程。

2019-01-01 open Assets:Cash:JPY JPY
2019-01-01 open Assets:Cash:USD USD
2019-01-01 open Assets:Bank:CH:UBS CHF
2019-01-01 open Assets:Bank:CN:BoC CNY
2019-01-01 open Assets:Bank:US:Chase:Checking USD
2019-01-01 open Assets:Bank:US:Chase:Saving USD
2019-01-01 open Assets:Bank:JP:SMBC:JPY JPY
2019-01-01 open Assets:Bank:JP:SMBC:USD USD
2019-01-01 open Assets:Broker:US:IB USD, JPY, CHF
2019-01-01 open Assets:Points:Airline:JAL P_JAL
2019-01-01 open Assets:Points:Airline:United P_UA

账户如何组织分类完全看个人需求和喜好,譬如我先分账户类型,再分国家,然后是金融机构名,最后是具体账户。分类的作用是可视化的时候,可以看某个非字节点下面所有账户的汇总。

接下来是负债类别的账户,最常见的就是信用卡,组织方式和使用方法和资产账户没有什么区别,譬如:

2019-01-01 open Liabilities:CreditCard:US:Discover USD
2019-01-01 open Liabilities:CreditCard:JP:Rakuten JPY

在开始记账之前,还差最后一步,就是收入和支出类别的定义。Beancount 把收入支出的类别也想象成了一个账户,在语法上和资产、负债类账户没有区别。譬如下面例子:

2019-01-01 open Expenses:Clothing
2019-01-01 open Expenses:Food:Dinner
2019-01-01 open Expenses:Transport:Airline
2019-01-01 open Expenses:Transport:Railway
2019-01-01 open Income:Salary
2019-01-01 open Income:Rebate

基本借贷记账

有了以上定义的账户以后,我们终于可以开始实践记账了。复式记账又叫作「借贷记账」。之所以这么叫,是因为每一条记录都至少有一条借记(Debit)和一条贷记(Credit)。可以看下面这个例子:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD
  Liabilities:CreditCard:US:Discover -1000 USD

这个例子表示我在日本航空购买了纽约-东京的机票,消费 1000 美元(贷记),付款的信用卡 Discover 扣款 1000 美元(借记)。

Beancount 基本的语法如下所示:

YYYY-mm-dd * ["payee"] "description"
  posting 1
  posting 2
  ...

第一行要有日期,接下来是*。收款者payee是可选的,如果*后面只有一个字符串,那就是省略了payee。从第二行开始,每一行开头空两个缩进,然后是账户名以及金额、货币。这里有一个要点:要保证所有条目的总和是 0,否则就会出现Transaction does not balance: (xxx USD)这样的错误。这个要求很好理解,因为花出去的钱必须和账户上减少的钱一样,否则就是所谓的「账目不平」了。

Beancount 语法的灵活性在于每个记账单元可以有任意多个条目(借记和贷记),只要保证它们的总和是 0 就可以。于是我们还可以这样记录:

2019-01-01 * "Walmart" "在超市买两件衣服和晚餐"
  Expenses:Clothing 20 USD
  Expenses:Clothing 10 USD
  Expenses:Food:Dinner 10 USD
  Liabilities:CreditCard:US:Discover -40 USD

更加复杂的例子可能是这样的:

2016-01-01 * "Google" "工资"
  Assets:Bank:US:Chase:Checking 500 USD
  Assets:Bank:US:Chase:Saving 1839.35 USD
  Assets:Pension:US:401k:PreTax 419.23 USD
  Assets:Pension:US:401k:PreTax 209.62 USD
  Expenses:Health:Insurance:Dental 3.14 USD
  Expenses:Finance:Insurance:TermLife 7.67 USD
  Expenses:Health:Insurance:Vision 0.98 USD
  Expenses:Tax:US:Federal 763.26 USD
  Expenses:Tax:US:Medicare 60.84 USD
  Expenses:Tax:US:SocialSecurity 260.15 USD
  Expenses:Tax:US:State:NY 212.59 USD
  Expenses:Tax:US:City:NYC 131.57 USD
  Expenses:Tax:US:State:NYDisability 1.2 USD
  Income:Salary:Regular -4192.31 USD
  Income:Allowance:TermLife -7.67 USD
  Income:Salary:401kMatch -209.62 USD

实际的记账中,一进一出的两个账户占了绝大多数。这个时候把正负的金额写两遍未免有点罗嗦了,所以 Beancount 还提供了金额插值的功能。简单说就是假设总和一定是 0,在有 N 个账户的时候,只要求 N-1 个账户声明金额。于是最初的例子还可以写成:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD
  Liabilities:CreditCard:US:Discover

在我的实践中,我只在正好是两个账户的时候才使用这个功能,因为可以避免重复的数字。但有多个账户的时候,把每个金额都写出来有助于避免错误,和「防御性编程」的理念一样。

在上面的例子中,我们还可以看出来,所有Expenses类别的账户都是正数,所有Income类别的账户都是负数。这是 Beancount 及类似工具使用负数来表示借记(Debit),正数表示贷记(Credit)的结果。简单来说,借记就是把账户上的资金移除,贷记就是增加账户的资金。同理,通常Assets类是正数,Liabilities类是负数。本质上每个账户的数值只有绝对值有意义,正负号并没有实际含义。

货币转换

如果在去国外旅游,免不了要进行货币转换。事实上 Beancount 本身没有定义任何货币,这也意味着你可以定义任何货币(或商品)。所以哪怕不出国,只要是记录了非主要货币类资产,譬如投资品,代金券,航空公司里程,那么就需要货币转换了。

在使用多个货币之前,需要先定义「工作货币」。工作货币可以不止一个,例如:

option "operating_currency" "JPY"
option "operating_currency" "USD"

定义了工作货币以后,在 fava 界面中可以看到工作货币单独列出的栏目。

Beancount 货币转换的语法有两种,一种是使用@记录单位货币的转换价格,例如:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD @ 110 JPY
  Liabilities:CreditCard:JP:Rakuten -110000 JPY

另一种方式我更常用,使用@@记录转换后的总额:

2019-01-01 * "日本航空" "纽约-东京"
  Expenses:Transport:Airline 1000 USD @@ 110000 JPY
  Liabilities:CreditCard:JP:Rakuten -110000 JPY

货币转换不一定只在一个账户上。下面的这个例子是以 2.5 日圆每点的价格,买了 10000 日本航空里程,但是付款的信用卡是以美元计价的,所以两遍都可以转换为 25000 日圆来平衡。

2019-01-01 * "日本航空" "购买里程"
  Assets:Points:Airline:JAL 10000 P_JAL @ 2.5 JPY
  Liabilities:CreditCard:US:Discover -220.0 USD @@ 25000 JPY

借贷管理

复式记账的强大之处是每个账户都有状态,而且每个操作都是原子的,这对复杂的资金进出记录非常有帮助。

生活中一个常见的例子是朋友之间的借钱和相互垫付,就拿我最近遇到一个例子来说吧,我和 X、Y 三人一起出游,从东京附近的横须贺坐船到猿岛,费用是每人 1300 日圆的船票和 200 日圆的登岛费,其中船票可以用信用卡支付,而登岛费只能付现金。我们一共需要付 4500 日圆,但是正好谁都没有这么多现金,于是决定我用信用卡付三人的船票,X 用现金付三人的登岛费,最后再结算。

2019-05-25 * "猿岛" "渡轮"
  Expenses:Transport:Ferry 1300 JPY ; 个人渡轮费用
  Assets:Receivables:X 1300 JPY ; 对X应收账款
  Assets:Receivables:Y 1300 JPY ; 对Y应收账款
  Liabilities:CreditCard:JP:Rakuten -3900 JPY

2019-05-25 * "猿岛" "登岛费"
  Expenses:Transport:Attraction 200 JPY ; 登岛费
  Liabilities:Payable:X -200 JPY ; 欠X的钱

第二天,三人结算完毕,X 付给我现金,Y 转账给我。

2019-05-26 * "猿岛" "费用结算"
  Assets:Receivables:X -1300 JPY ; X偿还债务
  Liabilities:Payable:X 200 JPY  ; 偿还对X的债务
  Assets:Cash:JPY 1100 JPY ; X实际付给我的钱到账
  Assets:Receivables:Y -1300 JPY ; Y偿还债务
  Assets:Bank:JP:SMBC:JPY 1300 JPY ; Y实际付给我的钱到账

最终可以看出来,我在Expenses:Transport:Ferry类别消费1300 JPY,在Expenses:Transport:Attraction类别消费200 JPY,信用卡扣款3900 JPY,收到了1100 JPY的现金,Assets:Bank:JP:SMBC:JPY收到了1300 JPY的转账。

以上这种记账方法可以让资金的流动一目了然,类别和金额也准确无误。记录对他人的债权(应收账款)和欠他人的债务(应付账款),我分别使用了Assets:ReceivablesLiabilities:Payable下面的账户。

应收账款和应付账款的另一个用途是区分付款和到货时间。一般来说交易是当场进行的,一个账户的借记和另一个账户的贷记同时发生,但是有些时候付款和到货并不是同时发生的,如果需要精确区分发生的时间的话,可以用这种方法把他们分成两笔记录。

下面这个例子是,用信用卡买日本航空里程,但是不知道为什么过了 1 个月才到账:

2019-01-01 * "日本航空" "购买里程"
  Assets:Receivables:JAL 10000 P_JAL @@ 25000 JPY
  Liabilities:CreditCard:US:Discover -220.0 USD @@ 25000 JPY

2019-02-01 * "日本航空" "里程到账"
  Assets:Points:Airline:JAL 10000 P_JAL
  Assets:Receivables:JAL

这篇文章到此为止,Beancount 最基本的用法已经介绍完毕了。用这些简单的语法,我们已经可以满足大部分的记账需求了,但 Beancount 的强大之处远远不止于此。

三 、结余与资产

先前一篇文章里我介绍了 Beancount 的基本记账方法和一切常见的规范。这篇继续讲述 Beancount 的常用语法,主要内容是对账和资产折旧。

对账

Beancount 的语法检查保证了每一笔交易的借记和贷记是平衡的,这已经可以避免许多会导致「账不平」的错误,但是对于数额本身的错误,或者某调账目漏记并没有办法。这就是为什么我们要定期对账。

资产负债表

所谓对账,就是看每个账户的结余(Balance)是否正确。每个账户的余额可以在资产负债表(Balance Sheet)页面中找到,可以查看样例资产负债表

资产负债表

fava 资产负债表把资产列在左边,负债和权益列在右边。权益即净资产,是根据资产和负债计算出来的(除了Equity:Opening-Balances,之后会讲到)。

需要注意的是 Beancount 的债务和权益是负数,所以并不是资产 = 负债 + 权益,而是资产 + 负债 + 权益 = 0。我之前提过一次,这是 Beancount 使用正数来表示借记(Debit),负数表示贷记(Credit)的结果。在传统的复式记账中,数字的正负号并没有这样的意义,无论是借记还是贷记都是正数,所以绝对值资产 = abs(负债 + 权益)也许更好理解。

在资产负债表上点击任意账户,可以进入账户的明细界面。账户的明细界面列出了涉及该账户的每一笔交易,点开后可以看到具体的交易信息。每一行的最右侧是这一笔交易后的该账户结余,这个数字就是对账的关键。

账户明细

结余断言

假设已知账户的某日结余金额,只要在这个账户明细界面看一看对应的日期的最后一笔交易后结余是否正确就可以了。如果 Beancount 计算出的结余和已知的是一样的,那么基本上就可以确定账没有问题。

这个步骤看似容易,但是随着账目增多,对账的负担会很重,而且容易看错。更严重的问题是,如果因为某种原因要修改过去的账目,已经对好的账就不一定正确了。惟一保险的办法是每次修改了过去日期的交易后,把涉及到的账户未来的结余再全部重新对一遍。好在这个过程是可以自动化的,方法就是使用结余断言(Balance Assertion)。

结余断言就是在记账中加入已知事实,即某个日期开始的时候的某个账户结余。如果你对单元测试有了解,这个方法肯定不会陌生。结余断言的语法非常简单,如下例所示:

2017-08-20 balance Assets:US:BofA:Checking 2298.50 USD

惟一需要留意的地方是,结余断言是所声明日期开始的时候的余额,即当日的交易不算在内。

声明结余断言之后,Beancount 会自动检查断言是否正确,如果不正确就会有错误出现。如果每个账户都有适当的结余断言,修改过去的交易就可以放心进行了。

文档链接

有了结余断言,接下来就是这个断言的事实从哪里来。最简单的方法当然是看看现在有多少结余,然后直接写上今天的日期。这种方法适合现金和其他一些不方便查询交易记录的账户。

除此之外,推荐使用银行月结单(Statement)上面的数字。许多国家的许多银行、信用卡都会定期发送交易明细,一般来说是每月。Beancount 提供了管理这些文件的一个语法,例如:

2013-03-20 document Assets:US:BofA:Checking "path/to/statement.pdf"

在 fava 中这条记录也会被显示出来,并且提供可以点击的链接。这个路径是相对于这条记录所在的 Beancount 文件的目录,这对于多文件记账很重要(include语法,之后会讲到)。

根据个人经验还有一个重要的提醒,就是要看清楚月结单包含哪些交易,尤其是账单周期末的哪些。因为很多银行、信用卡的交易并不是即时结算的,特别是有跨国交易的时候。如果发现某些交易还没出账或者账单周期开始包含了上个周期的交易,一定要注意余额断言的数额。

结余调整

接下来说一说账对不上的情况。事实上,这是一种常态,人毕竟不是完美的,错记漏记实在是太正常了。一旦结余断言失败,当然是先看自己有没有记错什么,如果实在困难,或者金额差距不大自己没那么在乎,可以考虑用结余调整。

结余调整并不是非要用什么特别的语法不可,实际上只是一种规范。这个规范是使用Equity:Opening-Balances来表示初始结余。下面是一个例子:

2015-01-01 * "账户初始"
  Assets:US:BofA:Checking 3490.52 USD
  Equity:Opening-Balances -3490.52 USD

这个例子的意思是,在 2015 年 1 月 1 日给Assets:US:BofA:Checking增加 3490.52 美元结余。假设这个账户之前没有记录的话,那么它现在的结余就是 3490.52 美元,如果有就是在原来的基础上加上 3490.52 美元。这 3490.52 美元从哪里来呢?Beancount 的规范是使用Equity:Opening-Balances

Equity:Opening-Balances是权益类别下面的账户,它是净资产的一部分。Beancount 中权益是负数,所以数字减少代表了净资产的增加。这一部分资产的来源可以理解为表外资产,即来源不明确,在 Beancount 账本中没有更详细的记录。

Beancount 还提供了一个更加简易的语法pad,结合了结余断言,功能是把结余调整到使得下个余额断言满足。用法如下所示:

2015-01-01 pad Assets:US:BofA:Checking Equity:Opening-Balances
2015-01-02 balance Assets:US:BofA:Checking 3490.52 USD

以上的效果是,无论Assets:US:BofA:Checking之前余额多少,在 2015 年 1 月 2 日开始之前都调整到3490.52 USD,差额从Equity:Opening-Balances来。

一般来说除非是调试错误或者导入数据过程中,否则我不建议使用pad这个语法,因为它会让结余断言丧失一定的准确性。使用pad的风险是,如果自动调整的数额过大,当修改了过去其他的账目导致需要调整的数额发生改变的时候,Beancount 并不会出现任何警告和错误。我更倾向于把需要调整的金额明确写出来,这样一旦变化就会有错误提醒,避免更大的错误。

资产购入与折旧

生活中有一些交易我们需要考虑到底要记为花费,还是资产的购入。在光谱的两端一般没什么争议,譬如吃饭肯定是消费,买房无疑是购入资产。中间许多类别就不一定了,这取决于个人的偏好和目的。

拿买汽车作为例子,无论是新车还是旧车,许多人在开一段时间以后会选择卖掉。如果我们把买车记为消费,卖车记为收入,这本身并没有任何错误。问题是,对很多人来说汽车还是一笔不可忽略的资产,如果直接记为消费,没有对应的资产入账,那就意味着净资产突然大幅减值。几年后卖出,净资产又突然增加。

要解决这个问题,我们就要把汽车记为一项资产,下面是例子:

2015-01-01 * "丰田汽车" "买入Corolla"
  Assets:Car:ToyotaCorolla 20000 USD
  Assets:US:BofA:Checking -5000 USD
  Liabilities:Loan:Car -15000 USD

2019-01-01 * "二手车商" "卖出Corolla"
  Assets:Car:ToyotaCorolla -20000 USD
  Assets:US:BofA:Checking 10000 USD
  Expenses:CarUsage 10000 USD

上面这个例子是 2015 年 1 月 1 日贷款买入了价格为$20000的汽车,并计入资产Assets:Car:ToyotaCorolla。中间省略还贷款的过程,四年后2019年1月1日,把车卖给了二手商,获得$10000,剩下的$10000 就是三年来用车的消费了。

这个方法对买卖交易之间的这段时间内净资产计算仍然不够准确,并没有完全解决净资产跳变。因为汽车的使用是三年来平均花费出去的,而不是最后卖的时候一下子花了$10000。要解决这个问题就要引入定期进行折旧(Depreciation)计算。一般的会计方法中把因为资产使用或者随着时间自然减值称为折旧,我记为Expenses:Depreciation:CarUsage

接下来需要决定的是折旧的周期,即把车的使用费按照多大的粒度来记录。这个完全因人而异,也因金额的大小而异。对于车我可以选择按年折旧,因为在许多国家(譬如美国),工作原因的资产折旧是可以按年抵税的。如果希望每个月的花销更加细致,那么按月折旧也是一个方案,只是需要多记录几笔而异。

2015-12-31 * "Corolla折旧"
  Assets:Car:ToyotaCorolla -2500 USD
  Expenses:Depreciation:CarUsage

2016-12-31 * "Corolla折旧"
  Assets:Car:ToyotaCorolla -2500 USD
  Expenses:Depreciation:CarUsage

2017-12-31 * "Corolla折旧"
  Assets:Car:ToyotaCorolla -2500 USD
  Expenses:Depreciation:CarUsage

2018-12-31 * "Corolla折旧"
  Assets:Car:ToyotaCorolla -2500 USD
  Expenses:Depreciation:CarUsage

下一个问题是,每次折旧减值多少。这个问题就是会计上可以操作的空间了,因为实际的价格只有在出售的时候才知道。一般会计准则是预估一个折旧年限,然后以此为根据来折旧。譬如说,我们预期汽车的寿命是 10 年,即 10 年后该车的价值清零,这样我们可以按照每年 10%的折旧率每年减记。最终当实际卖出的时候,我们再根据卖出价格做调整,或者差额记为其他类别。如果有特别的事件发生,还可以另外单独折旧。例如发生了车祸,车的估值大幅下降,可以在此时额外减值。

Beancount 并没有自动折旧的功能,每一笔都是要自己写的。如果怕忘了,其实可以把未来日期的折旧也写上。另外还有第三方的插件beancount-interpolate可以尝试使用。

到此为止我差不多已经讲到了日常使用中的大部分语法。如果把这个系列文章看成一个编程语言教程,那么语法已经差不多讲完了。下一篇文章我会讲述「真实世界」里的 Beancount 使用方法——把 Beancount 账本看成一个项目,该如何管理。欢迎加入 Beancount 中文讨论:t.me/beancount_zh

四 、项目管理

Beancount 系列文章的前三篇已经基本覆盖了常用的复式记账方法。记账本身是一门经验的学问,不仅包括账本身怎么记,还包括了「账本」怎么整理。这篇文章不涉及复杂的会计学概念,只是从更加微观的角度来讲一讲我实际记账过程中是怎么组织的。

版本管理

文本记账的最大优势就是它便于使用版本管理系统,像管理代码一样管理账本。最常见的代码版本管理工具就是 git 了,所以我推荐使用 git 管理 Beancount 的账本文件。使用 git 的好处是提升账本的可维护性,尤其是能够防止不小心改错、误删这样的动作,这在重构的过程中极其重要。

git 如何使用我不再赘述,对于会写代码的人来说属于基本技能。即使不熟悉,网上的资料和教程也是汗牛充栋了,更有 Sourcetree 这样的可视化工具。

惟一需要注意的是,你需要保管好你的 git 仓库,尤其是要避免盲目上传到 Github 之类的网站。账本信息属于非常敏感的个人隐私,因为其中可以透露出的信息非常丰富,甚至超过了日记能包含的。如果要上传 git 仓库备份,至少要使用支持私人仓库的服务,或者自己搭建 git 服务器(支持 SSH 即可)。最好在上传之前对整个仓库加密,譬如使用git-crypt

标签

标签是一个讲交易组织归类的方法,是开支类别之外的另一个维度。每一个交易都能加上一个或多个标签:

2019-06-01 * "奥地利航空" "东京-维也纳" #2019-07-Europe-Trip
  Expenses:Transport:Airline 600 USD
  Liabilities:US:CreditCard:Citi

标签的作用是方便查询,在 fava 和 bean-query 中都可以按照标签来过滤。

为了避免重复,Beancount 还提供了标签堆栈语法:

pushtag #2019-07-Europe-Trip

2019-06-01 * "奥地利航空" "东京-维也纳"
  Expenses:Transport:Airline 600 USD
  Liabilities:US:CreditCard:Citi

2019-06-01 * "奥地利航空" "维也纳-莫斯科"
  Expenses:Transport:Airline 100 USD
  Liabilities:US:CreditCard:Citi

poptag #2019-07-Europe-Trip

除了标签,Beancount 还提供了一个类似的语法^,叫做链接(Link),本质上和标签是一样的作用,但是被建议用作将财务上关联的交易组织在一起的方法。常见的使用场合是有时间跨度的一笔交易,例如汇款和收款,短期的债务,按次记录但是按月征收的某些银行手续费,或者仅仅是两个目的一致的交易。

2016-05-03 * "Chase" "取现" ^2016-05-overdraft
  Assets:Bank:US:Chase:Checking -75 USD
  Assets:Cash:USD 50 USD
  Expenses:Finance:BankFee:Overdraft 25 USD

2016-05-05 * "Chase" "还清欠款" ^2016-05-overdraft
  Assets:Bank:US:Chase:Checking 25 USD
  Assets:Bank:US:Chase:Saving

多文件组织

到此为止我一直假设所有的 Beancount 记录都是在单一文件中的,这个文件会随着账目的增多越来越膨胀,直到用编辑器维护不便。使用单一文件就像把一个巨大的程序写到一个源文件中一样,阅读和修改都很困难。所以 Beancount 提供了include文件包含语法,用法和多数编程语言一样。

include后面紧跟着要引入的文件名,路径是相对于当前文件的。

; main.beancount
include "accounts.beancount"
include "categories.beancount"
include "books/books.beancount"

; books/books.beancount
include "2016.beancount"
include "2017.beancount"
include "2018.beancount"
include "2019.beancount"

利用这个简单的语法,一个巨大的账本就可以分成若干个较小的账本组合起来了。

要注意,到目前(Beancount 2.2.1)为止,标签堆栈和文件包含是不能组合使用的,也就是说标签堆栈内的include的文件不会自动加上标签。我在 Beancount 的问题列表提出了这个问题,作者的答复是也许以后会实现。

账本划分

接下来我终于要讲到我的经验之谈了。尽管有文件包含语法,但每条记录到底怎么划分还是一个见仁见智的话题。我使用了三种分割方法,分别是按日期划分、按类别划分和按账户划分。这三种划分方式都有道理,各有优劣。总而言之,划分的目的是减少错误的可能,降低维护成本,节约注意力资源。下面我详细说来。

按日期划分

按日期划分账本是最直接的分割方法,我们可以按年份或者月份创建文件(譬如2019.beancount),每个文件内只包含这段时间内的记账。一般来说除非是修正错误或者重构,旧的账不会再修改,使用当前的账本来记账,把过去的账分割储存是一种有效节约注意力资源的方法。

除了按照日历时间划分,还可以结合标签,把某些事件提取出来,最常见的是旅行。下面这个例子是我把 2019 年 7 月关于欧洲旅行的账目全部放到2019-07-Europe-Trip.beancount中,并且结合标签堆栈,把整个文件中的账目标记上#2019-07-Europe-Trip标签。

; 2019-07-Europe-Trip.beancount
pushtag #2019-07-Europe-Trip

2019-06-01 * "奥地利航空" "东京-维也纳"
  Expenses:Transport:Airline 600 USD
  Liabilities:US:CreditCard:Citi

...
poptag #2019-07-Europe-Trip

使用单独的文件来记录某个时间段的某类事件相关的开销的好处是,一旦这个事件结束,这个文件就可以进入封存状态了。此外,哪怕是不使用任何可视化工具(如 fava),这个文件的可读性也非常高,甚至可以当作旅行日记了。

资产负债表

按类别划分

按类别划分指的是把统一类的重复记录整理到一起,便于根据时间纵向比较。譬如说每月的工资单、房租、水电费、定期投资、以及其他自动扣费的服务。

; Tokyo-Electric-Power.beancount

2018-04-25 * "东京电力" "电费"
  Expenses:Utility 4621 JPY
  Liabilities:JP:PrestiaVisa

2018-05-25 * "东京电力" "电费"
  Expenses:Utility 4956 JPY
  Liabilities:JP:PrestiaVisa

2018-06-25 * "东京电力" "电费"
  Expenses:Utility 6648 JPY
  Liabilities:JP:PrestiaVisa

2018-07-25 * "东京电力" "电费"
  Expenses:Utility 9394 JPY
  Liabilities:JP:PrestiaVisa

以上的例子是每月 25 日扣费的东京电力账目。这样记录的好处是每个月的开销一目了然,还可以观察不同月份的开销变化。如果可以从扣费的服务导出账单,就可以把单个来源放在一个文件。如果是手动记录,这种组织方式也可以最大程度上减少漏记的可能性。

按账户划分

我使用的第三种分割方法是按账户划分。按账户划分和按类别划分差不多是相互对称的两种划分方法,因为这种方法是以账目相关的账户为依据,分割出了单独的账本。按账户划分的最初目的是方便自动导入脚本,因为许多银行、信用卡都可以提供月对账单,非常适合自动转换为 Beancount 格式,减少人工。

但是完全按照账户的缺点也显而易见,那就是尤其和「按类别划分」冲突,所以我的实际手段是尽可能不使用这个划分方法。什么时候会使用呢?我的原则是和账户的关联性十分强的消费,包括银行利息、银行手续费、信用卡还款、返现返点、开户奖励、年费、账户间转账。

; AmexSPG.beancount
2019-02-01 * "American Express" "SPG年费"
  Expenses:Finance:CreditCardFee 33480 JPY
  Liabilities:JP:AmexSPG

2019-02-01 * "American Express" "Amex SPG开卡奖励"
  Assets:Points:US:Marriott 30000 P_MRT
  Income:OpeningBonus

2019-02-22 document Liabilities:JP:AmexSPG "AmexSPG/2019-02.pdf"

2019-03-11 * "American Express" "2019年2月账单还款"
  Assets:Bank:JP:SMBC -3254 JPY
  Liabilities:JP:AmexSPG

2019-03-22 document Liabilities:JP:AmexSPG "AmexSPG/2019-03.pdf"

2019-04-10 * "American Express" "2019年3月账单还款"
  Assets:Bank:JP:SMBC -63935 JPY
  Liabilities:JP:AmexSPG

2019-04-22 document Liabilities:JP:AmexSPG "AmexSPG/2019-04.pdf"

2019-05-10 * "American Express" "2019年4月账单还款"
  Assets:Bank:JP:SMBC -23718 JPY
  Liabilities:JP:AmexSPG

总结

我把我的原则总结成了以下规则,按顺序判断:

  • 如果该收支属于一个特别的事件,那么就将其加入这个事件对应的单独文件,例如#2019-07-Europe-Trip
  • 如果该收支是一个周期项目,但跟某个银行账户无关,则加入按类别划分的文件,例如水电费、房租、工资。
  • 如果该收支跟某个银行账户紧密相关,则加入该账户对应的文化,例如银行手续费、信用卡还款、返现。
  • 不属于以上情况,则加入按日期划分的文件。

以上就是我目前对账本管理的一些经验之谈,这些方法只是为了达成我前面提到的三个目的:减少错误的可能,降低维护成本,节约注意力资源。