来自生产实践
创始人招第一位金融科技工程师,该看什么
作者:曾祈荣 · · 阅读约 10 分钟
在大多数软件里,bug 只是让人烦一下。按钮点了没反应,页面加载慢,刷新一下也就过去了。在金融软件里,bug 就是某个人的钱:付了款却始终没有入账的入金,发出去两次的提现,本该被停掉却还在交易的账户。
你的第一位工程师定下的习惯,整个代码库都会继承。后来的人会照着现有的代码写,所以第一个月怎么处理重复的支付通知,到第三年还是那样处理。这次招聘虽然只招一个人,分量却比看上去重得多。
你也许没法评判代码,但仍然可以评判人,因为真正要紧的习惯,会在交谈中、在对方写下来的东西里显露出来。下面有八个值得留意的方面,一小段用大白话讲解的代码,第一次通话可以问的问题,以及一份危险信号清单。
1. 先问会怎么出错,再问长什么样
细心的工程师最先问的是失败的情况:支付服务商的通知到了两次、迟到了,或者根本没到,会怎么样。你会听到三个词。幂等,是指同一件事做两次和做一次结果相同;重试,是每家服务商都会做的重复发送;对账,是定期把你的记录和服务商的记录比对一遍。
我修过的大多数支付 bug,都不在结账页面,而在回调,也就是服务商发来告诉你“这笔付款成功了”的那条消息。在此前一份工作的一个票务项目里,正是状态轮询、幂等履约和自动对账任务,让漏掉的回调没有变成丢失的订单。
2. 会主动告诉你自己没做过什么
金融科技这一个词底下,其实是好几个专业方向。做过券商支付流程的专家,不等于做过交易引擎;做过交易所的人,也不等于对接过银行卡支付服务商。好的候选人会在你开口问之前,就把这条线给你划清楚。
拿我自己来说:作为自由职业者,我为一家线上自营交易公司(prop firm)做了 MetaTrader 5 对接和实时风控监控;在目前的工作中,我在一家正在运营的券商做入金和支付流程。自研交易引擎,我没有在生产环境交付过,我的网站上也是这么写的。如果你的产品需要一个,我会明说它是一份设计:在客户资金接近它之前,必须先经过测试、概念验证和独立评审的检验。
3. 先证明原因,再改代码
出了问题,草率的做法是改掉最可疑的那一行,看问题会不会消失。细心的做法是先找证据。在跟钱有关的系统里,对着错误的原因去修,真正的故障还在原地,又多添了一个。
在券商那边,加密货币交易出现过 HTTP 500 错误。靠关联 ID(correlation id,一个跟着同一个请求出现在每条日志里的标记)一路追查,最后定位到重复的交易对记录。修复的办法是让交易品种(symbol)的查找变成确定性的,而不是把错误藏起来。在自营交易公司那边,高负载下有些账户会短暂地读到另一个账户的数据。原因是交易平台的 SDK 不是线程安全的(不能同时从好几处安全地调用),所以现在所有调用都经过同一把锁,另有一道校验确认返回的账户就是请求的那个账户。
请候选人讲一个这样的故事。要听的是对方怎么知道原因的,而不是修得有多快。
4. 把事情写下来
看看有没有这些东西:写成文字的范围、不在范围内的事项清单、部署顺序,以及运维手册,也就是日常操作和紧急操作的简短说明。有了文档,你不读代码也能检查工作。下一位工程师也不必把一切从头摸索一遍。
在自营交易公司那边,部署手册只有四步:停掉两个服务,部署,在风控监控关闭的状态下手动检查一个已知违规的账户和一个已知正常的账户,然后再打开监控。它很短,却能消除一整类事故。谁要是说文档以后再补,就要多留个心眼。
5. 第一版做得小,架构不花哨
涉及资金的第一版,应该只做少数几件事,并且把它们做对。落到实处就是:能用一个可部署的程序就只用一个,每家服务商只走一条对接路线,自动化可以晚点做的环节先用人工操作。由一个人在管理后台审批提现,就是一个完全合格的第一版。
这种形态通常叫模块化单体:一个程序,作为一个整体部署,内部划分成边界清晰的几个部分。我自己的产品 SolveMY 是一个已经上线的市场平台,由我一个人做出来,跑在一个可部署的 Go 服务上,31 个领域服务共 235 个接口。这样的系统一个人就能运维。如果一个产品还没有用户,方案里却有许多小服务,每两个之间还隔着一个队列,就要谨慎对待。
6. 不只会写代码,还能运维生产环境
代码能在笔记本电脑上跑起来,只是开始。还得有人确保:有备份,而且真的恢复过;监控能赶在客户之前发现故障;告警能送到具体的人手里;还有一个预发布环境,让改动先对着服务商的测试模式试一遍。
在自营交易公司的项目里,这指的是带告警邮件的健康监控、崩溃通知、自动重连,以及模拟盘和实盘各自独立的服务。SolveMY 的生产环境由我自己运维,用的是 Docker、备份和监控。如果你的第一位工程师从没对一个线上系统负过责,就要另外安排一位有这种经验的人。
7. 对怎么用 AI 工具很坦诚
现在大多数工程师都在用 AI 编程助手,这没有问题。我每天都用。起草代码、熟悉陌生的代码、写测试,用它们都很快。
要紧的是产出怎么检查。在任何会动到钱的路径上,生成的代码都只是草稿:要有人把改动(diff)逐行读完,并由测试来证明行为正确。哪一行代码工程师自己解释不了,就不该让它上线,不管是谁写的。如果一份排期只有在没人去读工具产出的前提下才赶得上,就要当心。
8. 知道工程的边界在哪里
牌照、KYC 和 AML 政策(身份核验和反洗钱规则)以及法律架构,归你和你的顾问负责。要不要牌照、谁可以持有客户资金、能向投资者承诺什么,这些都是法律问题。工程师要是答得很有把握,那是在猜。
好的工程师会把系统设计成能满足这些管控要求:审计记录、审批环节、限额,以及合规顾问可以导出的记录。某个功能需要先找律师、再写代码的时候,也会直接告诉你。我不是律师,也不是合规官,这句话我宁愿在第一次通话时就说,而不是拖到第三个月。
用二十来行代码看“细心”长什么样
看懂这个例子,不需要会读代码。它是支付系统中的一部分,负责接收服务商发来的“这笔入金已付款”的消息。带编号的三条注释标出的那几行,就是细心的工程师和草率的工程师之间的差别。
const rawBody = express.raw({ type: 'application/json' });
app.post('/webhooks/payments', rawBody, async (req, res) => {
// 1. Prove the message came from the payment provider before reading it.
if (!signatureIsValid(req.body, req.get('x-signature'), WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
await db.transaction(async (tx) => {
// 2. The table accepts each provider event id once. A repeat saves nothing.
const saved = await tx.query(
'INSERT INTO payment_events (provider_event_id) VALUES ($1) ' +
'ON CONFLICT (provider_event_id) DO NOTHING',
[event.id],
);
if (saved.rowCount === 0) return; // seen before: change nothing
await creditDeposit(tx, event); // saved together with the row above
});
// 3. Reply "received" only after the transaction has committed.
res.sendStatus(200);
});有第 1 步,是因为接收这些消息的地址是公开的,任何人都可以往上面发一条假的“付款成功”。有第 2 步,是因为服务商只要不确定你收到了通知,就会再发一遍。数据库里没有这条规则的话,一条重复的通知就会给客户入账两次。
第 3 步讲的是顺序。如果服务器先回复“已收到”再保存,中间一旦崩溃,一笔真实的付款就丢了,而服务商没有理由再发一次。把回复放在最后,失败就会引来重试,而第 2 步保证了重试是安全的。
少了这三行的版本,每一次演示都能通过,只有在出事的那一天才会失败。完整的清单我写在永不重复入账的支付回调里。
第一次通话可以问的问题
这些问题都不需要技术背景。少听术语,多听回答够不够具体。
| 问题 | 好的回答大致是 | 让人担心的回答大致是 |
|---|---|---|
| 如果支付服务商把同一条通知发了两次,会怎么样? | 这种事一定会发生。每个事件都按服务商的 ID 存储,并加了唯一键,所以第二条不会改变任何东西。 | 这种事不应该发生。或者:服务商会处理的。 |
| 如果一笔入金付了款却始终没有入账,我们怎么知道? | 有一个定时对账任务,把我们的记录和服务商的记录比对,一有对不上的就告警给具体的人。 | 客户会联系客服的。 |
| 第一版不包含哪些东西? | 给出一份具体的清单,并说明这期间哪些靠人工处理,每一项什么时候再回头评估。 | 什么都不会落下,全都会做进去。 |
| 如果你离开了,还有谁能维护这套系统? | 任何熟悉主流技术栈的合格工程师都可以。代码、测试和运维手册都放在你们的代码仓库里。 | 你们不需要别人。或者:系统依赖一个只有这位工程师自己懂的框架。 |
| 这里面哪一部分是你以前没做过的? | 点出是哪几部分,并说明怎么降低风险:做概念验证、请人评审、把第一步做得更小。 | 这些我以前全都做过。 |
| 涉及余额的改动,上线前怎么测试? | 有自动化测试,有接在服务商测试模式上的预发布环境,发布前还有人把改动逐行读一遍。 | 我会在线上系统里仔细测试。 |
| 源代码、服务器和服务商账号放在哪里? | 从第一天起就放在你们公司自己名下的账号里。我拿到的是你们可以收回的访问权限。 | 先放在我的账号里,以后再转给你们。 |
| 开工之前,你需要我们的律师或合规顾问提供什么? | 由哪个主体持有客户资金,适用哪些牌照,KYC 规则是什么,以及支付服务商是否已经批准这项业务。 | 什么都不需要,那不是技术上的事。 |
危险信号
- 范围还没有人界定,第一次谈话就给出固定报价。
- 承诺两个月内全部做完。
- 对你的牌照,或者支付服务商是否批准了这项业务,一句都不问。
- 没有预发布环境,所以每一处改动都是先在存着真钱的系统上试。
- 余额只存成一个数字,没有留下形成这个数字的逐笔记录。
- 不愿意交出源代码,以及系统所在账号的访问权限。
出现其中一条,值得多问几个问题。同时出现好几条,就该继续找别人。
怎样从小处开始
你不必谈一次就把整个项目定下来。想看一个人怎么思考,成本最低的办法是一次付费的技术调研冲刺或架构冲刺:一段时间短、范围固定的工作,最后交出一份书面成果。
这份文档应该写清楚范围、不在范围内的事项、主要风险、对接路线、第一版的计划,以及留给律师的待定问题。检验标准是:换任何一个团队,能不能照着它做出来。如果能,从小处开始并没有让你损失什么。如果文档含糊不清,你也是花很小的代价就知道了。
我自己就是尽量按这个方式工作的,欢迎你拿这份清单来要求我。如果你的产品涉及资金,也想要这样的帮助,我的金融科技技术合伙人页面上有具体介绍,也可以联系我聊一聊。
- 招聘
- 创始人
- 金融科技
- 支付
- 工程实践