电脑屏幕上还挂着昨晚没跑通的那段代码手边的练习笔记翻到今天这一页第48天。说实话练到这个天数新鲜感早就没了能推着我坐下来的不是“今天要学点新东西”的兴奋劲儿而是一种更朴素的念头——昨天的坑还没填完今天总得让它跑起来。这篇想记的是第48天这次上机练习的完整经过练了什么、卡在哪、怎么排错、练完之后我又做了哪些调整。我目前主攻的方向是编程和办公自动化所以每天说的“上机练习”就是打开电脑对着真实任务写代码、调配置、跑测试顺手把能用的脚本留下来。如果你也在用类似的节奏坚持练一门技能不管是写代码、做表格、剪视频还是搞设计这套练习的编排思路和排查方法应该都能用得上。1. 连续练到第48天我为什么还留着一张“每日练习清单”先说说“第48天”这个位置有多尴尬。最开始练的两周每天都有新东西砸过来那种“我又学会一个技能”的兴奋感足够支撑我坐到电脑前。到了第三周、第四周新鲜感开始退潮真正剩下的是重复动作打开编辑器、写点代码、遇到报错、搜解决方案练完还得收拾心情去睡觉。第48天正好卡在一个很微妙的阶段——不是新手但也远谈不上熟练想放弃吧觉得可惜想冲刺吧又不知道往哪使劲。1.1 第48天之前踩过的节奏坑前一个月我犯过两个特别明显的错。第一个是“贪多”一天给自己排好几个练习任务上午学文件操作下午学数据分析晚上再看一小节视频课。结果每一项都只是浮皮潦草过了一遍白天学的文件操作还没在项目里用热乎晚上就被前面的内容冲淡了。第二个是“只练不想”有的日子是纯抄代码照着教程一个字一个字敲进编辑器敲完运行通过就觉得今天的任务完成了。可关上电脑我回想一下根本说不清这段代码解决了什么问题、换一个场景该改哪儿。这两个坑合起来导致一个结果练了三十多天手机备忘录里存了一堆半吊子笔记但真让我独立做个小工具脑袋还是会卡壳。到了第45天左右我实在看不下去这种状态停了两天没练新东西专门把自己手头的练习记录翻了一遍定了一条规矩每天必须有一张“练习清单”清单上写清楚今天要完成的唯一目标以及怎么判断自己算完成了。1.2 清单不写“练习两小时”而是写“跑通一个功能”我现在的练习清单非常简单就三行。第一行写目标第二行写关键动作第三行写复盘点。比如第48天这张清单是这么写的目标让下载目录整理工具能处理“文件被占用”的报错动作读报错信息、缩小问题范围、修改脚本、做回归测试复盘把“遇到报错不心慌”的过程总结成一套可复用的步骤不写“练习两小时”是因为时长和收获之间没有稳定的正比关系有时坐了一下午有效输出只有三行代码。而“跑通一个功能”是客观标准完成了就是完成了没完成就是没完成心里有数。这份清单每天只要花五分钟写但它最大的作用不是规划而是在你练到一半想放弃的时候帮你把注意力拉回到那个“唯一目标”上不许跑偏。2. 第48天的具体练习做一个下载文件夹自动整理工具第48天我给自己定的目标是写一个自动整理下载文件夹的小工具。为什么选这个题目因为它足够实用做完立刻能感觉到好处。我电脑的下载目录长期乱成一锅粥里面同时混着图片、压缩包、安装程序、PDF文档还有各种临时文件。以前靠人工整理基本是攒上一两个月才忍无可忍地清理一次。用脚本处理这件事既满足真实需求又正好能把文件遍历、扩展名匹配、目录创建、重命名、异常处理这些基本功串起来练一遍。这个项目放在第48天做还有一个原因难度刚好够得着我现在的水平又稍微超出一点舒适区。如果不考虑“文件被占用”“同名文件冲突”这些细节代码框架我大概半小时就能写出来但一旦把处理异常也加进去就需要静下来想清楚很多边界情况。这种“有点挑战但没到劝退”的感觉适合练手。2.1 先拆需求再写第一行代码拿到一个练习题目我最容易犯的毛病是“直接上手敲”。敲到一半发现漏了这个场景、忘了那个扩展名代码越写越乱。第48天我逼自己先花十分钟把需求拆成了三步扫描下载目录里的所有文件排除掉子目录根据扩展名把文件分门别类比如图片、文档、压缩包、安装包、代码文件移动到对应的分类子目录里如果目标位置已经有同名文件自动加序号避免覆盖这个拆法看起来很基础但它决定了后面代码的长相。分类逻辑我用一个字典来维护后面想加新类型只要往字典里添一行就行不需要改动主流程。import shutil from pathlib import Path RULES { 图片: [.jpg, .jpeg, .png, .gif, .bmp, .webp], 文档: [.pdf, .docx, .doc, .txt, .md, .xlsx, .pptx], 压缩包: [.zip, .rar, .7z, .tar, .gz], 安装包: [.exe, .msi, .dmg, .deb, .rpm], 代码: [.py, .js, .ts, .java, .c, .cpp, .html, .css], } def classify(file_path: Path) - str: ext file_path.suffix.lower() for category, exts in RULES.items(): if ext in exts: return category return 其他2.2 处理重名文件是这轮练习里最容易忽略的细节分类函数写完接下来是移动逻辑。我在练习清单里专门给自己设了一个提醒不能直接shutil.move就完事必须考虑目标目录里已经有同名文件的情况。因为下载文件夹里很容易出现“项目文档.pdf”被下载了两次这种状况直接移动会把旧文件覆盖掉这是不能接受的。所以我在移动前加了一个“找可用文件名”的函数如果目标文件已经存在就在文件名后面加_1、_2直到不冲突为止。def unique_path(target_dir: Path, filename: str) - Path: dest target_dir / filename if not dest.exists(): return dest stem dest.stem suffix dest.suffix counter 1 while dest.exists(): dest target_dir / f{stem}_{counter}{suffix} counter 1 return dest这段代码本身不复杂但我觉得它体现了“需求拆解得够细才写得出安全代码”这个道理。不拆的话我大概率不会想到重名问题等真实用户用起来才发现文件被覆盖那就晚了。2.3 跑测试先拿小目录试别直接开刀真实下载目录第一次写好脚本我忍住没有拿真实下载目录去跑。这个“忍住”很重要。我把当前目录下建了一个test_download文件夹在里面放了几类测试文件然后运行脚本。这样做的理由是万一逻辑有 bug最多影响测试目录不会把我真实的下载文件弄乱定位问题也容易得多。我给脚本加了一段简单的移动逻辑def organize(directory: Path) - int: moved 0 for item in directory.iterdir(): if item.is_file() and not item.name.startswith(~): category classify(item) target_dir directory / category target_dir.mkdir(exist_okTrue) dest unique_path(target_dir, item.name) item.rename(dest) moved 1 print(f{item.name} - {category}/{dest.name}) return moved在测试目录里跑完输出符合预期文件都进了对应分类文件夹。这时候我才敢把路径参数改成真实的下载目录。结果刚点运行就弹出来一个PermissionError场面瞬间就热闹了。3. 卡住的那两小时文件占用报错的完整排查链路都说写代码一半时间是在解决报错第48天上机练习最值钱的部分恰恰就是这一个半小时的排错过程。我没有直接冲上去搜“PermissionError怎么解决”而是耐着性子把整条排查链路走了一遍。这条链路走到最后其实没有什么玄学全靠一步一步把问题缩小。3.1 把报错信息当成一个句子去读遇到报错我的第一反应不是慌也不是立刻复制粘贴去搜而是先把报错信息从头到尾读一遍。当时终端输出的是这样一段PermissionError: [WinError 32] 另一个程序正在使用此文件进程无法访问该文件。这句话里信息量很大。左边是异常类型PermissionError说明是一个权限类错误中间是WinError 32这是操作系统返回的错误码右边这段中文解释已经说明白了——另一个程序正在使用这个文件。换句话说不是我的代码权限不够而是文件被别的软件给锁住了。大多数时候报错文本已经给了我们排查方向只是很多人习惯性地一看到英文或代码就跳过直接去搜索引擎找答案。其实应该先自己读一遍把里面出现的文件名、行号、错误码圈出来。我这次的报错信息里虽然没有直接显示是哪个文件出问题但至少告诉我方向是“文件占用”跟网络、跟路径都没关系。3.2 用“二分法”把问题范围一步步缩小知道了是文件占用但具体是哪个文件被占用、为什么占用还需要继续缩范围。我在脚本里临时加了一个try except把异常对象单独打印出来for item in directory.iterdir(): if item.is_file() and not item.name.startswith(~): try: # 移动逻辑... pass except PermissionError as e: print(f跳过文件: {item.name}, 原因: {e})跑完一遍被跳过的文件名字一个个列了出来。一看名字立刻明白了——好几个是扩展名为.crdownload的文件也就是浏览器正在下载中的临时文件。这些文件当时还在被下载程序占用操作系统不允许我移动或重命名所以才会报错。找到这一步问题已经从“我的代码坏了”缩小到“我的脚本处理不了正在下载的文件”。这是一个很自然的边界问题下载中的文件本来就不应该被整理工具挪走。我需要在分类之前把这类临时文件过滤掉。3.3 修复方案与验证加过滤条件后必须做回归测试修复方式不复杂加一个忽略列表把常见临时文件后缀都放进去PARTIAL_SUFFIXES {.crdownload, .part, .tmp, .download, .temp} for item in directory.iterdir(): if item.suffix.lower() in PARTIAL_SUFFIXES: continue加上这个条件后我又重新做了两遍验证。第一遍还是在test_download里模拟故意放一个fake.crdownload文件进去确认脚本不会碰它。第二遍才回到真实下载目录跑这次所有正常文件都被分类归位临时文件原地留着等对应软件用完再自行处理。修完这个 bug我的练习清单复盘点就派上用场了。我把这次排查的过程总结成三句话先读全报错再缩小范围最后对着边界条件做回归。这套步骤往后遇到别的报错我也能直接套用。4. 真正拉开练习差距的不是时长而是“手感”涨没涨上机练习和其他学习方式最大的不同是它特别强调“手感”。看书看视频的时候你会觉得“我都懂了”但一上机发现连在哪个菜单里找功能都要犹豫半天这就是典型的“脑子会了手不会”。到了第48天我明显感觉到大脑到手指这条通路比以前顺畅了但这种感觉很难量化。于是我开始琢磨怎么能观察到自己确实在进步。4.1 用三个可观察指标判断手感我目前常用的指标有三个。第一个是“从想法到敲下第一段代码”的时间。以前我想实现一个小功能得先楞两分钟想用什么函数、参数怎么传现在大多数常用结构可以不用思考直接打出来大脑腾出来想逻辑就行。第二个是“出错之后的反馈速度”。以前报错一出现我得仔细看半天才知道代码错在哪一行现在扫一眼错误信息基本能猜到大概是哪一类问题比如缩进、路径、编码还是类型不匹配。第三个是“对工具的熟练度”比如快捷键切换窗口、多光标编辑、调试器里加断点这些操作都很细小但每一步省几秒加起来的流畅度能感觉到明显变化。这三个指标不是用来给自己打分的而是用来帮自己找到状态。如果某天练习时我发现自己频繁在同一个低级操作上卡住就会停下来想想是不是这几天练的内容太杂了基本功没跟上4.2 操作慢下来的地方正是知识盲区上机练习特别有意思的一点是它会用“手”来暴露“脑子”的漏洞。比如我可以嘴上说得清清楚楚“用shutil.move移动文件”但真到写代码的时候可能忘了要不要先创建目标文件夹或者搞不清shutil.move和Path.rename的差别。这些细节在看教程的时候都会被带过只有上机动手手指停在键盘上半天没动静盲区才会现形。我的处理办法是一旦发现自己同一个知识点卡了好几次就把它单独记进一张“盲区清单”不急着当天解决但隔天一定要回头想一遍。因为这往往不是“这个函数不会用”而是“我对于文件路径、目录结构这些底层概念的理解还不到位”。盲区不补换个场景还是会卡。4.3 刻意练习和重复练习的分界线练了四十多天我还有一个很深的感触练习这件事分“重复练习”和“刻意练习”。重复练习是把已经会的操作多练几遍求熟练度刻意练习是在当前能力边缘往上顶一点每次给自己设置一个稍微处理不了的任务。前者不坏但只有后者才能真正扩大能力圈。区分两者的方式很简单如果一项练习做完之后你的感觉是“好熟练啊闭着眼也能做”那它多半是重复如果做的时候出现了一两次卡壳、两三次查资料的瞬间而且这些卡壳确实被解决了那才是刻意练习。第48天的下载整理脚本原本对我的难度是中等但“文件占用”这个边界问题成了那根轻轻往上抬的杠杆让我不得不去处理异常路径。这就是刻意练习在起作用。5. 第48天之后把零散练习攒成自己随时能用的工具箱上机练习到了这个阶段我开始不满足于“今天练了点什么”而是更在意“今天练出来的东西能不能留下来、下次继续用”。之前很多练习是碎片化的今天写个九九乘法表明天爬个网页练完就扔非常可惜。第48天之后我想换一种方式把每次练习的产出物都收拾干净让它们变成可以被复用的模块或工具。5.1 给每个练习项目设一个“下一版”清单第48天的下载整理工具跑通之后我没有把它打进冷宫而是顺手写了一个“下一版想加的功能”列表。比如给脚本加一个“演练模式”先让用户看看每个文件会移动到哪儿确认没问题再真移动再比如加一个日志文件让脚本每次运行完都记录下移动了哪些文件或者用配置文件代替写在代码里的分类规则。把这些想法写下来的意义在于下次想练点什么的时候不用再临时想题目直接从几个“下一版”里挑一个就行。这和追剧留悬念是一个道理——给未来留一个自然的入口练习才能连续起来。5.2 写练习日志但别把它当日记从第30天开始我每天练习完会花十分钟写一小段练习日志。日志不是流水账核心是记录“遇到什么问题、怎么想的、最后怎么解决”。比如第48天的日志就重点记了“WinError 32”的排查过程。这些记录以后翻起来比任何教程都有用因为它是你自己走过的路里面包含的背景和坑在别的资料里根本找不到。我给自己定了三个关键词目标、卡点、结论。目标是一两句话描述今天要完成的事卡点记录中间堵住的地方结论写这次练习沉淀出的一条可复用经验。有了这三个词写日志就很快不会变成负担。5.3 隔一段时间回读旧代码做一次“轻重构”上机练习的后期我养成了一个习惯每隔七八天挑一段之前的代码回来看一遍顺手做点轻量重构。说是重构其实就是给变量起更好的名字、删掉没用的注释、把重复的代码抽成小函数。这个过程不追求把代码改得多优雅而是逼自己重新理解一遍当时写的逻辑。很多当初写的时候灵光一现的东西过段时间再看会发现其实可以做得更简洁。更重要的是重读旧代码能让我看到自己成长了多少。第48天回看第10天写的脚本会觉得当时怎么写得那么绕再过几天回看第48天的代码也能发现新的优化点。这种“看到过去的自己”的落差感其实是上机练习最好的正反馈——它用事实告诉你手感是真的在涨。最后再分享一个小习惯每天开始练新内容之前我先花十分钟把前一天的练习日志和关键代码扫一遍。别小看这十分钟它能防止你把上一天刚摸到门槛的知识全部忘光。第48天那天我就是在回看日志的时候想起上次排错到一半的“文件占用”问题还没彻底验证这才有了后来完整的修复过程。上机练习这件事真的不是比谁练得久而是比谁每次都能留下点能积累的东西。