避免条件语句的智慧
循环复杂度是衡量代码复杂性和混乱程度的指标。
高圈复杂度并不是一件好事,恰恰相反。
简单地说,圈复杂度与程序中可能的执行路径的数量成正比。换句话说,圈复杂度和条件语句的总数(尤其是它们的嵌套)密切相关。
今天我们来谈谈条件语句。
反如果
2007 年,Francesco Cirillo发起了一场名为 Anti-if 的运动。
Francesco Cirillo 是发明番茄工作法的人。我现在正在“番茄钟下”写这篇博文。
我想我们都很快从这个活动的名字就明白了它的含义。有趣的是,该运动的追随者中有不少计算机科学家。
他们的论点坚如磐石——如果语句是邪恶的,会导致程序执行路径呈指数级增长。
简而言之,这就是圈复杂度。它越高,不仅阅读和理解代码就越困难,而且用测试来覆盖它也就越困难。
当然,我们有一种“相反”的指标——代码覆盖率,它显示了测试覆盖了多少代码。但是,这个指标以及我们编程语言中用于检查覆盖率的丰富工具是否证明忽略圈复杂度并仅基于“本能”散布 if 语句是合理的?
我想不会。
几乎每次我发现自己要将一个 if 嵌套在另一个 if 中时,我都会意识到我正在做一些非常愚蠢的事情,可以用不同的方式重写 - 要么没有嵌套的 if ,要么根本没有 if 。
你确实注意到了“几乎”这个词,对吧?
我并没有立即开始注意到这一点。如果您查看我的 GitHub,您会发现不止一个旧代码示例,它们不仅具有高圈复杂度,而且具有直接的圈复杂度。
是什么让我更加意识到这个问题?可能是我一年前学到和接受的经验和一些聪明的事情。这就是我今天想跟大家分享的内容。
破坏 if 语句的两种神圣技术
- Padawan,将未知值的每个条件检查移至该值已知的位置。
- 学徒,改变你的编码逻辑思维模型,让它不再需要条件检查。
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 也没关系——都是同样的垃圾!
很容易将其重写为:
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 的权衡是常识的结果,使代码变得更好。
The big difference here is that mechanically, on autopilot, we are unlikely to use these approaches unless we've internalized and developed the habit of thinking through the lens of this principle.
Otherwise, we'll automatically fall into if: if: elif: if...
2. Free Your Mind, Neo
In fact, the second technique is the only real one, and the earlier "first" technique is just preparatory practices, a shortcut for getting in place :)
Indeed, the only ultimate way, method — call it what you will — to achieve simpler code, reduce cyclomatic complexity, and cut down on conditional checks is making a shift in the mental models we build in our minds to solve specific problems.
I promise, one last silly example for today.
Consider that we're urgently writing a backend for some online store where user can make purchases without registration, or with it.
Of course, the system has a User class/entity, and finishing with something like this is easy:
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
But noticing this nonsense, thanks to the fact that our thinking has already shifted in the right direction (I believe), we'll go back to where the User class is defined and rewrite part of the code in something like this:
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
So, the essence and beauty of it all is that we don't clutter our minds with various patterns and coding techniques to eliminate conditional statements and so on.
By shifting our focus to the meta-level, to a higher level of abstraction than just the level of reasoning about lines of code, and following the idea we've discussed today, the right way to eliminate conditional checks and, in general, more correct code will naturally emerge.
A lot of conditional checks in our code arise from the cursed None/Null leaking into our code, so it's worth mentioning the quite popular Null Object pattern.
Clinging to Words, Not Meaning
When following Anti-if, you can go down the wrong path by clinging to words rather than meaning and blindly following the idea that "if is bad, if must be removed.”
Since conditional statements are semantic rather than syntactic elements, there are countless ways to remove the if token from your code without changing the underlying logic in our beloved programming languages.
Replacing an elif chain in Python with a match/case isn’t what I’m talking about here.
Logical conditions stem from the mental “model” of the system, and there’s no universal way to "just remove" conditionals entirely.
In other words, cyclomatic complexity and overall code complexity aren’t tied to the physical representation of the code — the letters and symbols written in a file.
The complexity comes from the formal expression, the verbal or textual explanation of why and how specific code works.
So if we change something in the code, and there are fewer if statements or none at all, but the verbal explanation of same code remains the same, all we’ve done is change the representation of the code, and the change itself doesn’t really mean anything or make any improvement.
以上是避免条件语句的智慧的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Python在游戏和GUI开发中表现出色。1)游戏开发使用Pygame,提供绘图、音频等功能,适合创建2D游戏。2)GUI开发可选择Tkinter或PyQt,Tkinter简单易用,PyQt功能丰富,适合专业开发。

Python更易学且易用,C 则更强大但复杂。1.Python语法简洁,适合初学者,动态类型和自动内存管理使其易用,但可能导致运行时错误。2.C 提供低级控制和高级特性,适合高性能应用,但学习门槛高,需手动管理内存和类型安全。

要在有限的时间内最大化学习Python的效率,可以使用Python的datetime、time和schedule模块。1.datetime模块用于记录和规划学习时间。2.time模块帮助设置学习和休息时间。3.schedule模块自动化安排每周学习任务。

Python在开发效率上优于C ,但C 在执行性能上更高。1.Python的简洁语法和丰富库提高开发效率。2.C 的编译型特性和硬件控制提升执行性能。选择时需根据项目需求权衡开发速度与执行效率。

pythonlistsarepartofthestAndArdLibrary,herilearRaysarenot.listsarebuilt-In,多功能,和Rused ForStoringCollections,而EasaraySaraySaraySaraysaraySaraySaraysaraySaraysarrayModuleandleandleandlesscommonlyusedDduetolimitedFunctionalityFunctionalityFunctionality。

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

每天学习Python两个小时是否足够?这取决于你的目标和学习方法。1)制定清晰的学习计划,2)选择合适的学习资源和方法,3)动手实践和复习巩固,可以在这段时间内逐步掌握Python的基本知识和高级功能。

Python和C 各有优势,选择应基于项目需求。1)Python适合快速开发和数据处理,因其简洁语法和动态类型。2)C 适用于高性能和系统编程,因其静态类型和手动内存管理。
