前前后后折腾了一个多月,我终于把零散的个人技能全部搬进了一个叫skills的终端工具里。它本质上是一个技能管理工具:敲一条命令,记录自己学了什么、熟练度到哪、下次该练什么;再敲一条命令,输出本周该优先练习的清单。解决的是我一直以来的毛病——收藏了很多课程,列了很多计划,却始终说不清楚自己究竟会什么。
下面这篇文章,我会按真实的开发顺序,把skills从需求分析、数据设计、核心功能,一直讲到一个月使用后的复盘和迭代。如果你也试过用表格或App管理技能但最终放弃,或者你想做一个自己真正会用的本地小工具,这篇内容应该能给你一些可落地的参考。
1. 为什么没有选表格或App,而是写了命令行工具
1.1 表格管理技能的"自由"是最大的坑
我最早是用在线表格来管理的。字段很简单:技能名称、掌握程度、备注。刚开始两个星期填得还挺勤,越往后越不想打开。问题不在懒惰,而在表格没有任何约束。同一项技能,我今天写“Python:熟练”,下周就可能写“Python:会用一点”,描述词完全对不上。等到想统计“我到底有多少技能达到了熟练”的时候,只能靠肉眼一条条找,数据基本没法直接使用。
表格适合管理固定表头的清单,不适合管理需要持续更新、交叉筛选、还要计算时间间隔的动态记录。自由度高看起来是优点,但个人做记录的时候,明确的结构约束反而更重要。没有约束的记录,最后都会变成一堆无法信任的文本。
1.2 App看着功能全,但数据不在自己手里
后来也试过几款成长记录类的App。它们的核心卖点是打卡、连胜、成就徽章,用来维持习惯确实有激励作用。但我的目标不是“每天打卡”,而是“随时搞清楚当前技能水平和下一步该练什么”。这类App的问题主要有两个。
第一,数据结构是预设死的。分类不能改,等级标准不能改,想加一个“最近实践日期”或者“前置依赖”这样的字段,往往找不到入口。第二,数据存在服务商的服务器里。哪天App停止更新,或者我自己换了工具,积累的记录就很难完整导出。考虑到技能记录需要长期维护,我更倾向于把数据放在一个完全由自己控制的本地文件里。哪怕格式简陋一些,但数据是资产,工具可以换,资产不能丢。
1.3 命令行工具的真正价值:数据可以被编程
所以最后选了命令行工具。这不是为了显得技术化,而是它同时满足了三个核心诉求:结构完全自定义,想加字段随时加;数据保存为本地文本,能纳入Git版本管理;可以跟系统里的其他脚本联动,比如定时自动生成周报。这三点叠加起来,技能管理就从“记录发生了什么”变成了“可编程的数据资产”。
下表是我当时做选型时的粗略对比,不一定适合所有人,但很能说明问题:
| 方案 | 结构约束 | 数据归属 | 二次开发能力 |
|---|---|---|---|
| 在线表格 | 弱,靠自觉 | 自己 | 困难,公式有限 |
| 成长类App | 强但不可自定义 | 平台 | 受限,只能按官方功能走 |
| 命令行工具 | 完全自定义 | 本地自己 | 自由,可写脚本和接口 |
对于只想简单记录的人来说,表格可能够用;对于需要长期维护、还要做统计分析的人来说,命令行工具的优势会随数据量增长越来越明显。
2. 技能数据模型:把“会什么”变成可计算的字段
2.1 一条技能记录最少需要哪些字段
写代码之前,我先把一条技能记录需要什么字段列了出来。最终定下的结构长这样,用JSON表示:
{ "name": "Python", "category": "语言", "level": 3, "tags": ["data", "backend"], "last_practiced": "2025-01-12", "goal": "level 4", "notes": "能用pandas做数据清洗", "dependencies": ["编程基础"], "history": [ { "date": "2025-01-12", "level": 3, "note": "完成了月度报表脚本" } ] }每个字段都有明确作用。name是唯一标识,不能重名;category决定这条技能归到哪个大类;level是核心指标,代表当前熟练度;tags用于跨分类检索;last_practiced记录最近一次练习日期,它决定“多久没碰了”;goal是目标状态;notes是补充说明;dependencies记录前置技能;history数组是最关键的部分,每次练习都追加一条历史,为后面的周报和趋势分析打基础。
有一些想得很美的字段后来砍掉了,比如“价值评分”“紧急程度”。这些词太主观,填的时候根本不知道该选什么,最后还是靠 level 和 last_practiced 这两个客观可查的字段驱动决策。
2.2 熟练度0到5的评级标准必须统一
如果不定义清楚 level 的取值,数据会很快变回一坨模糊文本。我参考了常见的能力层级模型,简化成一张判断表:
| 等级 | 含义 | 判断标准 |
|---|---|---|
| 0 | 未入门 | 只听说过,没有实际用过 |
| 1 | 了解 | 看过教程,没有独立完成过任务 |
| 2 | 会基础 | 能照着文档完成标准流程 |
| 3 | 可独立 | 不查资料也能完成常规任务 |
| 4 | 熟练 | 能处理异常情况,也能指导别人 |
| 5 | 精通 | 形成方法论,能设计新流程 |
这张表解决了大问题。以前填“熟练”完全靠感觉,现在我会先问自己:“不查资料能不能独立完成一个常规任务?”能,就是3;要边查边做,顶多2。统一标准之后,不同技能之间的水平才真正可比,后面的推荐算法才有意义。
2.3 分类和标签为什么要分开
分类和标签一开始被我当成一回事,后来发现必须分开。我的做法是:分类单选,用于归档;标签多选,用于交叉检索。分类给固定的几个值:语言、框架、工具、软技能、领域知识。标签则更自由,比如data、写作、后端、运维。
打个比方:分类像是衣柜里的大格子,一件衣服只能放进一个格子;标签像是衣服上的吊牌,可以贴很多个。分类决定了打开哪个抽屉找它,标签决定了它能在哪些主题里被搜到。这样“pandas”会放在“语言”分类下,但打着data标签,之后用--tag data过滤,所有和数据分析相关的技能都能一起出现。
2.4 为什么用JSON而不是SQLite
其实一开始我也纠结过,觉得SQLite才够“正统”。但这个工具的使用场景很明确:本地、单人、数据量最多几千条、需要频繁读取。JSON完全可以覆盖。更重要的是,JSON文件可以直接放进Git仓库,每次修改都能看到diff,哪条技能在哪天被改成了几级,一清二楚。
实现上只需要一对简单的读写函数,我也没有引入ORM。真正的代码在加载和保存时统一做一次格式校验,保证字段完整。后面如果数据量真的涨到需要复杂查询,再迁到SQLite也不迟,因为改动只集中在数据访问层。
3. 核心功能实现:输入一条命令,完成一次技能登记
3.1 命令设计:add、ls、update、stats、suggest
这个工具的命令行入口我用的是 Python 的argparse,子命令设计成五个:add新增技能,ls查看清单,update修改基本信息,practice记录一次练习,stats输出统计,suggest给练习建议。主逻辑非常简单,加载数据、执行对应子命令、保存数据。
def main(): parser = argparse.ArgumentParser(prog="skills") sub = parser.add_subparsers(dest="cmd", required=True) p_add = sub.add_parser("add", help="新增技能") p_add.add_argument("name") p_add.add_argument("--level", type=int, default=1) p_add.add_argument("--category", default="领域知识") p_add.add_argument("--tags", default="") p_add.add_argument("--goal", default="") p_add.add_argument("--notes", default="") p_add.set_defaults(handler=cmd_add) # 其余子命令结构类似 args = parser.parse_args() args.handler(args)实际开发时我没让add一次接收所有参数,因为太长的命令根本记不住。先用skills add Python建一条初始记录,然后用update逐步补充分类、标签和目标,这样更符合渐进式记录的习惯。
3.2 新增技能:写入前的校验与去重
新增的核心逻辑是去重和字段校验。技能名必须唯一,如果已经存在,就直接提示用户改用update。level 必须落在0到5之间,否则拒绝写入。
def cmd_add(args): data = load_data() if any(s["name"] == args.name for s in data["skills"]): print(f"技能 {args.name} 已存在,请用 update 修改") return if not 0 <= args.level <= 5: print("level 必须在 0 到 5 之间") return record = { "name": args.name, "category": args.category, "level": args.level, "tags": args.tags.split(",") if args.tags else [], "last_practiced": "", "goal": args.goal, "notes": args.notes, "dependencies": [], "history": [] } data["skills"].append(record) save_data(data) print(f"已新增技能:{args.name}(level {args.level})")注意last_practiced初始为空字符串。只有真正发生过一次练习之后才填日期。这个空值会在后面的suggest里被当作用户从没碰过标记处理,不会因为默认成当天日期而被误认为刚刚练过。
3.3 查看清单:按分类和标签过滤
ls命令负责把当前技能清单打印成表格。实现不复杂,核心是支持--category和--tag两个过滤条件。输出时按分类排序,每个分类下再按 level 降序排,这样一眼能看出当前强项在哪。
语言 Python [3] data, backend SQL [2] 工具 Docker [1]这个输出看起来清爽,实现也很短:遍历技能列表,逐个判断分类和标签是否匹配,匹配后按格式打印。真正重要的不是打印代码,而是输出顺序的设计。把最强的技能放在最前面,能让人在打开清单的第一秒就获得“我确实会点东西”的反馈,而不是淹没在几十条未整理的信息里。
3.4 记录练习:每次实践都留痕
practice是使用频率最高的命令,也是整个系统里最有价值的设计。它的职责是更新 level 和 last_practiced,并在 history 数组里追加一条记录。
def cmd_practice(args): data = load_data() for s in data["skills"]: if s["name"] == args.name: s["level"] = args.level today = datetime.date.today().isoformat() s["last_practiced"] = today s["history"].append({ "date": today, "level": args.level, "note": args.note or "" }) save_data(data) print(f"{args.name} 已更新为 level {args.level}") return print("未找到该技能")之所以要把每次实践追加到 history,而不是只覆盖 level,是因为统计需要时间维度的数据。一个月后回头看,哪项技能是在稳步提升,哪项只登记过两次就再也不碰了,都可以从 history 里查到。这也是后期做技能轨迹分析的基础。
4. 从清单到计划:用“最低行动量”驱动技能维护
4.1 清单如果不产生行动,就只是个收藏夹
有了清单之后,我很快发现一个问题:如果只是把它放在那里,根本不会产生任何学习行为。清单和收藏夹最大的区别,在于它能不能回答“下一步做什么”。于是我给skills加了一个suggest命令,让它每次给出三个最值得练的技能。
def cmd_suggest(args): data = load_data() candidates = [ s for s in data["skills"] if s["level"] >= 1 and s["level"] < 5 ] candidates.sort(key=lambda s: s["last_practiced"] or "0000-00-00") for s in candidates[:3]: print(f"{s['name']}:上次 {s['last_practiced'] or '从未练习'},目标 {s['goal'] or '未设置'}")排序逻辑很简单:最后一次练习日期最早、且还没有达到5级的技能排最前。不考虑优先级、不考虑兴趣,只看“多久没碰”。这背后的理念是,维持热度的优先级高于学习新东西。很多人都有体会:新技能学了不少,旧技能丢得也快。suggest就是用来对抗这种“只学不练”的惯性。
4.2 周报:让每周数据自己说话
stats --week是我第二个使用的主动功能。它会统计过去七天里新增了多少技能、练习了几次、有哪些技能已经超过30天没有碰。这些数字加起来,基本就是本周技能资产的真实变化情况。
我把它设计成一个每周日晚上的固定动作。跑完命令,看到数字,再决定下周的练习重点。这个动作一开始很麻烦,但养成习惯后作用很大——它让“自我评估”不再依赖情绪,而是依赖数据。今天状态好,觉得什么都会一点;状态不好,又觉得自己什么都不会。周报用客观数字把人拉回现实。
4.3 依赖关系:让建议顺序符合学习规律
技能之间不是孤立的点。没有 dependencies 时,suggest 只知道“哪项技能最久没练”,却不知道“练这项之前,是不是该先补一个前置技能”。比如我想把“数据分析”从3练到4,但它的前置技能“SQL”还停留在1级,那直接练“数据分析”效率一定很低。
我在suggest里加了一步:先检查候选技能的所有依赖项,把依赖项中 level 最低、或最久没练的排在前面。也就是说,suggest 推荐的可能是“SQL”,而不是“数据分析”,直到 SQL 达到目标等级。这个改动让推荐顺序符合真实学习路径,而不是机械地按最后实践日期排序。
4.4 和定时任务联动:把建议送到你面前
工具本身不会提醒人,但它可以被系统调度。我把skills suggest接入了本地定时任务,每天早上九点自动跑一次,输出到我的备忘录文件里。这样不用主动打开工具,也能在开始工作前看到今天的练习建议。
提示:如果只想用最简方式,一条 shell 命令加 cron 就够了。关键是别把工具做成必须“专门打开”的东西,最好让它主动出现在你的工作流里。越不需要意志力去想起它,这个工具就越容易坚持用下去。
5. 可视化输出:让技能结构一眼可见
5.1 终端技能树:最快速的全貌预览
ls打印的是平面清单,但我后来发现,把技能按分类和多级缩进组织之后会更像一棵树,视觉效果更好。于是我做了一个简化版输出,每行显示技能的等级、标签以及最近练习时间:
语言 Python [3] data, backend 上次 2025-01-12 SQL [2] data 上次 2025-01-05 工具 Docker [1] 运维 上次 2024-12-20这种带缩进的树形输出,在终端里比表格更透气。每次打开终端,跑一遍skills tree,整个人对当前能力结构就有了一目了然的感知。它不需要任何图形库,只是字符串处理,但它的价值不在美观,而在于信息密度。
5.2 用matplotlib导出雷达图:看见能力短板
文字输出适合日常使用,但有些场景需要更直观的图,比如季度总结、向团队展示能力模型。我写了一个脚本,从 JSON 里读取每个技能分类的平均等级,然后用 matplotlib 生成雷达图。
import json import matplotlib.pyplot as plt import numpy as np from math import pi def load_data(): with open("skills.json", encoding="utf-8") as f: return json.load(f) data = load_data() categories = ["语言", "框架", "工具", "软技能", "领域知识"] values = [] for cat in categories: skills = [s for s in data["skills"] if s["category"] == cat] avg = sum(s["level"] for s in skills) / len(skills) if skills else 0 values.append(avg) angles = [n / len(categories) * 2 * pi for n in range(len(categories))] angles += angles[:1] values += values[:1] plt.figure(figsize=(6, 6)) ax = plt.subplot(111, polar=True) ax.plot(angles, values, "o-") ax.fill(angles, values, alpha=0.25) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) plt.tight_layout() plt.savefig("skills_radar.png", dpi=150)雷达图最大的价值,是把“哪些分类明显偏弱”暴露得很彻底。我第一次生成这张图的时候非常意外,以为自己最强的分类是“工具”,结果数据告诉我“语言”的平均等级更高。直观地面对数据,能修正很多自我认知的偏差。
5.3 分级配色:用颜色指示行动,而不是评判好坏
在终端输出里,我给不同等级用了不同颜色的ANSI转义码。0到2级用灰色,3到4级用蓝色,5级用橙色。这个设计不是为了让画面好看,而是为了让人一眼知道该往哪使劲:灰色代表未激活,不需要焦虑;蓝色代表已经能独立干活,可以继续精进;橙色代表这个领域已经有了足够沉淀,下一步可以考虑输出或迁移。
注意:可视化永远是服务于决策的。如果一张图只是展示现状,却没有引导下一步动作,那它就只是一个漂亮的装饰。我的原则是,任何可视化输出旁边都要能顺带回答“然后呢”。
6. 一个月实测复盘:这套系统踩过的坑和改法
6.1 最大的坑:等级虚高,数据完全失真
刚建好数据的时候,我一股脑填了三十多项技能,给很多技能都打了3级。结果 suggest 跑出来一堆“3级但很久没练”的条目,根本不知道从哪开始。问题出在我登记时用了自己的直觉,没有严格对照那张0到5的判定表。
后来我做了一件事:把判定表打印出来,贴在显示器边框上。每次登记 level 前先问自己:“现在不查资料,我能不能独立完成一个常规任务?”能,才写3;一旦犹豫,就写2。这个改变很痛苦,因为要承认很多我以为“会”的技能其实只是“了解”。但正是这种痛苦让数据重新变得可信,suggest 也终于能给出合理的建议。
6.2 每天更新太琐碎,改成每周集中维护
最初的设计是每次学习完立刻记录。实际上这几乎不可能坚持。工作一忙,可能连续几天想不起来打开终端,等想起来的时候已经积了一堆没记的练习。与其强迫自己每天执行,不如接受人类天生倾向于批量处理。
我改成每周日晚固定腾出十分钟,把这周相关的练习一次性补进practice历史。数据粒度从“天”降到了“周”,但长期趋势依然清晰,而且维护成本低了一个量级。这个小改动让我意识到,工具迭代不一定是加功能,也可以靠改变使用节奏来匹配自己的真实习惯。
6.3 技能不是孤立点:依赖关系是最关键的一次迭代
使用到第三周,我发现清单本质上还只是一张名单,无法支撑路径规划。于是我补上了 dependencies 字段,并花了半天把常见技能之间的依赖关系理了一遍。比如“数据分析”依赖“Python基础”和“SQL”,“视频剪辑”依赖“剪辑软件基础”和“叙事逻辑”。
补上依赖之后,suggest 的推荐顺序立刻变得可靠了。有一次我想把“数据分析”填成4级,但程序检查出它的依赖“SQL”还只有1级,我只好先把数据分析改回2,转头去补 SQL。这个判断如果靠人脑,大概率会被我忽略;但数据不会说谎,它会用红字告诉你:前置条件没达到。
6.4 一个完整案例:把“想学数据分析”拆成可行动子技能
这里拿我自己的经历举例。最初我在 skills 里只加了一条“数据分析”,很长一段时间根本不知道怎么下手。后来我把它拆成四个条目:数据分析(领域知识)、Python(语言)、SQL(语言)、统计学基础(领域知识)。
然后让 suggest 根据依赖关系排优先级。第一周练 Python,第二周练 SQL,第三周回来看数据分析。每周末只处理一个点,每个点都有一个明确的 level 目标和实践记录。这种拆解逻辑让我从“收藏了一个大目标”变成了“每周推进一小步”。
6.5 荒废技能提醒:果断标记放弃也是一种进步
一个月后跑 stats,发现有七项技能超过30天没碰。suggest 会把它们单独列出来,让我决定是重新激活还是标记放弃。最开始我做不到,总觉得“以后可能用得上”,于是每条都标成希望重新捡起。结果发现列表越积越长,反而干扰了真正想练的技能。
后来我想通了一件事:暂时放弃不叫失败,叫释放注意力。我专门加了一个--status字段,可以标记为“active”或“paused”。paused 状态的技能不参与 suggest 排序,也不再制造焦虑。等真正想重新学的时候,再把它激活就行。这个设计在心理上是巨大的改善。
7. 把skills当成个人仪表盘:下一步扩展方向
7.1 一键导出技能矩阵,直接用于简历和总结
每月写月报或者更新简历时,我都要手工整理自己会什么,费时费力。现在 skills 里已经有完整数据了,完全可以自动生成一份按分类和等级排列的 Markdown 表格。每条技能后面带上标签和最近实践日期,直接粘到文档里再用文字润色就行。
这个功能的实现很简单:遍历技能列表,按分类分组,输出成表格。但它省下的时间不多,真正的价值在于确保简历上写的每一条都能实时对应到历史记录里,不会出现“简历上写着熟悉某技术,实际上半年没碰”的尴尬。
7.2 从个人走向团队:共享技能地图
如果不止我一个人使用这套数据结构,而是团队里每个人都维护一份类似 JSON,就可以按成员维度生成一张技能覆盖度矩阵。哪个人擅长什么、哪块领域只有一个人能扛、哪些技能重复度太高,一目了然。
当然,这种用法要注意边界。它更适合用来做结对安排和任务分配参考,不应该变成绩效评价的标尺。技能数据非常容易被误读,一个人 level 没上4,可能只是最近几个月没练习,不代表他能力不行。数据是起点,不是结论。
7.3 长期积累后的技能轨迹分析
history 数组现在看起来只是每次练习的日志,但一旦积累半年以上,它就会变成一条技能发展曲线。用简单的数据处理就能看出:这项技能是在稳步上升、还是长期平台期,甚至已经明显衰退。这些判断远比“当前 level 多少”更有价值。
我现在的思路是每个月跑一次快照,把各技能的 level 和时间点存下来。之后用折线图观察趋势。趋势分析不一定需要复杂算法,简单的线性变化率就能说明很多问题。比如一门技能两个月内从0涨到3,说明这段时间投入有效;另一门技能连续三次月报都没有新记录,那它大概率正在从你的能力版图里淡出。
最后说一点个人体会。工具本身并不神奇,真正起作用的是每周那十分钟面对自己数据的时刻。skills 让我第一次看清:我有三十多项技能,但真正达到“可独立完成任务”的只有五项目。把模糊的焦虑变成明确的缺口之后,行动路径就清楚了很多。如果你也想做类似的事,不用照搬我的设计,先写下三到五个必留字段,跑起来,再根据你真实的习惯迭代。数据不需要完美,需要诚实。