某大厂P9,工作10年,年薪300万+,带上百人团队。他说:“今年业务砍了三轮,团队从200人砍到80人。下一个砍的,可能就是他
45岁,阿里干了10年,年薪300万+,手底下管过上百号人,放外面看,这履历够硬吧?很多人可能觉得,这种人怎么会慌,资源、人脉、钱都不缺。
结果人家一开口就是:我还能撑多久?
这话听着挺扎心的。不是他能力突然不行了,也不是他不努力了,是业务说砍就砍,团队从200人砍到80人,砍到最后,刀未必只往下面落。
以前大家羡慕大厂高P,觉得那是终点。现在看,可能只是另一种高压锅,薪水高是真的,半夜睡不着也是真的。辉煌的时候一群人围着你,风向一变,连自己都得先问一句:下一个是不是我。
算法题:深度合并两个对象
线上配置被覆盖,最烦的不是报错,是它不报错。
base_config 里明明有数据库超时、重试次数、日志级别,环境配置一合并,某个嵌套字段直接没了。你盯着 JSON 看半天,最后发现用的是普通 update()。
base = {
"db": {
"host": "127.0.0.1",
"pool": {
"min": 2,
"max": 10
}
},
"log": {
"level": "INFO"
}
}
prod = {
"db": {
"pool": {
"max": 30
}
}
}
base.update(prod)
print(base)
这个输出我一般第一眼就不太信。
因为 update() 只看第一层。它看到 db 这个 key 已经存在,就直接把整个 db 替换掉。至于里面的 host、pool.min,它不管。
最后结果会变成:
{
"db": {
"pool": {
"max": 30
}
},
"log": {
"level": "INFO"
}
}
db.host 没了。
这就不是“合并”,这是覆盖。
真正要写的是深度合并:两边都是字典,就继续往下合;只要有一边不是字典,就按右边覆盖左边。
我平时会这么写:
from copy import deepcopy
from collections.abc import Mapping
defmerge_config(base, patch):
result = deepcopy(base)
for key, patch_value in patch.items():
base_value = result.get(key)
if isinstance(base_value, Mapping) and isinstance(patch_value, Mapping):
result[key] = merge_config(base_value, patch_value)
continue
result[key] = deepcopy(patch_value)
return result
拿刚才的数据跑一下:
merged = merge_config(base, prod)
print(merged)
输出:
{
"db": {
"host": "127.0.0.1",
"pool": {
"min": 2,
"max": 30
}
},
"log": {
"level": "INFO"
}
}
这才对。
这里有两个地方我会特别留意。
第一个是 deepcopy。不要直接在原对象上改,尤其是配置、请求参数、默认模板这种东西。你这次改了,下一次调用可能就带着脏数据继续跑,排查起来很恶心。
第二个是列表不要乱合并。
比如:
base = {"allow_ips": ["10.0.0.1", "10.0.0.2"]}
patch = {"allow_ips": ["10.0.0.3"]}
到底是追加,还是覆盖?
这不是算法能拍脑袋决定的,这是业务规则。权限列表可能要覆盖,告警接收人可能要追加,插件顺序甚至还要去重保序。
所以我默认只对字典做深度合并,其他类型直接以后面的为准。规则清楚,问题少。
再补一个现场常用的校验:
defpick(data, path):
cur = data
for name in path.split("."):
ifnot isinstance(cur, Mapping) or name notin cur:
returnNone
cur = cur[name]
return cur
merged = merge_config(base, prod)
assert pick(merged, "db.host") == "127.0.0.1"
assert pick(merged, "db.pool.max") == 30
assert pick(merged, "db.pool.min") == 2
这种断言别嫌土。配置合并这种代码,平时看着简单,真出问题一般都是线上某个字段“悄悄没了”。
递归这块不复杂,复杂的是边界别装聪明。
两边都是对象,继续合。
不是对象,右边覆盖。
列表、字符串、数字、布尔值,别擅自发挥。
这样写,代码短,行为也稳。