从意大利面条式代码到整洁的Python:初学者指南

💡 原文英文,约1500词,阅读约需6分钟。
📝

内容提要

本文以订单处理函数为例,讲解如何重构混乱的Python代码:将多职责函数拆分为职责单一的小函数,用数据类替代字典,用抛出异常替代打印警告,并为每个函数单独编写测试,从而消除依赖执行顺序的bug,使代码更易读、测试和调试。

🔎

延伸解读

识别代码坏味道:从订单处理函数说起

文章中的订单处理函数将定价、折扣、库存更新、运费计算和邮件发送全部塞进一个循环,导致一个隐蔽的bug:常规客户的折扣判断基于循环中的累计总额,而非最终订单总额,因此折扣是否生效取决于商品顺序。这种bug难以发现,因为所有逻辑纠缠在一起。读者应警惕函数名与实际职责不符、变量含义随代码变化、计算依赖语句执行顺序等迹象。

拆分函数如何消除顺序依赖bug

将大函数拆分为calculate_subtotal、apply_discount和calculate_shipping等小函数后,每个函数只接收明确输入并返回明确输出,不再修改共享状态。apply_discount现在基于最终小计判断折扣,而非循环中的运行总额,从而直接消除了因执行顺序导致的bug。这种拆分让每个函数可以独立调用和测试,无需运行整个脚本。

用数据类替代字典:明确数据结构

原始代码使用字典传递订单和商品,键名和类型没有保证。改用dataclass定义OrderItem和Order后,字段和类型变得明确,调用方必须按结构传参。process_order函数因此变为协调者,依次调用计算、折扣、运费和库存更新步骤,代码可读性提高,同时类型提示能让linter在运行前发现参数类型错误。

错误处理与测试策略的改进

原函数在SKU缺失时仅打印警告并继续执行,问题容易被忽略。改为抛出ValueError后,错误在发生点立即中断,防止订单在库存更新未完成时继续处理。拆分后,每个函数可单独测试,例如用pytest测试apply_discount的VIP和常规客户场景。测试失败能直接定位到具体函数,而非像原来那样只能报告订单总额错误。

Q&A

意大利面条式代码有哪些典型迹象?

函数名与实际功能不符、变量含义随代码位置变化、计算结果依赖语句执行顺序。

如何将一个大函数拆分成多个小函数?

为每个职责创建独立函数,明确输入和返回值,避免在循环中修改共享状态,确保计算不依赖执行顺序。

为什么用数据类替代字典来传递订单数据?

数据类提供明确的字段和类型,避免字典键猜测,让代码更清晰,并允许linter在运行前捕获类型不匹配。

在库存更新中,为什么应该抛出异常而不是打印警告?

抛出异常能立即停止执行,防止订单在库存更新失败时继续,并使问题在测试和调试中更易被发现和追踪。

如何为拆分后的函数编写测试?

为每个提取的函数编写独立测试,例如使用pytest,断言其输出是否符合预期,这样失败测试能直接定位问题。

重构混乱代码的推荐步骤是什么?

列出函数的所有职责,将每个职责提取为独立函数,用数据类替代字典,用异常替代打印警告,并为每个函数编写测试。

🏷️

标签

➡️

继续阅读