1. 为什么会想写一个技能管理工具
1.1 一次复盘让我发现自己的"技能底牌"是模糊的
事情的起因大概是半年前的一次季度复盘。当时我在梳理自己这一年到底积累了哪些能力,脑子里的第一反应是"我好像什么都会一点,又好像什么都不够硬"。这种模糊感在面试、转岗、规划学习方向时尤其致命——你说你熟悉容器化,但让你把Kubernetes的调度原理讲清楚,你能讲几分钟?你说你了解前端工程化,那Webpack的构建链路你调过几条?
我试过用表格记技能,试过在备忘录里列清单,试过在网盘里存各种课程证书。结果都一样:记的时候热血澎湃,过两周就荒废,因为那个清单没有结构、没有量化、没有行动指引。说白了,它只是一份"自我感觉良好名单",不是能支撑决策的技能底牌。
所以就有了skills这个项目。它不是笔记软件的插件,也不是那种大而全的人才测评系统,而是一个跑在终端里的、用Python写的个人技能管理工具。它解决的核心问题有三个:让你随时看清自己的技能结构、让你诚实评估熟练度、让差距自动变成可执行的学习计划。
这个工具我断断续续写了三个月,中间推翻过两版数据结构,现在每天至少用一次。这篇文章把我完整的建模思路、代码设计、踩坑经历整理出来,给同样想"看清自己手里到底有什么牌"的朋友一条可以直接照做的路径。
1.2 市面现成方案的痛点:要么太重,要么太假
在动手写之前,我先研究过现成的技能管理方案,大致分两类。
第一类是轻量清单类——比如Excel表格、Notion数据库、各种TODO应用里的技能标签。它们的问题是:数据完全靠手填,没有任何评估引导,今天心情好给自己打个5分,明天焦虑了又改成3分,评分标准来回漂移。而且维度单一,只有"技能名+分数",没有技能之间的层级关系、没有证据记录、没有时间维度,根本支撑不了"我现在该学什么"这个核心问题。
第二类是正规测评类——比如企业级的人才盘点系统、胜任力模型平台。这类东西设计严谨,岗位模型、行为锚点、雷达图都很完善,但个人用起来太笨重。你需要先搭建岗位能力模型,再邀请同事给你做360评估,再等系统生成一份四十页的报告。跑完流程至少要一周,对于个人开发者或者独立从业者来说,这个成本根本不可接受。
我真正想要的,是一个介于两者之间的东西:轻量得像记事本,但内部数据结构要足够严谨,能支撑"差距分析"和"学习规划";评估标准要有稳定锚点,不至于今天和明天打分口径不一样;最好还能留证据——每个分数背后都有一个具体产出物支撑,而不是纯感觉。市面上没有这样的工具,那就自己造一个,这就是skills项目的起点。
1.3 设计原则:不做App,不做云同步,先做本地CLI
我给自己定了三条设计边界,防止项目失控。
第一,不做图形界面。技能管理是高频短时的行为,每天打开终端敲一条命令,比打开App点击七八次更快,而且CLI天然就是结构化数据的入口。Rich库可以把终端输出做得足够漂亮,根本不需要前端。
第二,不上云、不搞多人协作。这个工具的核心用户就是我自己,如果用云端同步,就要考虑账号、数据库、服务器,复杂度至少翻三倍。数据就存在本地目录下的YAML和SQLite文件里,用Git管理版本,天然免费且可控。
第三,优先保证"输入体验"。很多个人工具的失败在于录入太痛苦,所以我花了大量精力在skills check这个交互命令上——它要像答辩一样逐项引导你打分,而不是甩给你一张空白表让你自己填。后面我会具体讲这个设计。
2. 技能树的建模思路:从清单到三层结构
2.1 第一版教训:扁平清单根本不可维护
我先说一段黑历史。skills的第一个版本数据结构极其简单,就是一个技能名对应一个分数:
skills: - name: Python level: 4 - name: Docker level: 3 - name: Kubernetes level: 2 - name: 正则表达式 level: 4用了一周就发现问题了。首先是粒度混乱:"Python"这个技能太宽泛,我可能异步编程很熟但对类型系统一窍不通,一个4分根本表达不了这种差异。其次是没法做差距分析——我想规划"三个月后能独立带队做项目",但"Python"从4分涨到5分这件事,和我真实的能力短板(比如系统设计)之间没有任何可操作的连接。最要命的是,这种扁平结构会不断膨胀,今天加一个"Redis",明天加一个"Nginx",后天再看这个列表,根本不知道先从哪一行开始看。
那次重构给我一个重要教训:技能数据如果没有层级关系和统一粒度,分数成长线就只是一串自我安慰的数字。
2.2 领域/能力组/单技能三层模型
我把技能树改成了三层结构。
- 顶层是领域,代表一个你在职场或作品中会被人问到的能力方向,比如"后端开发""前端工程""团队协作""写作表达"。
- 中间层是能力组,把领域下的技能按目标逻辑分组。比如"后端开发"下面有"语言基础""工程化实践""高并发设计""数据存储"。
- 底层是单技能,它必须满足一个条件:能在两周到一个月内通过学习或实践产生可感知的进步。
用这个模型,前面那条完全没法用的清单就变成了这样:
domain: 后端开发 groups: - name: 语言基础 skills: - name: Python类型注解 - name: asyncio并发模型 - name: Python C扩展接口 - name: 数据存储 skills: - name: MySQL索引优化 - name: Redis持久化策略 - name: 消息队列选型关键是第三层单技能的粒度。拿"Python类型注解"举例,它可以在一个月内通过写一个包含Protocol、TypeVar、overload的小项目来验证进步;但"Python"不行,因为你没法定义"练到什么程度算进步"。
2.3 YAML定义技能树:字段设计与扩展性考虑
我最终采用的YAML schema在原有三层基础上加了几个关键字段。一个完整的技能条目长这样:
- name: MySQL索引优化 anchor: index-optimization group: 数据存储 domain: 后端开发 description: | 理解B+树结构与索引失效场景, 能针对慢SQL进行explain分析与索引重构 level: 3 evidence: - 2025-03 某订单查询接口从800ms优化到50ms - 2025-04 Node客户列表分页深翻页问题修复 next_action: 研究覆盖索引在排序场景中的应用字段设计时我反复斟酌过的有四个:
anchor是技能在系统内的稳定ID。技能名可以改,但锚点不能变,这样历史数据才能关联上。description不是摆设。它用一两句话限定这个技能的边界,防止"我以为的打分标准"和"实际的能力定义"之间产生漂移。level是当前熟练度,取值来自Dreyfus五级模型,这个我后面专门讲。evidence是证据列表,每一条都要能说清楚"做了什么、产出什么、解决什么"。
如果你从这个清单里看不到一个叫"Python"的技能,别奇怪,这是三层模型刻意为之的。真正在面试中帮到你的,是"asyncio并发模型设计过一个限流中间件"这种含金量的颗粒度。
3. 核心功能拆解与CLI设计
3.1 命令设计理念:查询和修改彻底分开
skills的CLI从一开始就遵循一个原则:读操作和写操作分离。我见过很多工具为了减少命令数量,让skills list既能查列表又能改分数,最后界面一堆flag,心智负担极重。
我的做法是两套命令空间:
| 动作类型 | 命令 | 作用 |
|---|---|---|
| 查询 | skills list | 查看技能清单,支持按领域/能力组/熟练度筛选 |
| 查询 | skills tree | 输出整棵技能树的结构 |
| 查询 | skills report | 生成Markdown格式的分析报告 |
| 修改 | skills entry add | 新增一个技能 |
| 修改 | skills check | 引导式评估,更新熟练度 |
| 修改 | skills evidence | 给指定技能追加证据 |
| 分析 | skills plan | 基于缺口自动生成学习计划 |
每个命令都只做一件事,参数最少化。比如skills list的过滤能做到skills list --domain 后端开发 --min-level 4这种程度,但绝不提供"顺带改分数"之类的隐藏功能。
3.2skills list:让技能树在终端里一目了然
这个命令是日常使用频率最高的。我依赖Rich库实现了一个带颜色的树形输出:顶部显示领域,下面缩进显示能力组,最底层是技能名、熟练度色块和最近证据时间。
# 简化版:核心输出逻辑片段 from rich.tree import Tree from rich.table import Table from rich.console import Console console = Console() def render_skill_tree(tree_data): root = Tree("🗂 我的技能树") for domain, groups in tree_data.items(): domain_node = root.add(f"[bold]{domain}[/bold]") for group_name, skills in groups.items(): group_node = domain_node.add(f"[cyan]{group_name}[/cyan]") for skill in skills: level_mark = "█" * skill["level"] + "░" * (5 - skill["level"]) group_node.add(f"{skill['name']} [green]{level_mark}[/green]") console.print(root)输出效果大致类似这样:
🗂 我的技能树 └── 后端开发 ├── 数据存储 │ ├── MySQL索引优化 [█████░░░░░] │ ├── Redis持久化策略 [███░░░░░░░] │ └── 消息队列选型 [██░░░░░░░░]熟练度用5格色块表达,扫一眼就能看出哪块比较薄弱。这个命令解决的核心问题是:每当你迷茫"我接下来该干嘛"的时候,技能树的视觉短板会直接告诉你答案。
3.3skills check:答辩式打分,治标也治本
如果只给一个懒人接口,那必然是skills check。执行后它会逐个取出尚未达到满分(5级)的技能,用交互问答的形式引导评估:
> 开始新一轮技能评估:当前需要评估 12 项技能 > [3/12] MySQL索引优化(当前等级:3) > 最近一次实际应用的产出物是什么?请输入关键词或留空: > 回答:某订单接口慢SQL优化 > > 根据以下5级描述,诚实评估当前熟练度: > [1级] 能在他人口述指导下完成固定操作 > [2级] 能独立解决常见问题,但某些边界场景需查阅资料 > [3级] 能系统性解决问题,并理解底层原理 > [4级] 能指导他人,并沉淀方法论 > [5级] 能创造新方法,并影响团队技术方向 > 你的选择(1-5):这里最关键的设计是:强制你先回忆最近一次实践证据,再打分。没有证据就不准打分。这一步把"凭感觉打分"硬生生扭成了"凭事实打分",是避免自我欺骗的核心机制。
我建议这个环节保持"每题必答"的强制力,虽然多花十几秒,但打分质量的提升非常明显。后面我会用数据说明自我评估偏差到底有多常见。
3.4skills plan:差距分析生成三个月学习计划
skills check完成更新后,skills plan就有了数据基础。它做三件事:
- 找出目标等级与实际等级差距>=2的技能,标记为"短板"。
- 根据技能所属能力组和领域判断优先级——同组多个短板优先处理,核心领域优先于边缘领域。
- 按可投入时间(默认每天1小时)拆分出每周行动项,并写入一个Markdown学习计划。
def generate_plan(skill_profile, target_levels, hours_per_week=7): gaps = [] for skill in skill_profile: target = target_levels.get(skill["name"], 4) if target - skill["level"] >= 2: gaps.append({ "skill": skill["name"], "gap": target - skill["level"], "domain": skill["domain"], }) # 按领域聚合、间隔排序,输出行动计划 gaps.sort(key=lambda x: x["domain"]) return render_plan_markdown(gaps, hours_per_week)生成的学习计划不追求精准到天,而是给一个"本周聚焦清单"。比如这周要做的三件事里,第一件永远是"给差距最大的技能补一个实战项目",而不是"看视频课"——因为只有实战能同时拉升技能等级和补充证据列表。
4. 数据存储与评估模型:Dreyfus五级怎么落地
4.1 为什么选YAML+SQLite双轨存储
skills的数据存储从第一版开始就确立了一个铁律:人类可读为主,机器可查为辅。
所有技能树的定义、描述、证据,都放在一个skills.yaml文件里。原因很简单:YAML我可以用任何编辑器打开、修改、复制、放到Git里做diff,没有任何黑盒。对于个人工具来说,可解释性比性能重要一万倍。
但纯YAML也有两个问题:一是grep跨文件查询能力弱,当技能数量超过100个时,想快速查"有哪些技能低于3级且最近没有证据"就很别扭;二是没有时间维度,历史变更记录难以追踪。
所以我在YAML之上加了一层SQLite,作为只读索引库。写操作仍然先写YAML,然后触发一次skills index同步,将解析结果导入SQLite。查询走SQL,修改走YAML,两边各司其职。
同步逻辑是手动调用的,不是文件监听。个人工具没必要搞常驻进程,一个命令搞定反而更可控。建议每周末跑一次索引同步,这样报告的统计口径是稳定的。
4.2 Dreyfus五级配套行为锚点,避免评分漂移
提到熟练度模型,很多人第一反应是"1到5分打个分不就行了"。但前面提过,没有锚点的评分必然漂移——今天心血来潮给自己打4分,过几天冷静下来又改成2分,数据完全失去可比性。
所以我把Dreyfus技能获得模型稍微改造了一下,每一个等级都绑定了具体行为描述。
| 等级 | 行为锚点 | 典型特征 |
|---|---|---|
| 1级 新手 | 能在他人的逐步指导下完成操作 | 需要看着教程操作,遇到报错会卡住 |
| 2级 进阶 | 能独立完成常规任务,但边界场景需查资料 | 不再需要手把手,但遇到非典型情况会慌 |
| 3级 胜任 | 能系统性解决一类问题,并理解底层原理 | 不只看怎么用,还能解释为什么这样设计 |
| 4级 精通 | 能指导他人、沉淀方法论、复盘复用 | 能把自己的经验结构化传递给团队 |
| 5级 专家 | 能创造新方法,并影响领域方向 | 别人遇到疑难杂症会主动来问,甚至团队的技术规范会参考你的方案 |
这个锚点表被直接编译在skills check的交互逻辑里,每次打分前都会展示一次。你可能觉得这样有点啰嗦,但正是这种反复强化,才把我的打分口径从"我觉得还行"校准到了"对照行为,我确实只到3级"。
这里有一个非常明显的例子:我在"消息队列选型"这项技能上,曾经一年内给自己打过2分、4分、3分三个版本。直到加了锚点之后才发现,我实际能做的只是"在两种常见队列间做比较和选择",完全谈不上"理解底层协议和一致性模型",最终稳定在2级。锚点最大的价值不是让你加分,而是让你诚实地给自己扣分。
4.3 记录"证据"而不只是分数:evidence字段的意义
我在skills.yaml的schema里给每个技能留了一个evidence列表,这句话现在看可能是全项目最关键的决策。
证据不是荣誉列表,而是"最近一次实际应用"的客观记录。我的填写规则是:每次为一个技能打分/更新分数时,必须有至少一条证据,并且证据要能回答三个问题——我在什么场景用过、我做了什么、产出或结果是什么。
比如:
- name: MySQL索引优化 level: 3 evidence: - 2025-03 某订单查询接口慢SQL定位与复合索引重构,耗时从800ms降到50ms - 2025-04 分页深翻页场景下改用搜索方案,解决offset过大性能问题这两条证据一摆出来,任何看到这份数据的人都能立刻理解"这个3级是实打实的"。证据还有一个隐蔽的价值:当你想要进步到下一级时,你会自然地去寻找"还缺一个什么样的项目来证明我到了4级",这就把一个抽象的熟练度目标变成了一个具体的实操待办。
5. 可视化报告:把技能底牌摊开给自己看
5.1 终端里的技能热度图:0到5分的色块映射
光有树形列表还是不够直观。技能树告诉你有短板,但你想知道"整个能力结构的健康状况"是什么样,热度图更合适。
skills heatmap命令输出一个按领域分组的矩阵:行是单个技能,列是周序号,格子颜色从红色到绿色表示该周该技能是否有实践/学习记录,以及熟练度变化。这个图在终端里就是一块用█字符拼成的色块墙,我能很直观地看到过去六周里哪些技能区域一直没被触碰。
def render_heatmap(records, weeks=6): # records: [{ week, skill, delta_level, has_practice }] from rich.console import Console console = Console() header = "技能名称 " + " ".join([f"W{weeks - i}" for i in range(weeks)]) console.print(header) for skill in skill_list: cells = [] for w in weeks: record = records.get((skill, w)) if record and record["has_practice"]: cells.append("█") # 用真色块 else: cells.append("░") console.print(f"{skill:<12}" + " ".join(cells))这功能一周跑一次就够了。它的核心洞察和代码量关系不大,但极其好用:当你严重偏科时,热度图上某个领域会连续几周全灰,这个视觉冲击比任何总结表格都强烈。
5.2 导出Markdown周报:给未来的自己看
热度图偏向即时感知,周报则偏向历史留痕。skills report会把当前技能快照和改动记录渲染成一份Markdown文档,我在每周日晚上生成一份,放Git仓库里的reports/目录。
Week report的内容包括:
- 本周新增技能数与总技能数
- 当前每个领域的平均熟练度、最大短板
- 本周实际完成的实践项目清单(来自evidence)
- 下周推荐聚焦的3个技能
我写这份周报时给自己定了一个要求:不要在周报里写空泛的自我鼓励。比如"下周要加油"这种话一句都不许有,只允许列出数据和具体行动项。写满三个月后回头翻,你能清晰地看到一条技能成长的轨迹——哪周连续断档,哪个技能突然起跳,都历历在目。
5.3 技能组合雷达图的取舍:SVG方案
多数技能管理工具会画一个雷达图展示多维度能力均衡性。我在skills里试过两个方案,最后都砍掉了。
第一个方案是在终端里用ASCII字符画雷达图,效果惨不忍睹,坐标轴稍微多点就混成一团。
第二个方案是生成SVG图片。技术上完全可行——生成一个report.svg文件,浏览器打开就行。但我发现雷达图对我的决策帮助极其有限:雷达图的优点是展示多个维度间的均衡,但我的技能树有十几个领域,雷达图画出来就是一团颜色鲜艳的变形虫,"哪个环节凹了"远不如前文的树形图直观。
砍掉后我把精力集中在"WEEK-LEVEL"的环比数据上。也就是说,与其看静态的均衡性,不如看动态的变化趋势。新增了一个skills trend --domain 后端开发命令,输出最近八周该领域所有技能等级的折线趋势。这个更实用,因为它直接告诉你:你的某一个行动项到底有没有拉高技能等级。
6. 踩坑与升级:真实使用三个月后我改了什么
6.1 踩坑一:打分上瘾,反而助长了自我欺骗
第一个月出现了最讽刺的问题:我每天跑十几次skills check,刷新的不是技能,而是分数条。有一阵每次更新完看分数上涨都会感到一阵满足,结果月底复盘时发现,好几项技能的evidence列表还是空的,分数却涨了一级。
这个坑伤害很大。等于说工具不仅没有帮我认清自己,反而成了自我安慰的玩具。
修复方式不复杂:我加了一个"冷静期"规则——任何技能两天内只能打一次分,而且更新时必须先填证据。同时在check交互里增加了一个二次确认:当本次打分高于上次,必须再回答一次"你最近的证据是什么"。这个改动让打分频次立刻降了下来,但证据质量和自评准确度都上去了。
6.2 踩坑二:技能粒度太粗,让计划无法落地
第二版技能树虽然比扁平清单好很多,但第三层仍然太粗。我最初把"Kubernetes"当一个技能,然后不管怎么努力,等级都很难越过3级。后来意识到,"Kubernetes"本身就是二三十个技能的组合——Deployment滚动更新、Service网络、存储卷、RBAC权限、Operator开发,每一样都可以作为独立的第三层技能。
调细粒度是项体力活,但收益极大。切分之后,每一项都变得"可以定义进步",比如"RBAC权限模型"可以在两周内通过做一个多租户应用加强,而"Kubernetes"本身很难在一个月内感知到提升。
我的判断标准是:如果一个技能的progress描述需要绕过三层,那么粒度就得拆;如果一个技能的计划描述里出现了"了解""熟悉"这种模糊动词,那一定还没拆到底。
6.3 踩坑三:只记录不回顾,数据沦为僵尸
第三个月我发现一个危险信号:我的技能数据在持续增长,但我再也不看历史数据了。YAML文件越来越长,weekly report堆了一堆没人翻。数据一旦停止被使用,它就从管理工具变成了数字藏品。
我被迫引入一个"回顾仪式":每周日的那次skills report,加上一次原生SQL查询,专门找出"过去两周都没有任何evidence更新的技能":
SELECT s.name, s.level, MAX(e.evidence_date) AS last_evidence FROM skills s LEFT JOIN evidence e ON s.anchor = e.anchor GROUP BY s.anchor HAVING last_evidence < date('now', '-14 days') ORDER BY last_evidence ASC;然后逐条问自己:这个技能是故意搁置的,还是被遗忘的?前者允许,后者要么补上实践,要么干脆从树里降级。
这一招挽救了整个工具——数据流重新转动起来,不再是"记完就死"。
6.4 最终保留的工作流:每周15分钟闭环
经过三个月的迭代,我淘汰了大部分复杂功能,只保留了最小可行闭环。现在每周只花大约15分钟在skills上:
- 周一:跑一次
skills check,针对上周计划里的3个重点技能打分,更新证据。 - 周日:跑
skills report,结合trend看本周变化,然后跑skills plan制定下周聚焦清单。
这15分钟的价值不只是记录,更重要的是形成了一个"实践—打分—规划—实践"的闭环。打分迫使你面对事实,计划迫使你选择方向,证据迫使你真正动手。
最后说一点个人体会
写完这个项目,我自己最大的改变倒不是学会了多少新技能——技能树只是度量工具,种树还得靠实操。真正的改变是我不再逃避评估这件事了。以前想到"盘点技能"就觉得麻烦、心虚,现在每天打开终端跑一条命令,数据全在那儿,短板暴露得清清楚楚,反而没那么焦虑了。
如果你想复刻这个项目,我建议你别照抄我的YAML和命令设计,而是先想清楚你最需要回答的问题是什么——是想转行?想晋升?还是单纯想找到下个月学什么?你的问题决定了你的模型。skills的价值不在于它本身有多完美,而在于它逼着你把模糊的"我感觉我还可以"翻译成可定位、可验证的一行行技能记录。
最后再分享一个操作层面小技巧:在skills.yaml所在目录建一个Git仓库,每周末提交一次。三个月后回看提交历史里的skills.yamldiff,那种看到自己能力结构一点点重构的感觉,会比任何打卡App的连续天数都更真实。