说实话,最后一次按下回车,看到命令行里干干净净地打出那行All tests passed的时候,我差点从椅子上跳起来。这个项目从开始写第一个文件到今天,整整三个月,中间有过两次想放弃的深夜,有一周加班到凌晨两点的记录,甚至还有一次因为临时改需求,把刚写好的模块整块删掉的崩溃瞬间——但我的代码,总算是“生”出来了。
这篇文字不是来晒成绩的。我是想把这三个月的完整过程记录下来:这个项目到底做了什么、技术选型为什么这样定、过程中为什么差点烂尾、上线前那几个 Bug 是怎么一个个揪出来的、跑通以后我又做了什么。如果你正准备做自己的项目,或者正写着一个写到一半想放弃的东西,希望这篇复盘能给你一些真实的参考。咱们直接开始。
1. 这个项目到底“生”出来的是什么
事情的起因特别朴素:我每周五下午都要花大半天整理工作数据、做 Excel、写周报。流程固定到不能再固定——从几个不同来源把数据拎出来,清理掉格式问题,按模板统计,最后填进周报里。听起来不难,但真正做起来,你会发现时间全耗在“搬运”和“对齐”上:有些表格从系统导出是乱序的,有些字段一会儿叫“负责人”一会儿叫“经办人”,还有些数据源根本导不出文件,只能手动复制粘贴。
我一开始想找现成方案解决。翻了商业报表工具,功能确实强,但报价对个人项目来说毫无性价比;也看过不少开源工具,不是字段对不上,就是要为我的场景做大量改造,改完以后维护成本比自己做还高。至于继续人工?偶尔偷懒可以,但每个星期都重复这套操作,迟早会出错,而且已经出过错了。
所以最后我决定:自己写一个自动化汇总工具,把我每周的重复劳动交给程序。项目范围很快收敛成四个核心能力:
- 自动从几个固定数据源读取记录,包括导出的文件、数据库表,还有同事偶尔发来的散装 Excel;
- 对数据进行清洗和规范化:统一字段名、处理缺失值、剔除明显异常数据;
- 按周报模板自动生成 Excel 和文字摘要;
- 生成结果后通过团队的 IM 机器人推送到群里,周五一键出报。
我给它起了个名字叫“周记”。一开始纯属顺手,后来发现这名字还挺贴切——它真的像一本自动帮我写好的周记。
这里想多说一句“自己的代码”的含义。市面上教程很多,但大多数教程教你的是“用某个框架搭一个 Demo”,把零件拼起来就完事。而这次我是从需求分析、数据结构设计、编码、测试到部署全部自己完成,没有照着任何现成的开源项目改。这个过程难度其实大得多,因为没有任何“标准答案”兜底,每一步都要自己判断对不对。等真跑通了,那种成就感是复制粘贴别人的代码完全体会不到的。
2. 技术选型的三个回合,我是怎么被教训的
2.1 第一回合:要不要上 Web 界面
项目最开始我心里想的是写个 Python 脚本跑完拉倒。但很快发现不现实:这个工具不只是我自己用,同组同事也要用。如果交付一个命令行程序,同事电脑上得装 Python 环境,还得去配置依赖,他们大概率会用不了两天就放弃。就算我用 PyInstaller 打成 exe,操作路径还是太反人类。
所以第一回合的结论是:需要一个可以浏览器访问的界面。但这里必须把控好度——我需要的不是炫酷的动态交互,而是“打开网页,上传文件,看到结果,点击下载”这种最简单直接的流程。技术选型能轻则轻。
2.2 第二回合:技术栈的取舍
确定要做 Web 应用之后,摆在面前的是几个方向。我把当时的判断整理成一张对比表,这里的思路比结论本身更重要:
| 备选方案 | 学习成本 | 团队使用门槛 | 部署维护成本 | 我的判断 |
|---|---|---|---|---|
| 纯 Python 脚本 | 低 | 高(只能命令行操作) | 极低 | 只适合做原型验证,不适合长期给团队用 |
| Python + FastAPI + 轻量前端 | 中 | 低(浏览器访问即可) | 低(一台小服务器足够) | 最终选择 |
| Node.js + React 前后端分离 | 中高 | 低 | 中(需要单独维护 Node 服务) | 对当前需求来说过重 |
| 桌面应用(PyQt 等) | 高 | 中(每台机器都要安装) | 高(更新一次发一次版) | 不合适,更新和维护都是噩梦 |
最终敲定 Python + FastAPI + Vue(通过 CDN 引入)+ SQLite 的组合。
选 Python 的理由很直接:我在数据清洗上最熟的是 Python,Pandas 处理脏数据的能力不是其他技术栈能比的,而且 FastAPI 自带 OpenAPI 接口文档,联调时会省大量沟通成本。前端用 Vue 但完全没上工程化那套构建流程,直接 CDN 引进来写点简单逻辑就够用得不得了。为什么不上 React 加 Webpack?因为这个项目最大的前端交互就是“上传文件、渲染一张表格、点几个按钮”,我不需要花一礼拜搭一套 Node 构建链。维护依赖越多,项目越容易死。
数据库这块我也犹豫过。后来选了 SQLite,理由很直白:团队总共几个人,数据量撑死十万行以内,SQLite 作为文件型数据库完全扛得住。真要单独部署一个 PostgreSQL 服务,反而增加运维负担,服务器重启忘了拉起数据库,整个项目就瘫了。
2.3 第三回合:要不要上“高级架构”
这里有个特别想吐槽自己的点:项目进行到第二周,我被各种技术文章影响了,一度想给项目上 Redis 做消息队列、上 Docker Compose 管理多个服务、把数据导入导出拆成微服务。真去推演才发现,我的业务场景里所有任务都是定时触发,接入方一共就几个数据源,一条流水线串下来最清晰。
我最后说服自己的理由是:架构必须服从实际需求,而不是反过来。微服务解决的是团队协作复杂度和弹性伸缩问题,我这两样都用不上,硬上只会让项目从“还在跑”变成“跑在了一片废墟上”。技术人经常犯的毛病是手里拿着锤子,看什么都是钉子。控制住自己的冲动,比引入一个新框架更重要。
3. 差点烂尾:从“写不出来”到“垂直切片”
3.1 第一次计划翻车的全过程
项目初期,我的计划是按技术层次来拆任务的:第一周搭数据库结构,第二周写数据清洗逻辑,第三周做接口,第四周写前端页面。听起来挺合理对吧?结果执行到第二周我就崩了。
数据库结构建好了,清洗逻辑先写了五成,但这两个模块之间根本没法联调,因为你没有实际的数据在流水线上跑起来。第三周开始写接口,发现清洗模块返回的数据结构和我想的不一样,又回去改数据层,改完发现前端等不了接口,而 UI 一片空白根本没法看进度。整个项目看起来像一只一直在积木却拼不满任何零件的大船,每天都在写代码,却没有任何一个“可见的成果”能端出来给人看。
到第四周我问自己:这个东西真的做得出来吗?那几天我是真的动了放弃的念头,项目代码在仓库里躺了整整一个周末没动过。
3.2 垂直切片救了我的项目
后来和一个前辈聊了聊,他给了一个建议:别按技术层拆,按“一条完整的需求链路”拆。每个切片都从数据输入走到结果输出,哪怕中间代码很丑、很简陋,也必须是一个能跑完的闭环。我重新规划了五个切片:
- 命令行接受一个本地文件,跑完输出一份周报 Excel。这一步不接多数据源、不做网页,目标就是验证核心逻辑成立。
- 接入真实数据源,能自动合并多个文件,处理常见的格式不一致问题。
- 加网页界面,用户上传文件后能在浏览器里看到清洗完的数据表格和统计结果,能下载 Excel。
- 加入模板配置和定时任务,让系统每天早上自动跑一次、生成草稿。
- 最后补权限、操作日志和异常告警,让同事能放心用。
每个切片都有明确的完成标准:切片完成不是“代码写完了”,而是“按用户的使用方式操作一遍,能拿到预期结果”。
这个改变是整个项目最重要的转折点。每一个切片做出来,都意味着“我确实做出来了一个能用的东西”,这种正反馈在漫长的开发周期里比意志力可靠得多。我大概用两天做完切片 1,七天做完切片 2,切片 3 用了十天,切片 4 和切片 5 各用了一周左右。节奏一下子就顺了。
3.3 允许第一版写得很烂
关于开发心态我还有一个重要的经验:项目中途,允许自己写很烂的代码。
前期我写完一段代码总觉得不够优雅,反复重构,结果就是进度拖慢、心情变差。后来我给自己定了个规矩:第一版只要正确,不要优美。先把功能跑通,再在项目体检阶段统一优化。事实证明这样做不仅更快,而且当你看到完整功能已经成型时,再去重构反而更有底气,因为每一步都有验收结果兜底,改坏了也知道要改回什么状态。
4. 上线前最折磨人的三个 Bug:完整排查链路还原
4.1 Bug 之一:Excel 打开中文全是乱码
现象:网页预览完全正常,从页面下载生成的 Excel 文件,用 Excel 打开后中文全部变成乱码,但用记事本打开又正常。
我一开始怀疑是 Python 写文件的编码没指定。检查了一圈,源码文件是 UTF-8,open()写文件时也显式指定了encoding='utf-8',理论上不该有问题。为了确认,我打印文件字节流,发现里面的中文确实是以 UTF-8 编码正常存储的。
那问题就不在文件本身,而在 Excel 的解析逻辑。我回想了一下:Excel 打开 CSV 或文本类文件时,默认按本地系统编码(中文 Windows 一般是 ANSI,也就是 GBK 系列)去解,UTF-8 编码又没有 BOM 标记,Excel 就不知道这是 UTF-8,拿 GBK 去解 UTF-8 的中文字节,自然满屏乱码。
修复其实只有一行:
with open(output_path, 'w', encoding='utf-8-sig') as f: ...关键在utf-8-sig这个编码,写文件时会自动带上 BOM 头,Excel 读到 BOM 就知道按 UTF-8 处理了。这里给我的教训是:下游工具怎么解读文件,和文件本身是否正确是两码事。尤其是给非技术同事用的工具,默认环境都是 Windows,兼容性测试必须在 Windows 上做一遍,不能只在 Linux 服务器上看一眼预览就觉得完事了。
4.2 Bug 之二:数据量一上来,页面直接卡死
现象:本地测试用几千条数据,页面流畅得很。接入真实数据源后,单次查询返回几万条记录,浏览器直接转圈,整个页面像假死一样。
我先用浏览器开发者工具看了一眼 Network 面板,发现接口一次性返回了全量数据,几万行记录直接塞给前端去渲染。再往数据库提层看,慢查询日志显示关键表的查询是全表扫描,没有走索引。
这个问题的定位过程其实不复杂,但值得说一下排查顺序:一定是先看接口层是不是返回了过多数据,再看数据库层是不是有索引,最后看前端渲染有没有卡点。我是按这个顺序一个个排除的。
修复分三步走:
给查询接口加分页参数,前端用滚动加载替代一次性渲染全部数据。
-- 修复前 SELECT * FROM task_records WHERE create_date >= ?; -- 修复后 SELECT * FROM task_records WHERE create_date >= ? ORDER BY id LIMIT ? OFFSET ?;给高频查询字段建索引,查一次执行计划确认走索引。
CREATE INDEX idx_task_records_create_date ON task_records(create_date);前端表格改为按需渲染,配合分页接口。
修复后同样上万条数据,首屏加载时间从原来快十秒降到一两秒,滚动加载时也不卡了。
这个 Bug 最大的教训是:本地测试的数据量级必须接近真实场景。几千条数据掩盖了很多问题,几万条数据才真正暴露系统瓶颈。项目上线前应该主动用生产环境量级的数据压一遍,而不是拿一份“干净、小巧”的样例数据自我感动。
4.3 Bug 之三:两个人同时提交,后写的覆盖了先写的
现象:团队里两个人同时录入数据,各自改动了同一个项目里的不同字段,提交后发现其中一个人的修改莫名丢失。查操作日志,两个人的请求时间只差了几秒。
我先怀疑是不是系统里有两个地方同时写同一条记录。查过代码,发现写入接口确实只有一处,但问题出在写入方式上:前端提交的是完整对象,后端拿到后直接执行整行覆盖更新,完全没有校验版本。也就是说,A 先读取到的数据是 v1,B 也读取到 v1,两个人各自修改后提交,系统都按 v1 覆盖写,后提交的 B 就会覆盖 A 的修改。
修复方案是给数据表加版本号字段,并让更新语句带上版本条件:
UPDATE records SET field_a = ?, field_b = ?, version = version + 1 WHERE id = ? AND version = ?;UPDATE 的语句返回受影响行数为 0 时,说明版本已过期,后端直接返回“数据已被他人修改,请刷新后重试”,前端弹窗提示用户重新拉取最新数据。
这个 Bug 的修复工程量不大,但它让我对“数据安全相关的机制,再小的项目也不能省”这句话有了切身体会。单机脚本里写不写版本号都无所谓,但一旦多人使用,并发问题一定会出现,迟早问题而已。所以后来我把这类约束列成了一个清单:任何涉及修改的接口,默认先想清楚并发情况下会不会互相覆盖。
5. 跑通之后才是真正的开始:优化和体检
5.1 从一坨函数到分层结构
项目跑通那会儿,代码其实是赶出来的,主流程集中在三个大函数里,大概六百多行,虽然能运行,但我自己每次改东西都胆战心惊,就怕改了 A 影响 B。项目上线稳定以后,我做的第一件事就是重构。
重构的目标不是追求设计模式,而是让代码分层清晰、职责单一。我把项目拆成了四个目录:路由层只负责接收请求和返回响应,业务层放核心的处理逻辑,数据访问层统一封装数据库操作,工具层放文件处理、日志封装等辅助功能。就是这次重构,让后续加功能和排查问题的成本都降了一大截。
5.2 日志与测试:没出事前觉得没用,出事后才觉得真香
重构之前,项目排查问题基本靠print()。项目跑通后我做的第一件事就是把日志补齐。我用了结构化日志,每条日志都带时间、级别、模块名、请求追踪 ID。这样即使多人同时使用,我也可以通过同一个追踪 ID 把一次操作的完整链路串联起来,定位问题从“靠猜”变成“靠查”。
测试方面,我给数据清洗和统计逻辑补了单元测试,因为这是整个系统最核心、最容易藏逻辑错误的地方。又写了一个端到端的冒烟脚本,每次上线前一键跑一遍,确保核心链路没有回归问题。补完测试以后,我再改代码的焦虑感直线下降。
5.3 部署、备份、性能,一个都不能偷懒
部署方面,我没有上 Kubernetes 那些重型方案,就是一台轻量服务器加systemd管理服务,设置开机自启和自动拉起。数据库备份则写了一个每日凌晨自动执行的脚本,把 SQLite 文件复制到备份目录,保留最近七天,防止误操作或者磁盘故障导致数据丢失。
性能方面,除了前面修的两处问题,我还在查询耗时超过 2 秒的接口上加了简单缓存,并顺手优化了 SQLite 的连接管理,避免每次请求都重新打开连接。
这是我记录下来的优化前后对比:
| 检查项 | 优化前 | 优化后 |
|---|---|---|
| 主流程代码结构 | 3 个大函数共 600 行,职责混乱 | 按路由、业务、数据访问分层,文件职责清晰 |
| 日志 | 只有 print,线上问题靠猜 | 结构化日志,带追踪 ID,可一键全链路检索 |
| 测试 | 无任何自动化测试 | 核心逻辑单元测试加端到端冒烟脚本 |
| 数据备份 | 无备份 | 每日自动备份,保留 7 天 |
| 关键接口响应 | 全量返回,几万条就卡死 | 分页加缓存,2 秒以内返回 |
| 并发安全 | 无版本控制,后写覆盖先写 | 乐观锁控制,冲突时明确提示 |
测试和日志这类工作,项目开发过程中你很难感受到它的价值,但一旦线上出了问题,它们就是你的眼睛和手。别等项目出了事故再补,那时候的代价一定是十倍。
6. 这行代码对我的意义
如果这篇复盘只留一句话,我想说的是:“做出来”这件事,本身比“做得多完美”重要得多。
写代码和养孩子真有几分相似。过程里你会遇到很多想撒手的瞬间,但只要你每天都能让一个切片跑通,让一个功能长出来,几个月后再回头看,那个当初觉得自己根本不行的人,已经和自己的项目一起变成了另一个样子。我的“周记”现在还活着,每个星期五下午都会准时在工作群里说一句“本周周报已生成,请查收”。同事早就习惯了它的存在,甚至忘了它是我一行一行写出来的——这大概就是一个程序员最满足的时刻。
如果你也打算做自己的项目,我唯一的建议是:别等万事俱备,别等自己“学会了再做”,现在就从你手边那个最烦人的重复任务开始,写第一行代码。烂代码没有关系,做到一半推翻也没有关系,持续去做,直到某个晚上,你也会像我一样,看着屏幕上那行绿色的All tests passed,忍不住跟所有人说一句:我的码生出来了。