马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 小甲鱼的二师兄 于 2023-12-4 04:22 编辑
最近圈子里总在传这个图:
很多兄弟顿时悟了 —— 所谓 “防御性编程”,就是确保自己被裁掉之后,代码无人能维护的编程实践!
好家伙……
其实,正儿八经确实是有这个词汇的:
防御性编程(Defensive programming)是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。
它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要用于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。
简单地说,防御性编程就像是为你的代码穿上了保护装备,让它在面对各种突发情况(比如错误的输入或者意外的行为)时,能够稳定地运行而不是崩溃。
下面我将结合实例,给大家解释一下在实际开发中,如何进行防御性编程实践。
一个简单的例子是,你的代码需要一个非零的数字进行除法运算。
在防御性编程中,你要先检查这个数字确实是非零的,然后再进行除法:
def divide(x, y):
if y == 0:
print("错了:0 不能作除数。")
return
return x / y
如果 y 为零,我们打印出一个错误信息,并直接返回,避免了除零的错误。这就是防御性编程的一个简单例子。
另一个防御性编程的例子:使用断言。
断言(assert)是一种在代码中设置检查点的方式,这通常用于检查那些 “绝对不能出错” 的条件。
如果断言条件为假,那么程序就会在那里停止,并抛出一个 AssertionError 异常。
例如,我们有一个函数,用于计算一个人的年龄。这个函数接受两个参数:出生年份和当前年份。
在这里,我们可以假设当前年份应该大于或等于出生年份。我们可以使用断言来检查这个条件:
def calculate_age(birth_year, current_year):
assert current_year >= birth_year, "当前年份不能小于出生年份~"
return current_year - birth_year
在防御性编程中,我们通常需要处理可能出现的错误或异常,以防止程序在遇到问题时崩溃。
Python 中,我们可以使用 try/except 语句来捕获和处理异常。在 try 块中的代码可能会抛出异常,如果确实抛出了异常,那么就会立即跳到 except 块中的代码去处理这个异常。
例如,我们有一个函数,用于从网络上下载文件。假设在网络连接有问题的情况下,这个函数可能会抛出一个 NetworkError 异常。
我们可以使用 try/except 来捕获这个异常,并打印出一个错误信息:
def download_fishc(url):
try:
# 这里的代码可能会抛出 NetworkError
do_some_network_operation(url)
except NetworkError:
print(f"下载失败!")
在上面的代码中,如果 do_some_network_operation(url) 这行代码抛出了 NetworkError 异常,那么程序就会立即跳到 except NetworkError: 这一行,然后执行 print(f"下载失败!") 这行代码。
这样,即使在网络连接有问题的情况下,我们的程序也不会崩溃,而是打印出一个错误信息,并继续执行后面的代码。
我们再来看看日志记录。
在防御性编程中,保留详细的日志是非常重要的,因为它可以帮助我们了解系统在运行时的状态,以及当问题出现时提供详细的上下文信息。
Python 中,我们可以使用 logging 模块来记录日志。
比如,我们可以记录一个函数被调用的时间,以及它的输入参数:
import logging
def calculate_age(birth_year, current_year):
logging.info(f"calculate_age called with {birth_year}, {current_year}")
return current_year - birth_year
这样,如果我们的程序在运行时出现了问题,我们就可以查看日志,了解问题出现时的具体情况。
最后是单元测试,单元测试是检查代码功能是否正常的一种方法,每一个单元测试都关注于程序的一个小部分功能。
写好单元测试可以帮助我们确认代码的质量,并在修改或者添加新特性时,保证现有功能的稳定性。这也是防御性编程的重要组成部分。
在 Python 中,我们可以使用内置的 unittest 模块来编写单元测试。
例如,我们有一个函数,用于计算两个数的和。
我们可以写一个单元测试来确认这个函数的功能是否正常:
import unittest
def add(x, y):
return x + y
class TestAdd(unittest.TestCase):
def test_add(self):
result = add(1, 2)
self.assertEqual(result, 3)
if __name__ == "__main__":
unittest.main()
在上面的代码中,TestAdd 类是一个单元测试类,它继承自 unittest.TestCase 类。test_add 方法是一个测试方法,它调用了 add 函数,并检查了 add 函数的返回值是否为预期的值。
如果 add 函数的返回值不是预期的值,那么 self.assertEqual(result, 3) 这行代码就会抛出一个异常,表明这个测试失败了。如果所有的测试方法都没有抛出异常,那么我们就可以说我们的代码通过了所有的单元测试。
通过这种方式,我们可以为我们的代码编写多个单元测试,以检查代码的各个部分是否都能正常工作。
这是防御性编程的一个重要组成部分,也是保证代码质量的一种有效方法。
然而,重要的是要理解,防御性编程并不是要你在每个可能出错的地方都添加异常处理或者日志记录。
因为过度使用防御性编程可能会导致代码冗余,可读性差,且难以维护。
使用防御性编程不仅是为了防止错误,更是一种编程思想,目的是让代码更健壮,更易于维护。
最佳的做法是在编程时就考虑到可能出现的问题,而不是在问题出现后再去修复。
这也是为什么单元测试如此重要,通过单元测试,我们可以在代码还在开发阶段就发现并修复问题,而不是等到代码部署到生产环境后再去解决。
总的来说,防御性编程是一个包含了参数检查、断言、异常处理、日志记录和单元测试等多种策略的编程方法,目的是让代码更健壮,更易于维护。
在编程时,我们应该根据实际的需要,适度地使用这些策略。
|