一个 HTML 文件的分账工具:bill-splitter 纯函数设计赏析
698 字
3 分钟
一个 HTML 文件的分账工具:bill-splitter 纯函数设计赏析

bill-splitter 源码赏析:一个 HTML 文件的工程哲学
bill-splitter 是一个合租账单分摊工具,整个应用只有一个 HTML 文件。但别被体积骗了——它的核心算法设计得非常严谨。
核心:__split 纯函数
整个应用的计算逻辑收敛在一个函数里:
__split(totalCents, weights, ...rest) → number[]作者给这个函数立了四条不变量(无条件成立):
| 编号 | 约束 | 含义 |
|---|---|---|
| I1 | 长度 | 返回数组长度 === 权重数组长度 |
| I2 | 整数 | 每个元素都是整数(金额用分表示) |
| I3 | 守恒 | 所有元素之和 === totalCents |
| I4 | 纯 | 同样输入永远得到同样输出 |
这四条不变量就是一份可执行的规格文档。不需要读实现,光看这四条就知道函数的行为边界。
最大余数法 + 零头集中
分摊算法用的是最大余数法:
// 1. 权重预处理:NaN 和负数 → 0// 2. 退化处理:权重总和为 0 → 全部置 1// 3. 按比例取整:floor(总额 × 权重/总权重)// 4. 零头分配:剩余零头全部给余数最大的人举个例子,__split(101, [1, 1, 1]):
// floor(101/3) = 33, 33, 33 → 剩余 2 分// 余数分别是 0.67, 0.67, 0.67(相等)// 按下标从小到大,第一个承担全部零头// 结果:[35, 33, 33]为什么零头要集中给一个人? 作者的理由很实用——房东一眼能看出每月零头算在谁头上。这是工程思维:算法上的「公平」(轮流承担)在这里不如「可解释性」重要。
防御性边界处理
函数对异常输入的处理也很教科书:
__split(100, [0, 0, 0]) // 权重全0 → 均分 [34, 33, 33]__split(50, [NaN, -1, 30]) // NaN/负数 → 0 → [0, 0, 50]__split(-100, [1, 2, 3]) // 负金额 → 守恒=-100__split(0, [5, 5, 5]) // 零金额 → [0, 0, 0]每个边界 case 都有明确的行为定义,不抛异常、不返回 NaN。
架构:数据驱动 + 全量重渲染
页面架构极其简单:
配置数据(PERSONS / BILLS / DAYS) ↓ input 事件render() 全量重渲染 ↓ 每笔账单调 __split()没有框架、没有虚拟 DOM、没有状态管理。因为数据量小(3 人 × 4 笔账单),全量重渲染的性能完全够用。在正确的场景用正确的复杂度——这也是工程能力。
学到的三件事
- 不变量比实现重要。 先定义行为约束,再写代码,测试和 review 都有据可依。
- 金额必须用整数(分)计算。 浮点数在金额场景是万恶之源。
- 零头集中不是 bug 是 feature。 工程决策要考虑业务可解释性,不只是数学正确性。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
一个 HTML 文件的分账工具:bill-splitter 纯函数设计赏析
https://blog-tyb.pages.dev/posts/bill-splitter-analysis/相关文章智能推荐
1
源码阅读笔记:Seat Reservation System 的定时任务与状态机设计
源码解析分析自习室座位预约系统的核心设计:座位状态机流转、TimerTask 定时异常处理、信用分奖惩机制。
2
多商户商城系统源码解析:一笔订单背后的拆单、渲染与兜底
源码解析深入一个 B2B2C 多商户商城的 Java 源码,拆解交易-订单两级拆单、购物车渲染流水线、定时兜底与商家端数据隔离四个核心设计。
3
DeepStudy 源码解析:三层卷积 CNN 的标准训练范式
源码解析拆解 DeepStudy 的 MNIST CNN 实现:三层卷积的尺寸推导、log_softmax+NLLLoss 组合、训练五步循环与完整的保存加载闭环。
4
TaskWeaver 源码解析:LangGraph 四节点循环 Agent 架构
源码解析深入 TaskWeaver 源码,拆解 Planner-Executor-Reviewer-Responder 四节点循环图的实现,以及它如何用双计数器防止 Agent 死循环。
5
如何阅读优秀开源项目的源码
学习方法分享一套可复用的源码阅读方法论:从宏观架构到核心链路,再到设计模式的落地应用。
随机文章随机推荐









