2024 年度记账总结
不知不觉居然到了 2024 年的最后一天。学习和生活方面其实没有什么好总结的,大约还是在继续摸索适合自己的节奏。不过去年 pick 了一个蛮有趣的小习惯,今年可以说是逐渐从入门到精通了,干脆写一篇文章总结一下个人的经验吧。
记账小白,小试牛刀
最初我开始记账也并非是源于什么契机,也只不过是觉得这是一个比较“有涵养”的高门槛习惯。平铺直叙地说,我并不是因为需要监控自己的消费习惯,或者培养某些“自律”之类的品德——纯粹是为了佯装自己是个理性而节律的人罢了。而且记账似乎能让我更好地学习一些金融学知识,跟股民朋友聊经济话题时,说不定能让自己不落下风一些。
但是既然下定决心要做一件事情,我就决定要把它做好,至少要做到令自己满意。每个记账人要抉择的第一件事就是找到一个属于自己的记账 APP。而对一个记账小白来说,这往往是最头疼的。一开始记账,我完全没有明确的目标:我想收获什么?为什么一定要记账?记账的终极目标是什么?或许是“一问三不知”。面对世面上众多的记账 APP,我难以分辨哪个才是适合自己的,因为心中并没有那个理想的形象。在刷了几天知乎和小红书,随手下了几个 APP 尝鲜后,我仍然一头雾水。最后我还是选择了叨叨记账这个所谓的“聊天式”记账 APP,因为它界面相对清爽,功能相对全面,好评也不少。
结果是比较惨淡的:使用叨叨记账一个月后,我就放弃了记账。原因是偶然有几天忘记了在那个聊天框里输入消费记录,又懒得去查前几天的流水把账本补上,所以干脆放弃了记账这个“负担”。我承认我的第一段记账体验几乎是毫无营养。叨叨记账(以及世面上大部分的记账 APP)其实都需要用户培养一个习惯:每次消费后,打开手机中的记账软件,输入一些信息。由于记账接口的封闭性、支付来源的复杂性,用户几乎无法避免需要肉眼核对每一条账单——一边是支付宝、微信支付呈现的格式,一边是记账 APP 呈现的格式。不断地重复工作实实在在地消磨记账者的耐心和兴趣。随着学业压力的增大,我也不再情愿腾出更多时间处理那个已经一团糟的账本了。
如今回过头来看,叨叨记账的确是一个不适合我的记账 APP。聊天式记账看似很创新,但是这个功能对我来说毫无作用,新鲜感褪去后,这种记账方式既不能让记账变得便利一些,也不能给我带来额外的情绪价值。很多小白就是像我一样倒在了这里,然后再也找不到记账的动力。
后来我反思:什么样的记账 APP 才是适合我的?使用叨叨记账的挫败经历教会我——重要的并不是用什么 APP 记账,重要的是找到适合自己的记账方式。好的记账方式应该至少有两个特点:便利性、能提供情绪价值。我毕竟不是专业的会计,记账不应该成为我生活的累赘,如果记账这件事无法带来成就感之类的情绪价值,那记账者很难借由“自律”来养成这一习惯。这对于绝大多数的业余兴趣来说都是如此的,培养习惯的最好办法就是形成正反馈。
叨叨记账中的账本也就尘封在我的账号里了,我的第一段记账经历永远成为了沉没成本。但是回过头来看,这段试错的经历其实也是很珍贵的——它让后来的我能更好地理解自己“为什么要记账”这个根本问题。
和 Beancount 的偶然结缘
叨叨记账还是那样躺在我的应用文件夹中,角标上的红色气泡在提醒我已经很久没有记账了。放弃记账一段时间后,我终于决定卸载它,估计自己很长一段时间内不会再碰记账这件“无聊”的事情了。
那学期的期末考后,我闲暇时逛 Github 发现了一些有趣的金融项目。印象比较深的是个人金融系统 maybe,据说是商业化失败后转而开源,结果成功续命。这些项目对我来说门槛太高,自然是放在 star 列表里躺尸了。可能因为我 star 同类型的仓库多了,Github 给我推送了 Beancount 项目。Beancount 真正意义上重新燃起了我的记账念头。
Beancount 的仓库简介:Double-Entry Accounting from Text Files。这是我第一次听说 Double-Entry Accounting 这个名词,Wikipedia 有张图很好地描绘了这种记账形式:
Double-Entry Accounting 的中文是复式记账法,因为每笔交易都至少记录在两个不同的账户当中,且该笔交易的借贷双方总额相等,即“有借必有贷,借贷必相等”。复式记账法的优势在于它完备的表达能力。传统的流水记账法面对现实生活中的某些交易场景束手无措,比如我给实验室买了开发板,但是先用自己的钱垫付,如果扣在自己的账户上,就无法表达实验室欠我钱这个信息;如果扣在实验室的账上,自己的账户没有变动,月末对账的时候就会感到头疼。而对复式记账来说,只需要表达成我的账户向实验室账户转入了一笔钱。复式记账也不需要消费类别这一概念,因为不同类别的消费可以表达为资金转入了代表不同消费类别的虚拟账户。统计的时候,只需要计算虚拟账户的余额,就可以立刻得知自己在某个类别上花费了多少钱。
对每笔交易来说,借贷双方总额相等,这是一个非常强的限制条件。在复式记账中,满足这一条件叫作“配平”交易。因为每一笔交易都是配平的,归纳得知所有交易的总额相加也是零,也就是说,所有账户的余额相加应该永远为零。虽然约束条件严格,但使用起来其实更加方便,就像是在编程的时候引入了“静态检查器”,能够降低记错账的可能性,也使得对账过程更加灵活可控。
Beancount 本身是复式记账一种实现,使用固定语法的纯文本文件表示账单,使得记账这件事和程序员编码几乎无异。而纯文本格式是最开放的格式,存储便利、修改简单,也非常适合将不同的账单格式统一起来。
初识 Beancount 时,我拜读了作者的文章 Command-line Accounting in Context,其中阐述了作者开发一款纯文本记账软件的初衷和目的,也涵盖了一些复式记账法的理念和 Beancount 的设计思想。我对记账这件事情突然燃起了极强的兴趣,我花了半个多月的闲暇时间去仔细学习复式记账法和 Beancount 的使用方法。不仅仅是因为 Beancount 这样看起来相当“极客”的记账软件让我感到“相见恨晚”,更是因为 Beancount 让我明白记账这件事其实大有学问,一些我之前记账的时候感到困惑的问题(比如如何正确表示基金的利息收入),在会计学中其实都有相当成熟和自洽的解决方案——绝非是我一拍脑门就能领悟的。我这才想起来,学习新知识本身就是我最早开始记账的动力之一,而枯燥乏味的记账 APP 既不能满足我的记账需求,又无法教会我新的知识,所以我才会对记账丧失了兴趣!
这一次我并没有急着开始记账,而是先踏踏实实地复式记账相关的基础知识。Beancount 的手册也写得相当详尽,学习 Beancount 语法的过程也同步加深了我对复式记账的理解,如此形成的正反馈也颇为有趣。
重拾记账
万事俱备,只欠东风,2024 新年的时候我决定重新开启记账这一习惯。过年期间待在家中,消费单数其实不多,但是刚开始使用 Beancount 这种纯文本的记账方式还是有些生疏。那时我姐看我在记账,吐槽说我哼哧哼哧打了半天字就为了记录一项消费,她在 APP 上点一下五秒钟就够了。
我有些尴尬,但是无法反对。像是月初我收到爸爸发的生活费时需要在账单中写下:
2024-01-02 * "父亲" "生活费"
Assets:CN:CMB:Debit-Card 3500.00 CNY
Income:Family:Living-Expense
当时我用来编辑账单的软件是 VS Code,它对 Beancount 的支持并不是很完善,完整编辑这些信息并不是一项很便利的事情。开学后我的消费数量增多,而我还是采用原始的手动录入方法来编辑我的账单,我逐渐吃不消每日记账的工作量,偶尔漏掉小数点、写错正负号是家常便饭,导致最后 balance(相当于 Beancount 中的 assert 语句)的时候往往对不上账,只能重新核对一遍消费记录,很是耗时。
开年后的记账习惯一直维持了三个多月,我几乎每天保持着手动录入账单的习惯,我的 Beancount 账单汇聚着来自微信、支付宝等支付平台的所有消费记录,相当完备地表示了多个账户的消费情况。然而,我的记账方式和习惯还是太“朴素”了——时间成本实在太高了。随着后半学期课程大作业蜂拥而至,我只能暂时搁置了重拾来的记账习惯。
这一段记账经历虽然也是以失败告终,但是比起上一次使用记账 APP 来说,我学习到了更多的知识,也首次在记账这件事中获得了成就感。我将其视为我找到完美记账解决方法前的必经之路,这也促使我去寻找一些优化记账效率的方法,让记账变得不再笨重和耗时。
完美的记账解决方法:Beancount Importers
Beancount 的作者在文档中介绍过 Beancount 记账的基本工作流:How the Pieces Fit Together。下面这张流程图基本能概述标准的 Beancount 记账方式:
使用 Importers 来转换不同来源的消费记录,输出统一的 Beancount 格式文本文件作为输入,最后形成 Web 页面展示或者其他数据报告。先前我的记账方式并没有采用 Importers 来导入其他消费记录,导致记账过程过于繁琐。人工介入的部分太多并不是件好事,因为这没有充分利用纯文本格式的灵活性和自由度,也容易导致错误频发。
顺着这个方向探索,我找到了 double-entry-generator 这一项目。虽然它用 GoLang 语言实现,并没有采用 Beancount 官方推荐的 Python API beancount.ingest
,但胜在对中文区的前端支持更为丰富,涵盖微信、支付宝、京东等常用支付平台。Beancount 的语言规范现在也非常稳定,另辟蹊径与 Python 依赖解耦也是能够接受的曲线救国方案。不过项目的用户操作体验有点儿不那么直观,刚开始使用时,我一直没搞清楚怎么写配置文件,如何正确调用命令行,最后还是结合源码阅读掌握了使用方式 😂。
虽然 double-entry-generator 项目本身有些槽点,但它依然是非常优秀的开源项目,因为它着实解决了我长期以来的记账痛点。使用这种自动导入的记账方式后,我幡然醒悟,自己原先记账的痛苦来源是枯燥乏味的对账过程。人很难从重复的工作中寻觅到源源不断的成就感和动力,最终就会因为正反馈缺失而无法养成习惯。Double-entry-generator 算是基本解决了我的记账症结问题,从此,我将记账习惯改为了每个礼拜一次,过程非常轻松:在微信和支付宝等平台下载个人账单数据,然后用 double-entry-generator 转换为 Beancount 格式,最后手动补充一下 FIXME 字段(因为有些账单不能完全自动转换为 Beancount 格式)并核对一下账户剩余金额。整个过程大约只要十五分钟,可以同时听几首歌或者看看视频,可谓是我每个礼拜最轻松惬意的时光。几乎全自动的账单导入方式也大大降低了记账的出错率,大部分情况下最后的 balance 语句都能直接通过,“it just works”!
至此,我的记账工作流已经近乎完美。我也改进了其他的一些周边设施,比如使用 GNU Emacs 作为编辑器,因为有官方提供的 beancount-mode 插件,编辑体验更加友好;将 double-entry-generator 打包到 Nixpkgs,方便安装使用;开发了 beancount_sjtu_ecard_importer 用来导入交大校园卡的流水信息,补足了自动化记账的“最后一块拼图”。如今,Beancount 记账非常自然融入了我的日常工作流,这得益于 Beancount 本身的高可拓展性和自由开放的格式,也多亏了开源社区的力量,让我能接触复式记账法这一高效完备的记账方法,学习更多和记账有关的会计学知识。
可视化和报告
这一节简单提一下我用来做账单可视化和报告的工具。记账的目的之一是监控自己的消费情况,并进行适度总结和反思。随着账单数据的逐渐累积,结果也变得更加有意义,记完账后我总是仔细观察曲线的形状变化,成就感也因此油然而生。
我使用 fava 来进行账单可视化:
Fava 还支持使用 Query 来进行精细的按需查询。Beancount 的 Query 语法和 SQL 几乎一致,例如,我想查询我的剩余总资产,可以使用:
1
select sum(position) where account ~ 'Assets:'
大二学到的 SQL 写法终于有一个实际的个人使用场景了!这也是 Beancount 的设计让我喜爱的一点:复用现有的框架和设计,尽量不去做额外的重复工作。对于技术爱好者,这是多么难得的福报!
附录
- double-entry-generator: 将微信、支付宝等支付平台的流水数据转换为 Beancount 格式。
- beancount_sjtu_ecard_import: 将 SJTU 校园卡流水转换为 Beancount 格式。
- Fava: Beancount 官方可视化工具。