0

0

避免条件语句的智慧

王林

王林

发布时间:2024-08-14 12:06:41

|

325人浏览过

|

来源于dev.to

转载

避免条件语句的智慧

循环复杂度是衡量代码复杂性和混乱程度的指标。

高圈复杂度并不是一件好事,恰恰相反。

简单来说,圈复杂度与程序中可能的执行路径的数量成正比。换句话说,圈复杂度和条件语句的总数(尤其是它们的嵌套)密切相关。

所以今天我们来谈谈条件语句。

反如果

2007年,francesco cirillo发起了一场名为anti-if的运动。

francesco cirillo 是发明番茄工作法的人。我现在正在“番茄钟下”写这篇博文。

我想我们都很快从它的名字就明白了这个活动的意义。有趣的是,该运动的追随者中有不少计算机科学家。

他们的论点坚如磐石——如果语句是邪恶的,会导致程序执行路径呈指数级增长。

简而言之,这就是圈复杂度。它越高,不仅阅读和理解代码就越困难,而且用测试来覆盖它也就越困难。

当然,我们有一种“相反”的指标——代码覆盖率,它显示了测试覆盖了多少代码。但是这个指标以及我们编程语言中用于检查覆盖率的丰富工具是否证明忽略圈复杂度并仅基于“本能”散布 if 语句是合理的?

我觉得不是。


几乎每次我发现自己要将一个 if 嵌套在另一个 if 中时,我都会意识到我正在做一些非常愚蠢的事情,可以用不同的方式重写 - 要么没有嵌套的 if ,要么根本没有 if 。

你确实注意到了“几乎”这个词,对吧?

我并没有立即开始注意到这一点。如果您查看我的 github,您会发现不止一个旧代码示例,它们不仅具有高圈复杂度,而且具有直接的圈复杂度。

是什么帮助我更加意识到这个问题?可能是我一年前学到和接受的经验和一些聪明的事情。这就是我今天要跟大家分享的内容。


破坏 if 语句的两种神圣技术

  1. padawan,将未知值的每个条件检查移动到该值已知的地方。
  2. 学徒,改变你的编码逻辑思维模型,让它不再需要条件检查。

1. 让未知为人所知

在我们还不“知道”某事时检查它可能是使用基于“本能”的条件语句的最常见来源。

例如,假设我们需要根据用户的年龄做一些事情,并且我们必须确保年龄有效(在合理范围内)。我们最终可能会得到这样的代码:

from typing import optional

def process_age(age: optional[int]) -> none:
    if age is none:
        raise valueerror("age cannot be null")
    if age < 0 or age > 150:
        raise valueerror("age must be between 0 and 150")

我们都见过并且可能写过数百次类似的代码。

我们如何通过遵循所讨论的元原则来消除这些条件检查?

在我们特定的年龄情况下,我们可以应用我最喜欢的方法——摆脱原始的痴迷,转向使用自定义数据类型。

class age:
    def __init__(self, value: int) -> none:
        if value < 0 or value > 150:
            raise valueerror("age must be between 0 and 150")
        self.value = value

    def get_value(self) -> int:
        return self.value

def process_age(age: age) -> none:
    # age is guaranteed to be valid, process it directly

万岁,少一个如果!年龄的验证和验证现在始终在“已知年龄的地方”——在单独类的职责和范围内。

如果我们想删除 age 类中的 if ,我们可以走得更远/不同,也许可以使用带有验证器的 pydantic 模型,甚至用断言替换 if — 现在没关系。


有助于摆脱同一元思想中的条件检查的其他技术或机制包括用多态性(或匿名 lambda 函数)替换条件以及分解具有偷偷摸摸的布尔标志的函数等方法。

例如,这段代码(可怕的拳击,对吧?):

class paymentprocessor:
    def process_payment(self, payment_type: str, amount: float) -> str:
        if payment_type == "credit_card":
            return self.process_credit_card_payment(amount)
        elif payment_type == "paypal":
            return self.process_paypal_payment(amount)
        elif payment_type == "bank_transfer":
            return self.process_bank_transfer_payment(amount)
        else:
            raise valueerror("unknown payment type")

    def process_credit_card_payment(self, amount: float) -> str:
        return f"processed credit card payment of {amount}."

    def process_paypal_payment(self, amount: float) -> str:
        return f"processed paypal payment of {amount}."

    def process_bank_transfer_payment(self, amount: float) -> str:
        return f"processed bank transfer payment of {amount}."

如果你用 match/case 替换 if/elif 也没关系——都是同样的垃圾!

怪兽AI数字人
怪兽AI数字人

数字人短视频创作,数字人直播,实时驱动数字人

下载

很容易将其重写为:

from abc import abc, abstractmethod

class paymentprocessor(abc):
    @abstractmethod
    def process_payment(self, amount: float) -> str:
        pass

class creditcardpaymentprocessor(paymentprocessor):
    def process_payment(self, amount: float) -> str:
        return f"processed credit card payment of {amount}."

class paypalpaymentprocessor(paymentprocessor):
    def process_payment(self, amount: float) -> str:
        return f"processed paypal payment of {amount}."

class banktransferpaymentprocessor(paymentprocessor):
    def process_payment(self, amount: float) -> str:
        return f"processed bank transfer payment of {amount}."

对吗?


将带有布尔标志的函数分解为两个独立函数的示例与时间一样古老,令人痛苦地熟悉,并且令人难以置信地烦人(在我看来)。

def process_transaction(transaction_id: int,
                                                amount: float,
                                                is_internal: bool) -> none:
    if is_internal:
        # process internal transaction
        pass
    else:
        # process external transaction
        pass

无论如何,两个函数会好得多,即使其中 2/3 的代码是相同的!这是其中一种场景,其中与 dry 的权衡是常识的结果,使代码变得更好。


这里最大的区别是,从机械角度来说,在自动驾驶仪上,我们不太可能使用这些方法,除非我们已经内化并养成了通过这一原则进行思考的习惯

否则,我们会自动陷入 if: if: elif: if...

2. 释放你的思想,尼奥

事实上,第二个技巧才是真正的技巧,而之前的“第一个”技巧只是准备练习,是到位的捷径:)

事实上,实现更简单的代码、降低圈复杂度和减少条件检查的唯一最终方式、方法(无论你怎么称呼它)是改变我们在头脑中建立的用于解决特定问题的心理模型 .

我保证,今天最后一个愚蠢的例子。

考虑一下,我们正在紧急为一些在线商店编写一个后端,用户可以在不注册或使用注册的情况下进行购买。

当然,系统有一个 user 类/实体,完成这样的事情很容易:

def process_order(order_id: int,
                                  user: optional[user]) -> none:
    if user is not none:
        # process order for a registered user
       pass
    else:
        # process order for a guest user
           pass

但是注意到这些废话,由于我们的思维已经转向正确的方向(我相信),我们将回到 user 类的定义位置并重写部分代码,如下所示:

class User:
    def __init__(self, name: str) -> None:
        self.name = name

    def process_order(self, order_id: int) -> None:
        pass

class GuestUser(User):
    def __init__(self) -> None:
        super().__init__(name="Guest")

    def process_order(self, order_id: int) -> None:
        pass

因此,这一切的本质和美妙之处在于,我们不会用各种模式和编码技术来消除条件语句等,从而使我们的头脑变得混乱。

通过将我们的注意力转移到元级别,转移到更高的抽象级别,而不仅仅是对代码行的推理级别,并遵循我们今天讨论的想法,消除条件检查的正确方法,并且一般来说,更正确的代码将会自然出现.


我们代码中的很多条件检查都是由于被诅咒的 none/null 泄漏到我们的代码中而产生的,所以值得一提的是非常流行的 null 对象模式。

执着于文字,而不是意义

当遵循反如果时,你可能会走上错误的道路,因为执着于文字而不是意义,并盲目地遵循“如果是坏的,如果必须被删除”的想法。

由于条件语句是语义而不是语法元素,因此有无数种方法可以从代码中删除 if 标记,而无需更改我们喜爱的编程语言中的底层逻辑

我在这里讨论的不是用 match/case 替换 python 中的 elif 链。

逻辑条件源于系统的心理“模型”,并且没有通用方法可以完全“删除”条件。

换句话说,圈复杂度和整体代码复杂度与代码的物理表示(文件中写入的字母和符号)无关。

复杂性来自于形式表达口头或文字解释特定代码为何以及如何工作。

因此,如果我们更改代码中的某些内容,并且 if 语句更少或根本没有,但相同代码的口头解释保持不变,我们所做的只是更改代码的表示,并且更改本身并没有真正的意义或做出任何改进。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

310

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

222

2025.10.31

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

237

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

479

2024.03.01

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

784

2023.08.22

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

191

2025.11.08

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号