如何用Python写出易维护的自动化脚本
接手过别人写的自动化脚本的人都体会过那种“打开文件就想摔键盘”的冲动。变量名叫a、b、c函数动辄两百行没有任何注释异常处理全靠try...except: pass。更恐怖的是脚本里硬编码了服务器IP、数据库密码、文件路径换台机器跑就直接崩溃。这种代码不是工具是定时炸弹。但问题不在写脚本的人不努力而在于“能跑”和“可维护”之间隔着一条认知鸿沟——很多人把写脚本当成了“一次性便利贴”而不是“长期资产”。要写出能让人愿意维护的自动化脚本第一步不是学什么设计模式而是改变对脚本寿命的预期。如果你心里想着“这脚本就跑一个月”你当然不会费心去设计。但现实是自动化脚本的生命周期往往远超预期那个“临时用一下”的报表导出脚本三年后还在每天凌晨跑着而它的作者早已离职。所以请把每一个自动化脚本都当成会存活五年以上的生产代码来写。先撕掉“一次性”的标签从目录结构开始我见过太多自动化脚本所有的逻辑都堆在main.py里从上到下是“导入模块、定义几个函数、然后一个巨大的if __name__ __main__:块”。看起来没什么问题直到你需要复用其中一个函数却不得不把整个文件import进来结果它连带执行了所有副作用——数据库连接、文件删除、发邮件。可维护的第一个信号是脚本的副作用可以被清晰地隔离。建议采用最朴素的分层结构config模块放配置、core模块放核心业务逻辑、runner模块放入口和调度。哪怕你只有三个文件也请坚持这种拆分。举个例子你的脚本要监控某个目录处理新出现的Excel文件然后入库。那么core里就只写“如何解析Excel”和“如何写数据库”runner里只负责“扫描目录→调用core→记录日志”。这样一来当你想在另一个脚本中复用“解析Excel”这个功能时直接from core.excel_parser import parse_workbook干净利落。如果做不到一眼看出“这个脚本是干嘛的、入口在哪、配置在哪”那就已经欠了技术债。当然目录结构不是越复杂越好。对于绝大多数自动化任务三个文件是黄金分割config.py、tasks.py、main.py。再大一点就用包package来组织。但无论如何请把配置从代码里彻底剥离。config.py里只放路径、阈值、开关这些变量不要放任何业务逻辑。而且配置项要集中、要有注释、要有默认值。最粗暴但也最有效的做法是脚本里不允许出现任何裸的字符串常量。像/data/input/、root、123456这种必须定义成INPUT_DIR /data/input/这样的常量否则换环境时你得像考古一样在代码里挖地三尺。日志不是可选项是救命稻草自动化脚本最大的特点是“无人值守”。它半夜两点跑出错了不会有人当场看到。这时候可维护性的核心就是“可诊断性”。而诊断的基础就是日志。很多脚本作者觉得“反正跑完就行记日志干嘛”结果出问题时只能两眼一抹黑不知道是没跑、跑一半挂了、还是跑完了结果不对。请停止使用print来调试自动化脚本。print的输出会丢失时间戳会混在标准输出里无法分级而且你很难控制它的详略。用logging模块配置好FileHandler让日志同时输出到控制台和文件。更重要的是要设计日志的级别和使用规范INFO记录每个关键步骤的起点和终点DEBUG记录中间变量的值WARNING记录可恢复的异常ERROR记录导致任务中断的故障。我还建议在每个函数入口处打一条logger.debug(fenter {func_name}, params...)——别嫌啰嗦等出问题需要定位时你会感激这些“废话”。另一个容易忽视的点是日志的轮转。脚本每天跑一次日志文件如果不切割一个月后就会变成几十MB的巨型文本查找问题如同大海捞针。用logging.handlers.RotatingFileHandler设置单个文件不超过5MB保留5个备份。可维护的日志应该是“最近的问题一翻就找到太久远的历史自动消失”。让错误“失败得响亮”自动化脚本最怕的不是报错而是静默失败。很多脚本用try...except Exception: pass把异常吞掉然后继续跑最后生成了一个残缺的结果还以为是成功的。这种行为比不写异常处理更恶劣因为它制造了“虚假的安全感”。宁可让脚本崩溃也不要让它带着错误跑完。正确的做法是捕获异常时要么记录完整的traceback并重新抛出要么在捕获后发送告警通知。如果脚本是定时任务那么任何ERROR级别的日志都应该视为“需要人工介入”的信号。你可以写一个装饰器在每个任务函数上统一处理异常捕获后记录日志、发送邮件或钉钉消息、然后退出或继续。失败得响亮比失败得优雅更重要——因为无人值守环境下“响亮”才能把人叫醒。另外对返回值的校验不能省。比如你调用了外部命令subprocess.run(...)如果不检查returncode脚本会假装成功。从requests获取API数据时不检查status_code就直接解析JSON很容易得到KeyError而不是明确的服务端错误。校验每一条外部依赖的返回值并给出有上下文的错误消息例如“调用XXX接口失败HTTP 503响应内容为...”这种代码一眼就能看出问题在哪。别硬编码时间与路径拥抱相对与动态自动化脚本里最常见的坑就是“过了今天就不能用”。比如你写了一个按日期生成报表的脚本直接把2025-03-12写进了字符串。第二天跑报表目录是昨天的。时间、路径、账号、URL这四类东西永远不应该出现在代码主干里。动态计算日期是基本功用datetime.now()和timedelta来推导“今天”“昨天”“上周一”。如果脚本需要从某个日期开始补数据请把起始日期作为命令行参数通过argparse或click传入。一个好的自动化脚本应该像一把瑞士军刀默认行为安全但允许你通过参数改变行为而不是让你改代码才能适应新场景。路径方面永远使用相对于项目根目录的路径而不是绝对路径。把项目根目录作为基准用pathlib.Path(__file__).resolve().parent.parent来动态获取。这样整个仓库挪到任何机器上都能跑除非是操作系统的本质差异。如果脚本需要访问网络共享目录或远程数据库也把这些“不稳定依赖”封装在独立的模块中并明确标记哪些是开发环境、哪些是生产环境。给每个文件戴上“身份牌”文档与类型很多脚本作者觉得“代码自己能说话”但那是对于刚好能看懂的人来说的。三个月后的你其实已经是另一个人。可维护性要求你写出“不需要阅读代码就能知道它在干嘛”的文档——但这不意味着写长篇大论的注释而是准确地命名、写docstring、以及标记类型。函数名应该是动词短语比如download_file、normalize_data、send_alert不要用do_stuff、handle这种模糊词。变量名要具体到“业务含义”比如incoming_invoices比files好一万倍。每个函数至少写一行docstring说明入参、出参、可能抛出的异常。如果你的脚本有超过五个函数请额外添加一个README.md用三句话讲清楚这个脚本解决什么问题、怎么安装依赖、怎么运行。放在仓库根目录字少事大成本极低收益极高。类型标注是Python 3.10以后最被低估的维护利器。不要嫌麻烦写上def fetch_file(url: str, timeout: int 30) - Path:IDE和类型检查器立刻能帮你发现许多低级错误。类型注解不是给解释器看的是给未来的维护者看的——它像一份免费的函数签名文档。如果有人不小心传了个None进去mypy会在运行前就报警而不是等脚本在半夜炸掉。抽干重复从“函数”到“装饰器”自动化脚本里最常出现的重复模式有三类重试、计时、告警。比如调用外部接口偶尔超时你不得不在每个请求外面写for i in range(3): try: ... except: time.sleep(2i)。这种逻辑如果复制粘贴到十个地方就变成了维护的噩梦——一旦要修改重试策略你得改十处。把横切关注点提炼成装饰器是自动化脚本可维护性的高级形态。举个例子写一个retry(max_attempts3, delay2)装饰器内部实现了指数退避和日志记录。然后把所有可能“瞬时失败”的操作都标上这个装饰器。再写一个notify_on_error(webhook_url...)装饰器函数一旦抛出异常就向钉钉或企业微信发消息。最后写一个log_duration装饰器记录每个关键步骤的执行时间。这样你的核心业务函数就只剩下纯粹的“做什么”而“怎么做可靠”“失败了怎么通知”都在装饰器里统一管理。代码的重复度直接决定维护的负担——每消除一处重复未来就少一个改漏的地方。同样的道理适用于“资源管理”。如果你的脚本要操作文件或数据库连接请务必使用with语句或contextlib.closing确保无论是否出错都会释放资源。你不想遇到“脚本跑完但文件被占用”的诡异问题吧自动化脚本里任何资源句柄都要有明确的关闭终结者这是最基本的品格。测试不是“大公司专用”有些读者看到“测试”两个字就想关页面。但针对自动化脚本你不需要搞什么单元测试覆盖率90%只需要做一件事写一个smoke_test函数或脚本把核心逻辑用一组精心挑选的小样本数据跑一遍并断言结果符合预期。这五分钟的投入能为你每次修改代码后省下半个小时的“手工跑一遍看结果”的时间。最理想的测试对象是“纯函数”——也就是输入相同则输出必定相同、没有副作用、不依赖外部环境的函数。在架构上尽量把业务逻辑写成纯函数把外部I/O读文件、发请求放在最外层。比如“根据日期计算报表文件名”是纯函数你可以放心测“调用API并保存到数据库”不是纯函数你没法稳定测。所以设计时就要知道可测性 耦合度的反向指标。当你觉得一个函数很难测试时其实是在提醒你它设计得太糟糕了。有了纯函数测试就简单了。用pytest写几个assert断言就够。不要小看这些粗糙的测试它们至少能防止你在修改一个函数时不小心把另一个函数的输出格式改坏了。自动化脚本的回归测试不需要追求全面但要锁定核心契约——比如“输出CSV的表头必须不变”“行数必须等于输入文件的行数减去表头”。让脚本自己“讲人话”输出与状态一个可维护的自动化脚本它的输出应该是“给机器看的状态”和“给人看的摘要”相结合。脚本跑完之后你或者接手的人应该能一眼看出今天干了什么、成功了多少、失败了多少、耗时多久。用标准的格式化输出比如“2025-03-12 03:00:01 [INFO] 处理完成共12个文件成功11个跳过1个原因文件为空”而不是一长串杂乱无章的print。更进一步可以考虑在脚本结束时生成一个简单的报告文件report.txt或report.json里面包含运行参数、关键统计、异常列表。这样即使你第二天早上再看也能轻松复盘。如果脚本需要被监控系统盯住请统一约定“成功退出码为0任何异常退出码非0”——不要所有情况都返回0那样监控系统形同虚设。最后别忘了“幂等性”。一个优秀的自动化脚本应该可以安全地重复运行。不管是重复执行还是重新补跑结果应当一致且不产生垃圾数据。设计时多问自己一句如果这个脚本意外跑了两遍会不会出乱子如果会请加个锁例如filelock或者至少做个“已处理记录”去重。可维护不是指“代码漂亮”而是指“在真实世界中经得起折腾”。收尾这不是“过度设计”而是“职业素养”回头看看上面说的每一条都不深奥拆分文件、用常量、写日志、拒绝静默异常、动态路径、类型注解、装饰器、冒烟测试、幂等性。这些不是什么高深的架构只是把写业务系统的常识移植到了自动化脚本这个“小世界”里。很多人不做的原因只有一个——觉得“脚本嘛能用就行”。而正是这种心态制造了无数个半夜被人从被窝里叫醒的运维事故。自动化脚本的本质是把人的重复劳动交给机器。如果这个过程中还需要人来维护、调试、救火那么自动化就失去了意义。维护成本的高低才是衡量一个脚本真正价值的标准。从今天起当你下次新建一个.py文件时不妨想象它会被一个未来的你或者一个比你有耐心的同事打开他会看到什么样的代码他会不会在心里默默送你一句“这兄弟干活真地道。”这就是我们要追求的全部。