为什么先聊指标定义

做工程师画像这件事,听起来挺高大上,真动手第一道坎就是指标怎么定。

我们 CoreTeam 的用户画像系统,最早是想给研发团队的能力做量化分析。简单说就是:从 Git 仓库、需求系统、代码评审工具里把行为数据捞出来,多维度分析每个人的编码能力、协作习惯、成长潜力。

但是量化这件事,指标定错,整套系统就废了。这是我最直接的体会。

举个例子,最早我们参考过一些开源的工程师洞察工具,里面清一色只统计”提交次数”和”代码行数”。看起来挺直观,对吧?但其实这两个指标是经典的”反 KPI 教科书”。

只看代码行数的话,一个习惯写水代码的同学一天能给你整出三千行。

只看提交次数的话那就更糟了,有人会拆 commit 拆到上瘾,一个功能拆 20 个 commit,看起来特别勤快。

所以我感觉,指标的定义必须先想清楚它到底反映了什么,再落到字段上。下面这些维度,都是我们在第一版上线被吐槽之后改出来的。

指标不能单一,必须组合

落地的时候,我们把编码指标拆成了四个维度:

  • 提交维度:提交数、提交频率、是否规律
  • 代码维度:新增行、删除行、净改动、影响文件数
  • 协作维度:评审参与、被评审次数、评审意见密度
  • 产出维度:完成需求数、缺陷修复数、缺陷密度

这四个维度互为牵制,单看哪一个都会作弊,组合起来才看得出真实状态。

一个个说。

提交维度:看的不是”多少”,是”稳”

提交次数这个指标,我们后来不那么看中绝对值,真正想看的是节奏感。

1
2
3
4
5
6
7
8
9
# 简化的提交节奏特征抽取
def commit_pattern(commits):
counts_by_day = group_by_day(commits)
return {
"total": len(commits),
"active_days": len(counts_by_day),
"stddev": stddev(list(counts_by_day.values())),
"burst_ratio": max(counts_by_day.values()) / mean(counts_by_day.values()),
}

具体来说我们会看:

  • 活跃天数:一段时间内有提交的天数,比总数更能说明问题。30 天只活跃 5 天的人,跟天天活跃的人状态明显不一样。
  • burst_ratio:峰值日提交量除以日均提交量。如果这个值特别大,基本说明是”赶工型”,平时摸鱼,临近发版狂补。
  • 节奏标准差:标准差小、节奏均匀,说明工作是规划出来的;标准差大,多半是救火型。

这一组指标其实是为了筛掉”突击型选手”。我们认为长期稳定的输出比短期爆发更值钱。

代码维度:净改动比行数更靠谱

代码行数这个事情吧,最容易踩坑。

我们一开始也犹豫要不要纳入”新增行数”和”删除行数”,最后决定都纳入,但权重不一样。

1
2
3
4
5
6
7
8
9
10
11
-- 简化的代码改动量计算(脱敏后的伪 SQL)
SELECT
author_id,
SUM(added_lines) AS added,
SUM(removed_lines) AS removed,
SUM(added_lines - removed_lines) AS net_change,
SUM(files_touched) AS files_touched,
-- 改动量 / 提交数,避免一次大提交刷分
SUM(added_lines + removed_lines) / COUNT(DISTINCT commit_id) AS per_commit_load
FROM git_commit_diff
GROUP BY author_id;

几个关键判断:

  • 删除代码也是贡献。能删代码的人,往往才是真正在重构、在收敛技术债。光会加代码反而危险。
  • 净改动量比纯行数诚实。一个加 1000 行删 800 行的人,跟一个加 200 行的人,工作量完全不在一个量级,但只看”新增行”会被前者刷分。
  • per_commit_load(每提交平均改动行数)是为了防”拆 commit 刷数”。提交数很高但 per_commit_load 特别低的人,基本就是在水。

别人写一行能解决的事,你写了三十行还搞不定,这种”工作量”我宁愿不要。

协作维度:评审才是真功夫

这是我最看重的一个维度。

写代码是个人能力,做评审是团队贡献。一个团队里如果只有一两个人在做评审,那这个团队的代码质量一定好不了。

我们采集了几个字段:

  • 主动评审次数:评论过多少别人的 PR/MR
  • 被评审次数:自己的代码被多少人看过
  • 评审意见密度:平均每个 PR 留多少条意见
  • 评审响应时间:从 PR 提出到收到第一条评审意见的间隔

这里有个反直觉的点:评审意见密度高,反而是好事。说明评审是真的在看代码、在抠细节,不是点个 LGTM 就走人。

当然也得去噪,比如纯 “+1” 或者表情包评论——这种在清洗层就会被滤掉(下篇讲数据管道的时候再展开)。

产出维度:得回到业务

光看代码是不够的。工程师的价值最终要落到业务结果上。

我们把需求系统(Jira 类工具)的数据也接了进来:

  • 完成需求数:一段时间关闭的需求 / 任务数
  • 缺陷修复数:处理了多少 bug
  • 缺陷密度:自己代码引入的 bug 数 / 自己的代码量
  • 需求交付周期:从需求被认领到关闭的平均时长

这一组字段其实最难做干净,因为需求系统的数据质量本身就差。比如有的同学喜欢把所有事都建一个任务,有的同学干了一堆活但就是不建任务。所以我们在归一化的时候,会把产出维度当成参考项而不是排名项,只看异常,不排名次。

排名会逼着人去刷数据,看异常却能发现问题。这是我们在第一版上线后学到的。

一张总表

最后大概长这样(脱敏版):

维度 字段 反映什么
提交 活跃天数、burst_ratio、节奏标准差 工作是否规划、是否稳定
代码 新增/删除/净改动、per_commit_load 真实工作量
协作 评审次数、意见密度、响应时间 团队贡献
产出 需求数、缺陷数、缺陷密度 业务结果

四组加起来大概四十多个字段。字段不算多,但每一个都是反复吵过的。

一点感受

我感觉,做工程师画像最难的其实不是技术上怎么实现,而是怎么定义”好”。

技术栈随便选,Git 解析也好、AirFlow 也好、Vue 看板也好,都是工程问题,工程问题总有解法。但指标定义是设计问题,它直接决定了你这套系统到底是在激励什么,还是在逼着大家演戏。

所以这一篇先把这些定义聊清楚,后面三篇分别讲:

  1. 数据管道:Git 仓库怎么解析、怎么入库
  2. 指标计算:AirFlow 怎么把清洗和归一跑顺
  3. 可视化:Vue 2 看板怎么把异常亮出来

字段定下来之后,下一步就是怎么把数据弄进来。下一篇见。