3.3 函数与方法:短小、单一职责、卫语句


3.3 函数与方法:短小、单一职责、卫语句

本节摘要:函数是代码组织的基本单元,函数设计规范的核心是单一职责、短小精悍、参数克制与卫语句前置。本节给出函数长度与参数数量的经验上限、动词命名的搭配要求、类型提示的用法、一致返回值的原则,并演示用卫语句把异常条件拦截在函数开头、让主逻辑贴左侧的完整过程。

读前必看

阅读完本节,你应当能够:

  1. 用单一职责标准判断一个函数是否需要拆分
  2. 控制函数参数在三到五个以内,超出时用参数对象封装
  3. 用卫语句重写深层嵌套的函数开头
  4. 为函数签名添加类型提示并说明其收益
  5. 设计一致的返回值策略,避免用空值表示错误

一、问题与直觉:两百行函数的三宗罪

接手过一个两百行的下单函数:开头校验参数,中间查库存、算价格、凑优惠券,尾部落库发消息,六层嵌套,八个参数。它的三宗罪在维护中逐一兑现。其一,没人能一次读懂:读者的工作记忆大约能维持四个左右的活跃变量与条件,两百行意味着读到后面忘了前面,只能反复回滚重读。其二,测试写不动:想单独测"价格计算"这段逻辑?不行,它和数据库查询、消息发送焊死在同一个函数体里,单测被迫连带启动一堆依赖。其三,修改影响面模糊:改价格逻辑可能碰坏发消息的分支,因为共享着同一个作用域里的十几个中间变量。

三宗罪同源于一个设计缺陷:函数承担了太多职责。函数的本质是命名过的计算单元——名字承诺一件事,函数体兑现这件事。承诺两件的函数,名字说不清,测试测不全,修改必误伤。

二、核心原理:四条设计规范

2.1 单一职责与长度上限

每个函数只做一件事。判断标准很朴素:能用一句"做什么"的短句概括吗?概括不出来或者要写"并且",就该拆。长度上的经验上限是二十到五十行(不含注释与空行)——不是铁律,而是预警线:超过它的函数几乎总在偷偷承担第二职责。第 2.1 节的拆分信号在这里同样适用:需要用注释给函数内部分段时,就让段落升级为函数。

回顾订单函数拆分后的样子(概念代码):

def place_order(order): validate_order(order) total = calculate_total(order) deduct_inventory(order) save_order(order) send_confirmation(order)

主函数五行,读起来是流程目录;每个子函数独立可测、独立可改。价格计算想加新规则?只动 calculate_total,其他环节毫发无伤。

2.2 参数克制

参数数量上限建议三到五个。原因有二:参数越多,调用方的组合成本与出错概率越高(顺序传参时类型相近的参数极易互换);参数列表本身也在暗示函数职责过多——要那么多输入才能干的事,多半不止一件事。超限的解法是参数对象:把相关参数聚合成一个结构体传入。八个散参数变成一个"下单请求"对象,调用代码可读性立刻改善。布尔参数是另一个坑:place_order(order, True) 里的 True 是什么意思?调用处无从知晓,应改为具名参数或拆成两个意图明确的函数。

2.3 类型提示与一致返回

类型提示为参数与返回值标注类型(概念示例):

def calculate_area(length: float, width: float) -> float: return length * width

收益是三重的:调用处 IDE 即时提示类型、静态检查提前抓出类型不匹配、读者不必读函数体就知道输入输出契约。返回值的一致性原则同样重要:同型返回——函数要么总返回同类型,要么在非法输入时抛异常;切忌有时返回数据、有时返回空值、有时返回错误码,调用方被迫三路判断,遗漏一路就是缺陷。空值返回是最常见的隐患来源,更安全的策略是用异常表达"给不出结果",或用明确的"结果对象"同时携带成功数据与失败原因。

2.4 卫语句:把异常挡在门口

卫语句指函数开头集中处理异常条件与前置条件的模式:条件不满足就立刻抛异常或提前返回,让主逻辑不被嵌套包裹。对照(概念代码):

# 嵌套版 主逻辑被推到右侧 def place_order(order): if order is not None: if order.is_valid: if order.items: # 真正的主逻辑从这里开始 ... # 卫语句版 主逻辑贴左 def place_order(order): if order is None: raise InvalidOrderError("订单不能为空") if not order.is_valid: raise InvalidOrderError("订单校验未通过") if not order.items: raise InvalidOrderError("订单无商品") # 主逻辑从这行开始 一路向右不回头 ...

卫语句版每行条件独立成立、独立可读,主流程零缩进。它与"尽早失败"原则(第 4.1 节展开)天然配套:错误条件在入口即被拦截,不会带着脏状态流入深层逻辑。

函数规范速查

维度 规则 预警信号 修复手段
职责 一件事一个函数 名字里要写并且 拆子函数
长度 二十到五十行预警 注释分段 段落升级为函数
参数 三到五个以内 布尔裸参数 参数对象封装
返回 同型返回 用空值表示失败 异常或结果对象
嵌套 开头卫语句拦截 三层以上嵌套 提前返回
命名 动词开头可预期 名字说不清做什么 对照动词词汇表

💡 关键直觉:函数的调用处是函数设计的镜子。调用代码读起来别扭(参数记不住顺序、返回值要三路判断、布尔实参含义不明),说明设计有问题——修函数,别让每个调用点各自忍耐。

三、工程实践要点

3.1 私有成员的表达

函数与方法的可见性表达因语言而异:有的语言用单下划线前缀表示"约定私有"、双下划线触发名称改写;有的用关键字显式声明;新一代脚本语言用未导出即私有、类成员加井号前缀的机制。团队的统一约定应是:私有命名只用一种记号,公开接口必须配文档注释(第 3.1 节),让"能调用什么"一目了然。

3.2 避免副作用与全局状态

理想函数是纯函数:同样输入永远同样输出,不修改任何外部状态。纯函数可测性极佳(不需要准备环境、不需要清理现场)。现实中不可能全部纯化,但方向要坚定:函数需要外部状态时通过参数注入而非直接读写全局变量——全局变量让函数行为依赖"此刻全局是什么",测试时要先摆好全局、并发时互相踩踏。这也是依赖注入思想的微观形态(第 5.2 节展开)。

⚠️ 常见坑:把"函数要短"执行成机械的行数竞赛,拆出几十个两行小函数、主流程要跳转七八层才能读完。拆分的单位是职责不是行数——两个步骤强耦合且必须按序阅读时,强行拆开反而增加理解成本。判断标准始终是读者的总理解成本。

3.3 用重构手法而非重写达成规范

存量长函数的治理不是推倒重写。惯用手法是提取函数:找到内聚的代码段(比如那十五行价格计算),原样提取为新函数,参数与返回值按数据流向梳理,调用点替换,测试护航,提交。一次一个提取,每步可验证。这种小步重构累计起来,能在不冻结开发的情况下把两百行函数逐步驯化。

核心回顾

  • 函数是命名过的计算单元:名字承诺一件事,函数体兑现一件事
  • 长度预警线:二十到五十行,超限几乎总意味着第二职责
  • 参数克制:三到五个以内,超限用参数对象,布尔裸参数必须具名
  • 同型返回:数据与错误不同流,空值表失败是缺陷温床
  • 卫语句:异常条件门口拦截,主逻辑贴左零缩进
  • 纯函数方向:状态靠注入不靠全局,测试性随纯度上升
  • 小步重构治理存量:提取函数一次一段,测试护航

下一节缩小到最小单元:变量与常量——作用域怎么压、魔法数字怎么灭。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U