避免条件语句的智慧
来源:dev.to
时间:2024-08-15 12:45:45 498浏览 收藏
今天golang学习网给大家带来了《避免条件语句的智慧》,其中涉及到的知识点包括等等,无论你是小白还是老手,都适合看一看哦~有好的建议也欢迎大家在评论留言,若是看完有所收获,也希望大家能多多点赞支持呀!一起加油学习~
循环复杂度是衡量代码复杂性和混乱程度的指标。
高圈复杂度并不是一件好事,恰恰相反。
简单来说,圈复杂度与程序中可能的执行路径的数量成正比。换句话说,圈复杂度和条件语句的总数(尤其是它们的嵌套)密切相关。
所以今天我们来谈谈条件语句。
反如果
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 的权衡是常识的结果,使代码变得更好。
这里最大的区别是,从机械角度来说,在自动驾驶仪上,我们不太可能使用这些方法,除非我们已经内化并养成了通过这一原则进行思考的习惯。
否则,我们会自动陷入 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 语句更少或根本没有,但相同代码的口头解释保持不变,我们所做的只是更改代码的表示,并且更改本身并没有真正的意义或做出任何改进。
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《避免条件语句的智慧》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
498 收藏
-
457 收藏
-
385 收藏
-
206 收藏
-
439 收藏
-
466 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习