打个比方,你在简历上写了“熟悉Python”,但你心里清楚自己到底是能写爬虫、能调API,还是能独立完成一个数据处理管道?“skills”这个项目最初就是冲着我自己的这个困惑去的——我想把“我到底会什么”这个问题,从一份拍脑袋的自我评估,变成一个可记录、可回溯、可查询的事实。做出来的这个东西,本质上是一个自托管的技能管理工具,用命令行交互,核心解决三件小事:技能清单怎么维护、技能之间怎么组织、以及我怎么知道下一步该学什么。如果你也在维护个人知识体系,或者想给团队做一套轻量的技能盘点方案,这篇文章应该能给你一些可以直接抄走的思路。
1. “skills”的由来:被电子表格逼疯之后
1.1 我到底会什么——这个看似简单的问题
2023年年中的时候,我在准备一个内部的技术分享,主题是“数据工程入门”。按说这个话题我并不陌生,但真要列提纲的时候我傻眼了:我发现自己很难说清楚,到底哪些内容算“数据工程”的必备技能,哪些只是我零散接触过的边角料。为了搞清楚这个,我打开了一个电子表格,打算把自己掌握的东西列出来。
结果越列越不对劲。第一版表格长这样:
- Python:会
- SQL:会一点
- Spark:用过
- Airflow:用过一点
- Docker:大概会
这种列表毫无意义。因为“会一点”“用过”“大概会”根本不是可度量的状态,而且它完全没有体现技能之间的内在关系。比如我会写Python,但那和我会用Pandas做数据清洗、会用FastAPI搭个接口服务,是完全不同层级的事情。把“Python”作为一个条目列在表格里,等于把所有粒度混在一起,既不能指导学习,也没法用来评估。
那个表格我修修补补了很多天,最后变成了一个乱七八糟的东西:有些行是这个,有些行是那个,后面还跟着一堆“在XX项目中使用过”的备注。说实话,用电子表格维护技能清单,在技能数量少于20个的时候还能凑合,一旦上了规模,维护成本和查询效率都很糟糕。
也就是那段时间,我决定自己写一个工具来做这件事。“skills”这个名字没什么花头,就是直白地表示这是我个人的技能管理系统。
1.2 现有方案太笨重,我要的是“能坚持用下去”的工具
在动手之前,我其实快速调研过一圈市面上的方案。很多团队会买商业的人才盘点系统,或者用成熟的开源产品搭建内部的技能矩阵(Skill Matrix),这类东西功能很全:有评分、有等级、有部门维度、有岗位映射,甚至还有人才九宫格。但我个人的看法是,这类系统对个人用户来说完全overkill,维护成本极高,你每天要投入的心力会让它在两周之内被弃用。
我当时列了自己的需求清单,核心就四条:
- 记录要快。打开终端敲一句话就能记录一次练习,而不需要打开网页端、找到对应条目、点编辑、选状态、保存——这条路径太长,摩擦一大,人就不会坚持。
- 层级要清楚。技术栈本身是分层的。比如“Python”下面有“异步编程”,而“异步编程”下面又有“asyncio库”和“并发模型”两个子主题。我需要的是一个能表达这种父子关系的结构,而不是一张扁平表格。
- 状态要可变化。技能不是“会”和“不会”两种状态。从没见过、听说过、看过教程、自己写过Demo、在项目里深度用过,这中间至少有五六个阶段。我希望能够记录这种渐进的变化,并且能回看自己走过的时间线。
- 要能回答“下一步学什么”。光记录过去没有用,我更关心未来——我最新记录的技能是哪个方向?哪些技能的前置依赖已经满足了,但当前技能本身还没开始?这些才是真正的学习优先级信号。
基于这四条,我确定了一个最基本的原则:这个工具的数据结构一定要简单,简单到哪怕半年之后我忘了所有代码,也能在十分钟内看懂数据模型。“skills”在很长一段时间里只有三个核心概念:技能(Skill)、状态(Status)、依赖(Dependency)。在此基础上,我再逐步加上了记录(Log)和复盘(Review)两个维度。
2. 核心数据模型设计:把“会一点”变成可度量的状态
2.1 技能实体:一个技能只做一件事
“skills”里最基本的数据实体是技能。一个技能条目保存了以下几个字段:唯一Id、名称、所属分类、推荐的学习资源、以及一段简短备注。在最初的设计里我还放过“掌握程度百分比”,后来删掉了。原因很简单,百分比是个伪精度——你说自己“70%掌握Python”是什么意思?这个数字既无法验证也没有指导意义,它只能制造一种虚假的掌控感。
那为什么有些技能管理工具坚持用百分比?因为做报告好看,可以画仪表盘。但如果使用者是自己,你应该问的问题不是“我掌握到百分之多少了”,而是“我目前处于哪个阶段”。阶段要比百分比诚实得多。
技能的粒度也是一个反复调整过的地方。最开始的思路是“技术栈名”做条目,后来发现粒度太粗,根本没法表达真实情况。“Python”下面应该拆出“语言基础、标准库、异步编程、类型注解、包管理、虚拟环境、性能优化”这些子条目。而反过来,过细也会出问题。我当时一度把“Python的列表推导式”单独建了一个技能条目,结果就是技能库膨胀到几千条,每条都很小,维护起来极其繁琐。
后来我停用了这套做法。我的经验是:一个技能应该对应“一个能够独立练习30分钟以上的主题”。比这个更小的事情,比如列表推导式这种语法特性,应该作为这个技能下的“要点笔记”,而不是一个独立技能。这样技能库的规模基本能控制在几百条的量级,既足够细,又不会碎。
2.2 技能状态:从unknown到mastery的五段式设计
接下来说状态设计,这一块是这个项目里改得最多、也最值得说清楚的地方。技能状态我没有做成简单的布尔值,而是采用了五段式:unknown、learning、practicing、proficient、mastery。
这套设计的出发点其实很朴素:技能成长是一条渐进线,一个数据模型如果要诚实反映这个渐进过程,它至少要有五档。下面是每个状态的具体含义:
| 状态 | 和日常理解的对应关系 | 触发方式 |
|---|---|---|
| unknown | 还没听说过,或者只是听说过名字 | 新建技能时的默认状态 |
| learning | 看过教程,跟着做过,但没法独立完成 | 通过skills note记录一次学习 |
| practicing | 能在无参考的情况下完成基本操作,但需要反复试错 | 连续多次学习和自测后,手动升级 |
| proficient | 能在真实项目里独立交付,不用查文档就能完成常规任务 | 一次完整项目交付后,手动升级 |
| mastery | 能给别人讲解、能指导他人、能基于它做创新 | 有过分享或教学经验后,手动升级 |
你可能会注意到,除了从unknown到learning是自动触发的,其他状态变化都需要手动确认。这看起来像个缺点,但实际是个刻意设计的好功能。自动判定等级听起来很智能,但你必须预设一整套行为规则来支撑它,比如“学习超过多少次就算practicing”——这种规则的误差大到没有意义。手动升级虽然需要使用者多一步操作,但这个过程本身强制你回顾和反思,而反思才是技能管理的价值所在。
在实际使用中,大多数长期“learning”状态的技能,往往不是因为你学不会,而是因为缺少一次真实项目的历练。技能状态不升级,其实是数据在向你提个醒:这个技能你没有用过,它还没有变成你的东西。这个信号对规划下一步学习方向极有参考价值。
2.3 依赖关系:前置知识是学习顺序的地图
说完了技能本身,再说说技能之间的关系。“skills”里技能之间允许存在两种关系:前置依赖(prerequisite)和相关关系(related)。前置依赖很好理解,它表示“想学B,必须先对A有至少practicing级别的掌握”。比如“异步编程”的前置依赖是“Python基础语法”,“FastAPI”的前置依赖是“Python异步编程”和“HTTP基础”。
相关关系则更松散,它表示“这两个技能在实践中经常同时出现”。比如“Docker”和“CI/CD”互为相关,“Pandas”和“SQL”也是常见搭配。相关关系不参与学习顺序的计算,但在查询的时候,它会帮你发现技能组合的可能性——有时候你单独看两个技能都平平无奇,把它们摆在一起会突然生成一个新的应用场景。
为什么要特别设计依赖关系?因为它直接服务上面提到的第四个需求:回答“下一步学什么”。如果没有依赖关系,技能列表就是一堆孤立点,你不知道从哪儿下手。有了依赖关系,你可以在技能图上做拓扑排序,轻易找到那些“前置条件已满足、但自己还没开始”的技能点。这些点才是性价比最高、最应该接着学的地方。
我还做了一个补充数据结构叫“技能组”。这个设计是后来加的。因为在实际使用中,我发现有些技能天然属于同一个知识域,比如“数据可视化”可以拆成Matplotlib、Seaborn、Plotly、Tableau,以及配色和图表设计规则。但它们彼此之间不是依赖关系,而更像是同一棵树的枝丫。于是我用技能组的维度来聚合它们,查询时可以从组的角度看整体覆盖度,也可以在组内看单点状态。这个抽象在后来做学习复盘时非常好用。
3. 关键技术实现:命令行交互、JSON存储与复盘算法
3.1 技术选型:为了长久维护,我选了最无聊的方案
讲完数据模型,说一下落地。这个项目的技术选型毫无惊喜感,模块相关技术上没有用数据库,文件存储上用的是JSON格式,交互端是命令行。有读者可能会问:为什么不用Postgres?为什么不上个Web界面?为什么不用现代前端框架?
我的回答是:这是一个个人使用的工具,核心诉求是长期低摩擦地维护,而不是做成产品。
先聊存储。JSON文件存储确实有天然缺陷——并发写入有问题、数据量大了查询会很慢、没有索引。但“skills”的数据量是什么量级?一个活跃开发者精心维护两年,技能条目数大概也就几百到一千条,记录数会多一些,大概几千条。这个量级下JSON文件的读写速度是毫秒级别的,根本感觉不到性能瓶颈。反而用数据库会引入新的摩擦:你得维护数据库实例、处理迁移版本、考虑备份策略。这些成本对个人工具来说太高了。
JSON的另外一个好处是透明的可迁移性。假设有一天我不再用这个工具了,我获得的不是什么锁定的专有格式,而是几个干干净净的JSON文件。到任何时候,我都随时可以用Python脚本把它们转成任何别的结构。这一点对我来说很重要——“skills”是为我的技能数据服务,而不是让我的技能数据为“skills”服务。
命令行作为交互界面,很多开发者会不习惯,但我想说,恰恰是命令行让这个工具能够长期使用。打开终端输入一条命令,比打开浏览器、等待加载、点击菜单、找到条目再编辑,整个过程快十倍。更关键的是,命令名就是你的操作语言,敲多了肌肉记忆会自动形成,用起来跟呼吸一样自然。
我做了下面这些CLI子命令,覆盖日常全流程:
skills list [--group 分组名] [--status 状态]:按维度列出技能,支持按组和状态过滤skills add [名称] [--group 分组名] [--dep 前置技能Id]:新增技能,同时声明依赖关系skills note [技能Id]:给指定技能追加一条学习记录,自动打开系统编辑器skills review:启动每周复盘交互流程,按依赖优先级展示学习建议skills dot:导出技能依赖图为DOT格式,供Graphviz渲染成可视化图谱
3.2 技能树的查询与可视化:让依赖关系“看得见”
查询是“skills”最常用的操作入口。list命令支持按组、按状态过滤,也可以两个条件组合。但是我用得最多的功能其实是一条结果排序规则,这个必须好好说,因为它直接决定了“skills”好不好用。
常规的排序可能是按名称、按创建时间、按状态排序。我的“skills”排序逻辑是:先看依赖完成度,再看最近活跃度。具体来说,一个技能如果它的所有前置依赖都已经达到proficient状态,而它自己还没开始,系统会把它标记为“ready to start”。这个列表永远排在所有查询结果的最前面。
这个排序逻辑的实现并不复杂:查询时加载所有技能和依赖关系,对有依赖的技能做一次遍历,检查每个前置依赖的状态是否满足阈值,然后生成一张“是否就绪”的布尔表。这份就绪表的价值怎么强调都不过分,因为它等于把你的技能库变成了一张带方向的学习地图,你永远能一眼看到自己下一步可以往哪走。
可视化方面,我一开始用了一个简化版的Graphviz渲染,输出为SVG,用Graphviz是很老的技术,2010年之后的开发者可能都不用它了,但它的DOT语言描述依赖图非常合适,渲染效果也足够直观。每个技能节点根据状态着色:unknown是灰的,learning是蓝色,practicing是绿色,proficient是深绿,mastery是金色。有了这张图,你可以直观看到自己的技能版图全貌,以及哪里有大片灰色——大片灰色意味着一个你一直没启动的领域。
不过说实话,可视化视图我用的频率并不高。因为技能图谱在技能数量少的时候看着很爽,一旦上了规模,比如超过100个节点,整张图就成了一团乱麻,密集的连线让你什么都看不清楚。所以我后来做了一个限制:只在单一技能组内进行依赖图渲染,跨组的关系默认隐藏,需要点开节点才能看到全部关联。这个限制看起来降低了灵活性,却把技能图的可读性拉高了非常多。
3.3 复盘算法:用依赖图自动算“下一站”
复盘(review)功能是这个项目里最能体现数据模型价值的部分。它的本质是一个算法计算:给定你目前所有技能的状态,以及技能间的依赖关系,系统应该告诉你——你现在最应该把时间花在哪几项上面。
这个算法的逻辑很简单,一共只有三步。
第一步,提炼“已就绪但未开始”的技能集合。条件是:自身状态是unknown或learning,且所有前置依赖技能的状态至少是practicing。这个集合就是你的“候补学习区”。
第二步,从候补学习区里筛出“高杠杆技能”——也就是被其他多个技能依赖为前置条件的技能。比如“Shell脚本”和“版本控制”这种,虽然它们本身不算高深,但很多下游技能都依赖它们。先学这种,等于一次性为未来多个技能打开了入口,投入产出比极高。
第三步,结合最近活跃度做最终排序。如果你最近在某个技能上有连续好几条学习记录,系统会倾向提示你在这个方向继续深入,因为学习有延续性,中断一个进行中的主题去开新方向,如果不是策略性切换,多半是在浪费时间。
每次跑skills review,输出会分为三个区块:下一站推荐、正在冷却的主题、持续拖延的灰色地带。这个划分给了我一个非常清晰的行动指南。特别是“正在冷却的主题”这一项——它找出那些状态是learning但已经超过30天没有新记录的技能,提醒你是时候决定一下:要么把它放下,要么给它分配时间把它推到practicing。这个机制帮我处理了非常多“学过一点但永远没学完”的悬空技能。
4. 实战踩坑记录:数据迁移、技能粒度与坚持使用的设计
4.1 从Excel迁移到“skills”时丢掉的东西
复盘算法再准确,也得有足够好的数据喂给它。我在把最初那套Excel表格迁移到“skills”的过程中踩了不少坑,其中有一个坑最有代表性,就是我在Excel里存了很多隐性的“技能状态”。你可能以为Excel里的“备注”列只是文字,但它实际包含大量状态信息:“上周在公司搭过环境”“读完了第一本参考书”“给别人讲过这个”。这些信息如果用一个“备注”字符串存着,就意味着它们无法被程序处理。
迁移时我不得不把所有备注重新阅读一遍,人工判定该把它们对应到哪个状态档。这件事情花了整整一晚,但它也让我意识到:信息的结构化程度决定了它的复用价值。如果一开始就用五段式状态管理,那这些信息就已经是数据了,迁移时只需要复制即可,完全不需要人肉判断。
这次迁移的教训反过来也验证了“skills”的一个设计理念:状态必须单独作为一个字段存在,而不能隐含在文本备注里。对于任何技能管理工具,这一点都是底线。
4.2 技能粒度的反复拉扯:从几百条涨到两千条再砍回来
技能粒度问题是整个项目里最反复的一个设计决策。在项目早期,我把“Python基础语法”“Python内置函数”“Python字符串方法”分别建成了三个独立技能条目。这么做的结果是:技能库条目数很快就突破了2000,而且每天要做的更新量非常大。我发现自己每天维护技能库的时间比真正学习的时间还长——这明显本末倒置了。
于是我做了一次痛苦的瘦身。所有“语法层面”的细节技能全部降级为“要点笔记”,挂载在上层技能下,不再作为独立技能。瘦身之后,技能库大概稳定在400条左右,每次记录也快了很多。
我后来总结了个经验。技能条目划分的三个原则:
- 可独立练习性:能否单独花30分钟以上练习这个主题?如果能,它可以是一个技能;如果不能,它只是技能下的一个知识点。
- 可验证输出:这个技能练完之后,你能不能产出一个可展示的东西,比如一段代码、一个页面、一个部署好的服务?如果学完什么也产出不了,说明描述过于抽象。
- 长期复用价值:这个技能管理的是长期存在的能力,还是一个一次性任务?比如“了解某次会议的讨论结论”就不该是一个技能,因为它没有可复练性。
4.3 摩擦感是最大的敌人:所有设计都要为“坚持记录”让路
我身边很多朋友也尝试过做个人知识管理,但几乎都在两三周内放弃了。问起来原因都高度一致:“太累了”“老是忘了记”“两三天没记就彻底断了”。所以在这个项目里,“降低记录摩擦”几乎是压倒一切的设计目标。
为此我做了几个取舍。
第一,学习记录默认打开系统编辑器,而不是让我输入标题、标签、时长这些元数据。很多工具让你记录一次学习要填五个字段,这个摩擦我完全无法接受。系统编辑器打开后,我想写什么就写什么,想写多少就写多少,关闭后全部保存。字段能自动生成的绝不让用户填。
第二,升级操作允许模糊匹配。用Id查询技能的确严谨,但要先查Id才能操作,这就多了一步摩擦。所以我同时支持名称模糊查找。终端里输入skills note 异步,系统会列出名字包含“异步”的所有技能让你选。这看似是个很简单的体验优化,实际使用频率极高。
第三,每周复盘由一个固定流程驱动,而不是全靠自觉。人类靠自觉记录一定是三天打鱼两天晒网,有固定节奏的复盘才能沉淀数据。我每周日晚固定跑一次skills review,按照系统推荐的结果更新状态、补充记录。这个仪式感的东西在后期成为了整个工具最有价值的部分,因为它强迫我每周花20分钟认真看待自己的技能版图,而不是闷头前进。
5. 这个工具真正解决的问题:从实战经验中看技能管理的价值
回头来看,“skills”对我真正的价值其实超越了一个“记录工具”。它更多是让我重新理解了技能管理这件事本身。
先说技能与岗位的映射。在个人使用中,我越来越觉得技能管理不应该是“我有什么技能”的单向列表,而应该是“岗位要求什么技能”与“我有什么技能”的不断对比。我在“skills”里给每个技能增加了一个可选的“岗位目标标签”。比如当我浏览一个后端工程师的岗位描述时,我会把它要求的技能标签化,然后和“skills”里的当前状态做对比。这个对比的结果往往比任何自我感觉都准确——它直接告诉你差距在哪里,差距有多大。
我在实际使用中还尝试过用“skills”给团队做技能盘点。做法是把每个人的技能导出为JSON,汇总到一起,写一个脚本做交集与差集。这样能够很直观地看出团队的能力盲区,比如三个人都会Python但没有一个人用过消息队列,那你们接下来做事件驱动架构的项目就有技术风险。这个用法没有额外开发任何功能,纯粹是数据透明带来的附加收益。
再说这个工具的边界。有人可能会问:它能不能替代正式的项目管理系统?当然不能。它不追踪项目进度,不算工时,不管理任务分配。它做好的只有一件事:让技能状态可记录、可比较、可回溯。它服务的是“学习和成长”这个维度,而不是“交付与协作”这个维度。这两个维度虽然在很多场景下会交叠,但底层的数据逻辑是完全不同的。
最后聊聊这个项目后续可能的扩展方向。我个人最想尝试的是把技能状态与真实项目的提交记录打通。比如我的某个技能在Git仓库里有对应的代码改动记录,那么状态升级就不光依赖手动确认,还能有客观提交数据做参考。这会最大程度减少状态评估里的主观偏差。另一个想法是做一个简单的年轮图——把每年技能的增量和状态变化渲染成一张环形图,年底回看时,你一眼就能看到这一年到底“长”在了哪里。
“skills”目前依然是一个个人项目,代码不算精巧,文档也不算完备。但如果你问我会不会重新写一版核心逻辑,我不会——因为它的数据模型设计和交互哲学已经足够稳健。这个项目教会我的最重要的一课是:好的个人工具,不是功能最全的那个,而是能让你在一年之后依然愿意打开它录入一条数据的那个。一切设计都应该为这个目标服务。