现场给你一段约 50 行的 Python 函数 process(items),它在一个三层嵌套的 for/if 里同时做四件事:跳过 item['status'] != 'ok' 的记录;把 item['amount'] 累加到以 item['uid'] 为 key 的字典里;对负数或 None 的 amount 打一条日志;循环结束后再遍历一遍这个字典,算每个用户的平均值并写回同一个字典。变量名是 i、tmp、d1、data2 这种。请你做两件事:(1) 指出这段代码在读的时候最难的地方具体是什么;(2) 在不改变任何行为的条件下重构,讲清你会拆成哪几个函数,每个函数的名字、输入和输出分别是什么。
面试官在考什么
判断候选人能不能把「可读性」落到具体的职责边界和命名契约上,并且知道重构的第一约束是行为不变(包括日志这类副作用)。
回答框架 · 前两步
- 定位问题要具体:一个函数承担了过滤、聚合、日志、统计四种职责,加上 i/tmp/d1 这类无信息量的名字和三层的嵌套,读的人要在脑子里同时维护多个状态。
- 拆分原则是按职责切出纯函数:例如 iter_valid(items) 产出合法记录、amounts_by_user(items) 返回 {uid: [amount]}、build_averages(...) 返回平均值,每个函数的名字直接体现输入输出。
还有 3 条答案要点、 3 层追问、 5 个常见坑, 以及针对你简历的追问路径,在定制报告里。
同一能力域的其他题
- 现场编码:给定一个长度约 10^6 的 int 数组 nums(元素值域 1~10^4,可能大量重复)和一个整数 k(1 ≤ k ≤ 不同元素的个数),返回出现频率最高的 k 个元素,顺序不限。要求你先把方案说清楚——你打算用哪种做法、为什么——再动手写代码。写完后请说明你的时间/空间复杂度,以及当 k 等于「不同元素个数」时你的代码会发生什么。
- 有一个 200GB 的日志文件,每行是一个 IP 地址,单机内存 16GB,要求输出出现次数最多的前 100 个 IP。请写出核心代码(语言不限,可用伪代码描述关键部分),并说明你为什么这么选。如果换成 200TB、10 台机器,你的方案哪里要改?
- 场景:你在做 API 网关的单机限流。要求:窗口 1 秒,QPS 上限 1000,允许突发 2000,P99 延迟增加 < 1ms,内存 < 10MB。请现场手写核心数据结构与 tryAcquire() 代码,并说明你选固定窗口、滑动窗口、令牌桶还是漏桶,以及理由。如果再要推广到 500 台机器做全局限流,你会怎么演进?
- 请手写一个函数:输入一个只包含小写字母的字符串 s,返回其中不含重复字符的最长连续子串的长度。例如 s = "abcabcbb" 返回 3,s = "bbbbb" 返回 1,s = "" 返回 0。要求时间复杂度 O(n),并在写完后说明你在循环里维护的那个不变量(不变式)到底是什么,以及它为什么保证答案不会漏掉。
- 现场实现一个函数 parse_duration(s) -> int,把形如 "1h30m"、"90s"、"2h" 的时长字符串解析成总秒数,单位只支持 h/m/s。要求你分两步做:第一步先用一句话写清这个函数接受的「合法输入」到底是什么(契约),并列出你会覆盖的边界输入清单;第二步再写代码。
- 有一个按 key 非降序排列的数据集,共 10^9 条,存在远端存储上,只提供两个接口:get(i) 读取第 i 条记录(一次调用约 5ms,支持随机访问),size() 返回总条数。数据中 key 有大量重复。请实现 lower_bound(target):返回第一个 key >= target 的下标,都不满足则返回 size()。写出代码,并说明你对哪些边界做了处理、每一处为什么这么处理。