事情还要从去年的一次团队复盘说起。当时某团队的知识库已经堆了几百篇文档,每个人的技能点却还是靠口口相传来了解。有人数据库写得很溜,但团队里没人知道;有人刚啃完一门在线课程,自我评价畏畏缩缩。我接到的任务是做一个叫 Skills 的小项目,把“谁在什么领域有什么水平”这件事从人肉记忆里解放出来。最初以为只是做一个技能列表,结果越做越深,最后变成了一整套技能记录、成长追踪、证据关联的小系统。这篇文章不聊高大上的架构,就记录我在设计 Skills 时踩过的坑、想明白的道理,以及如果你也想做类似的事情,可以直接参考的数据模型和交互方案。
1. 起底:为什么非要做个叫 Skills 的项目不可
1.1 技能分散带来的管理痛点
我是在一次项目救火之后决定动手的。当时某项目X上线前出现一个数据迁移问题,负责的后端同事临时请假,大家翻了半天通讯录也没找到谁能顶,最后发现另一个部门有个同事半年前写过类似脚本,而且写得相当干净。这件事不复杂,但暴露了一个很现实的问题:团队里的技能信息基本靠“记忆+口耳相传”,没有结构化的地方能回答“谁会这个”。
我把这个问题带回团队,大家的第一反应是“我们不是有知识库吗”。但翻了几遍知识库,里面全是项目文档和技术方案,很少有人把自己的能力边界写进去。偶尔有人在文档下面留言“这块我熟”,但信息零散、没有标准,真要检索的时候根本搜不全。更麻烦的是,技能不是一个静态标签——有人三个月前会某个工具,这三个月一直没碰,到底还算不算“会”?这种动态变化,表格和文档都很难表达。
同理还有新人入职的场景。新人想看团队里谁能带自己,老员工想找搭子做跨领域需求,管理者想判断某个方向的梯队厚度。这些需求背后都是同一个问题:技能信息没有被当成数据来管理,而是散落在各种聊天记录和文档标题里。所以 Skills 并不是一个锦上添花的小工具,而是被真实痛点逼出来的。
1.2 市面方案为什么没直接拿过来用
动手之前我先看了一圈已有的工具。市面上不是没有技能管理类产品,但大多偏向人力资源管理,把技能当成员工履历的一部分,主打“能力评估”“岗位匹配”。这类东西对上百人的公司可能有用的,但对一个二三十人的团队来说,配置成本和维护成本都偏高,而且字段往往是预设死的,想加一个“最近一次使用时间”都得绕路。
另一种是学习平台自带的技能矩阵,通常跟着课程走,你学完一门课它就帮你点亮一个技能点。这种思路的问题在于,技能不能等价于“学过某门课”。真正支撑一个技能水平的,是实际项目中的产出、踩过的坑、写过的代码或做出的作品。课程只代表输入,不代表输出。我们想要的是让技能和“证据”挂钩,而不是跟“课程”挂钩。
还有一个很现实的因素是预算和效率。为一个内部小工具走采购流程,时间可能比自建还长。我算了下工作量:核心功能其实就是一套技能树维护、若干条用户记录、一个搜索接口、两个页面。用团队熟悉的 Python 后端加一个轻量前端框架,两周内就能出可用版本。“自建”就成了顺理成章的选择。现在回头看,这个判断是值得的,因为只有在自建过程中,你才会被迫想清楚“技能”到底应该怎么建模,而这恰恰是买现成工具永远学不到的东西。
2. 需求收敛:Skills 第一版到底解决哪几件事
2.1 从“技能列表”到“技能档案”的认知转变
最初的构想特别简单:做一个技能标签云,每个人给自己加几个标签,点击标签能看到有哪些人。原型画完,我立刻发现一个尴尬的问题:标签只能回答“有/没有”,回答不了“水平怎么样”。同一个“Linux”标签,可能属于能熟练排查故障的人,也可能属于刚装过虚拟机的新手。如果只是这样,那跟在线表格第一列填技能的方案没有本质区别。
后来我把“技能”拆成了三层:技能(Skill)、熟练度(Level)、证据(Evidence)。技能描述“会什么”,熟练度描述“会到什么程度”,证据描述“凭什么说会”。这个三角结构是整个项目最核心的认知,后面几乎所有设计都是围绕它展开的。有了这三层之后,“技能列表”就变成了“技能档案”:一个技能不再是孤立的标签,而是一份可追溯、可讨论的说明。
这个转变也直接影响了数据表设计。如果只做标签,一张人员表和一张技能表就能搞定;但要支持熟练度和证据,就需要引入关联表和证据表,权限和校验逻辑也会更复杂。好在这个复杂度是值得的——没有证据链的技能档案,很快就会变成又一张“自我感觉良好”的评分表,而这不是我做这个项目的初衷。
2.2 第一版功能清单与“不做清单”
第一版功能我控制得比较克制,总共只有四个模块:
- 技能分类与技能节点维护:可新增、合并、停用技能节点,技能默认支持多级分类。
- 个人技能档案:每个人可以在自己的档案页添加技能、选择熟练度、填写最近一次使用时间、关联证据链接。
- 技能找人:按技能名或关键词搜索,列出所有相关条目,并支持按熟练度过滤。
- 个人视图与团队视图:个人能看到自己的成长记录,团队能看到某一技能下的人员分布。
我也列了一个“不做清单”:不做动态社交、不做积分排行榜、不做课程推荐、不做强制定期评估。理由是,社交动态会增加内容运营压力,排行榜会诱导刷数据,课程推荐把技能和课程绑在一起,违背了我们“证据优先”的原则。强制定期评估更是大可不必——当团队规模不大时,自评加证据已经能提供足够有用的信息,真要严肃评估,线下聊两句比填表靠谱得多。
这份“不做清单”后来帮了大忙。需求总是会膨胀的,如果一开始就想着把所有功能都做了,第一版可能永远也出不来。先做最小闭环,让数据跑起来,再根据真实反馈决定下一步。
3. 数据模型与后端设计:技能不是标签,而是有结构的东西
3.1 技能分类、技能项、用户记录与证据:四张核心表
Skills 的表结构不算复杂,但每一张都有存在的理由。核心字段如下:
| 表名 | 字段 | 说明 |
|---|---|---|
| skill_categories | id, name, parent_id | 技能分类树,支持多级目录,parent_id 指向父分类 |
| skills | id, category_id, name, aliases | 技能节点,aliases 存别名,方便搜索时做同义词匹配 |
| user_skills | id, user_id, skill_id, level, last_used_at, updated_at | 人员技能关联表,level 用整数存,约定 1-4 对应了解、熟悉、熟练、精通 |
| evidence | id, user_skill_id, title, url, description, created_at | 证据表,关联到某一条具体的技能记录,用来支撑熟练度判断 |
第一版的时候,我把技能做成了一棵纯粹的单父节点树,每个技能只能属于一个分类。上线后发现行不通,后面会专门讲。除了四张核心表,我还加了一张很小的“技能别名表”,或者直接用 skills 表的 aliases 字段。当时后端用的是 PostgreSQL,所以 aliases 直接存了数组类型,查询时用array_to_string或者ANY来匹配,效果不错。
user_skills 表是整个系统里改动次数最多的表。最初它只有 user_id、skill_id、level 三个字段,后来陆续加了 last_used_at(最近一次使用时间)、source(自评/他评/项目标记)、note(备注)。这些字段不是为了炫技,而是在试用过程中真切遇到的问题:没有 last_used_at,你会发现在三年前用过一次的人也显示“熟练”,完全失真;没有 source,你没法判断这条记录到底是谁填的、可信度几何。
3.2 熟练度分级:为什么用四级而不是五级
关于熟练度,我一开始照着一套常见的五级量表,从新手到专家划了五档。原型拿给几个同事看,反馈非常一致:中间那一档成了大多数人的默认选项。这种“安全选项”看起来很方便,实际上把所有数据都推到了中间,区分度完全丧失。
后来我改成四级:了解、熟悉、熟练、精通。四个选项没有中间值,你必须偏向一边。为了减少“永远选保险项”的冲动,我还在界面上加了描述:
- 了解:听过概念或看过基础教程。
- 熟悉:能在指导下完成日常任务。
- 熟练:能独立完成,并能解决常见问题。
- 精通:能带人、能制定方案、能处理疑难问题。
描述不是摆设,它让不同的人对同一等级有更接近的理解。这里还有一个小设计:升级到“熟练”及以上,必须关联至少一条证据。自评可以写“熟悉”,但想标“熟练”或“精通”,没有实际的代码仓库链接、项目文档或作品地址,后端直接拒绝保存。这一条后来被证明是避免水分最有效的机制。
3.3 接口设计:优先保证“有人能查到”和“我能快速更新”
后端接口我做得非常克制,两个查询接口承载了日常大部分流量:
GET /api/skills/search?q=&level=:按关键词搜索技能,返回相关技能节点和人员列表。GET /api/users/{id}/skills:拿某个人的完整技能档案,包括熟练度和证据。POST /api/user_skills:新增一条技能记录。PATCH /api/user_skills/{id}:修改熟练度、上次使用时间、备注。
字段校验集中在两处:一是 skill_id 必须存在且不是停用节点;二是 level 必须介于 1 到 4,且当 level 大于等于 3 时,证据列表不能为空。这个校验逻辑在数据库层也做了一半,用触发器防止绕过前端直接调接口乱写数据。别笑,真的会有人绕过前端刷“精通”。
整套接口没有引入复杂的分页策略,因为第一版数据量撑死也就几百条记录。我唯一考虑的是搜索的响应速度:技能别名表加了一层模糊索引,中文场景下拉菜单也能有不错的响应。实测下来,即使技能节点涨到几千个,搜索请求基本都能在几十毫秒内返回,完全够用。
4. 前端交互:让“记录技能”这件事足够轻
4.1 个人档案页:修改熟练度必须面对证据
前端我用了一个很轻的方案,只有一个单页应用和两个路由:一个是“我的技能”,另一个是“找技能”。没有刻意做花哨的动效,核心宗旨是让每一步操作都“低成本、低门槛”。
“我的技能”页面按分类展示技能树,旁边列出当前等级和最近使用时间。每一项都有一个“修改”按钮,点开是一个弹窗:等级下拉框、最近使用时间、备注、证据列表(可添加多个链接)。如果用户把等级选到“熟练”或“精通”,而证据列表是空的,弹窗底部会直接红字提示“请至少添加一条证据”,保存按钮置灰。这个交互没有讲任何道理,但试用时大家几乎都理解了为什么必须加证据。
这里有个细节:证据链接填写框刚开始只放了一个输入框,后来发现很多人只丢一个网址,不加标题。数据库里就出现了一堆孤零零的链接,下次自己想不起来是什么。于是我把表单改成了三字段:标题、链接地址、一句话说明。别看只是多两个字段,后期查看别人档案时,可读性提升了一个量级。
4.2 找技能页:搜索不是新鲜事,但结果排序要想明白
“找技能”页面是团队视角的核心入口。顶部一个搜索框,输入“Docker”或“容器”,下面会列出所有匹配的技能节点,以及每个节点下的人员数量。点击一个节点,出现人员列表,并按熟练度从高到低排序,每行都显示“上次使用时间”。
排序逻辑一开始直接用 level 倒序,后来发现不对劲:一个两年前标了“精通”但再也没碰过的人,排在一个上个月还在天天用、只是谦虚标了“熟悉”的人前面,这明显不符合现实需求。于是我改成按“level 优先、last_used_at 次之”的综合排序,并专门把最近三十天内用过的人用一个小标记标出来。实测下来,这个改动让“找对人”的成功率提升了不少。
空状态也做了特殊处理。搜索不到任何技能时,页面不会只写“无结果”,而是列出几个相近技能名,比如搜“K8s”时提示“你是不是想找 Kubernetes”。这部分没有用任何 AI 接口,就是技能表里维护的 aliases 字段,加上一个简单的相似度匹配,已经足够用了。
4.3 为什么没在第一版做雷达图和趋势图
很多同事看到原型后第一个要求是“能不能给我来个雷达图,看看我的技能全貌”。我确实做了一版雷达图,但用了一天就发现问题:当一个人的技能数量只有五六个时,雷达图看起来像一个扁扁的三角形;技能稍微多一点,图形又乱成一团。更关键的是,雷达图只能给人“我好像还行”的错觉,完全无法回答“我接下来该补什么”。它本质上是娱乐功能,不是管理功能。
于是我决定第一版不做任何可视化仪表盘,只保留一个最简单的文本统计:共记录了 X 个技能,其中熟练以上 Y 个。这个数字足够直观,也不会过度解读。后来的经验也证明,技能数据还没到几百条以上时,任何图表都很难产生真正有价值的洞察,先保证数据质量比先做漂亮图表重要得多。
5. 踩坑与重构:试用三轮后改掉的三个设计
5.1 “自评虚高”怎么用证据机制和最近使用时间拉回现实
第一轮试用很热闹,大家把技能表填得满满的,但数据质量让人皱眉。有同事把自己三年前写过一次的语言标成“精通”,有同事把“看过教程”和“能独立开发”混为一谈。此时单纯依靠四级量表已经不够了。
我们紧急加了两个字段:last_used_at和evidence。前者逼着每个人回答“你上一次真正使用它是什么时候”,后者要求熟练以上必须有作品或项目链接。这两个字段上线后,表面数据立刻缩水了一大圈,但剩下记录的可信度直线上升。我甚至看到有人主动把自己从“精通”改成“熟悉”,理由是“证据链不够硬”。
这个经历给我的启发是:任何自评系统都逃不过人性中的乐观偏差,与其靠道德约束,不如靠结构约束。你不需要指责谁虚报,只需要让“虚报”暴露在证据面前。
5.2 技能分类树从单父节点重构为多标签
前面提到,最初技能树是单父节点的目录结构,比如“后端开发”下面有“数据库”,“数据库”下面又有“MySQL”“PostgreSQL”。这个结构看起来很整齐,但很快就出了问题。一个既属于“后端开发”又属于“数据工程”的技能,到底放哪边?一个“爬虫”技能,说是“后端开发”没错,说是“数据分析”也没错。
纠结几个案例后,我把 skills 表的 category_id 换掉,改成独立的关联表,让一个技能可以挂在多个分类下。这个改动让分类从强制的树变成了灵活的标签组织。虽然页面上的展示还是以分类树为主,但数据层已经不再被单父节点锁死。这里我建议所有类似项目都这样做:技能天然是交叉学科,分类树只是浏览视图,不是存储事实。
5.3 团队真正高频使用的不是“个人档案”,而是“按项目找人”
统计试用日志时,我又发现一个反直觉的现象:大家访问最多的是“找技能页”,而不是“我的技能页”。仔细一想就明白了:对大部分同事来说,这个系统的价值首先是“当我需要某项能力时,能快速找到谁可以帮忙”,而不是“每天上去看看自己会什么”。个人档案填写是一次性的,找人却是日常的。
基于这个反馈,我把前端导航的顺序调整了,默认落地页从“我的技能”换成“找技能”,并在首页直接放了一个全局搜索框。同时增加了“按项目/方向浏览”的入口,某个项目下会列出相关的技能组和对应的候选人。这个改动带来的使用率提升非常明显。它提醒我一个更普遍的道理:一个工具的价值应该定义在用户的“高频动作”上,而不是定义在数据生产者的“低保真填写”上。
6. 如果重新做一遍,我会怎样设计技能管理
6.1 从一开始就把“证据链”作为一等公民
如果把 Skills 推倒重来,我第一个会改的,是把证据表从“可选项”变成“必需项”。不是只限制“精通”必须证据,而是所有等级都应该尽量关联一条证据——哪怕只是“某年某月在某个项目里用过”。这和写简历一样:没有具体产出支撑的技能表述,本质上只是自我认知,不是可被他人验证的能力。
具体实现也很简单:新增一条 user_skill 记录时,允许先不填证据,但系统会标记为“未验证”;达到熟练及以上时必须补证据;每个月可以给“长期未更新”的记录发一条提醒,让用户更新 last_used_at。这些机制不复杂,却能把数据的生命周期延长很多。
6.2 不只盯住“高手”,也要看见“正在爬坡的人”
最后一次复盘时,有人提了一个很好的问题:这个系统是不是只关心“谁会”,而不关心“谁正在学”?换句话说,一个刚入门但热情很高的人,在技能分布图上永远是最底层,很容易被忽略。但论团队潜力,他可能比很多停滞不前的“熟练者”更值得关注。
如果重做,我会在个人档案加一个“成长中”标记,或者单独维护一个“想学/在学”的技能列表。它不是正式技能记录,不会进入团队找人的结果里,但会展示在个人主页上,也能让管理者看到团队的学习方向。这个想法来自一次真实的对话:有个同事默默学了三个月可视化,没人发现,直到他主动拿出一个项目才被注意到。如果系统能提前一个月展示“他在学什么”,很多机会也许就能更早对接上。
6.3 用数据做自我审视:技能接口和 UI 都是次要的,重要的是定义清楚“技能”这个概念
说了这么多,Skills 最后其实是一个很小、很朴素的系统。代码量不大,没有分布式,没有缓存,也没有复杂的算法。但它在做的事情,是帮一个团队把“看不见的能力”变成“可检索、可讨论、可成长”的数据。这个转换的价值,随着团队人数增长会越来越明显。
我个人在实际操作中的体会是:做技能管理,最难的不是写代码,而是说服自己接受“技能可以被程度化地描述,但永远不可能被精确量化”。四级熟练度、最近使用时间、证据链接,所有这些都只是逼近真相的辅助线,不是真相本身。明白了这一点,你就不会迷信某个数字,而是会倾向去看数字背后的具体项目、代码和成果。这也是一个技能管理工具最值得投入的地方——帮人建立对“技能”的诚实认知,而不是造一个漂亮但空洞的排行榜。
最后再分享一个小技巧:如果你也想在团队里做类似的事,别一上来就铺开做全量录入。找两三个不同岗位的同事,先手工用表格跑两周“谁找谁帮忙”的场景,把真实发生的求助问题和解决时间记下来。当你积累了三五十条真实记录之后,这个系统该设计哪些字段、哪些筛选、哪些排序,答案自己会浮出来。这也是我做 Skills 项目最大的收获:不要先造工具再造需求,要让需求自己把工具的骨架撑起来。