函数长度
让你读一个200行的函数,并说出它是做什么的,你是什么感受?
函数的长度直接影响可读性,从而影响可维护性。所以要保持你的函数简短。50行是一个随意的数字,在我看来是合理的。你编写的大多数函数应该要短一些。
如果一个函数遵循单一功能原则,它很可能是相当短的。如果它是纯函数或是幂等的(下面讨论) ,它也可能是短的。
那么,如果函数太长,应该怎么做?重构。这会改变程序的结构而不改变其行为。
从一个长函数中提取几行代码,并把它们变成自己的函数。这是缩短长函数的最快、也是最常见的方式。
加上你给所有这些新函数取了合适的名称,因此生成的代码读起来也会更容易。
幂等和函数纯度
不管被调用了多少次,幂等函数总是在给定相同参数集的情况下返回相同的值。
结果不依赖于非局部变量、参数的可变性或来自任何I / O流的数据。下面的这个add_three(number)函数是幂等函数:
def add_three(number): """Return *number* + 3.""" return number + 3
不管一个人调用add_three(7)多少次,答案总是10。以下是一个非幂等函数:
defadd_three():
"""Return 3 + the number entered by the user."""
number = int(input('Enter a number: '))
return number + 3
这个函数的返回值取决于I / O,即用户输入的数字。对add_three()的每次调用都会返回不同的值。
如果它被调用两次,用户可以第一次输入3,第二次输入7,分别调用add_three()返回6和10。
幂等性的一个现实中例子是在电梯前点击“向上”按钮。第一次按时,电梯会被“通知”你要上去。因为按按钮是幂等的,所以反复按它都没有什么影响。结果是一样的。
为什么幂等很重要?
可维护性和可维护性。幂等函数很容易测试,因为在使用相同的参数时,它们总是返回相同的结果。
测试仅仅是检查通过不同调用返回值的预期值。更重要的是,这些测试很快,这是单元测试中一个重要且经常被忽视的问题。
而在处理幂等函数时,重构是轻而易举的事情。无论如何在函数之外更改代码,使用相同的参数调用它的结果总是一样的。
什么是纯函数?
在函数编程中,如果一个函数既幂等又没有可观察到的副作用,它就被认为是纯函数。函数外部的任何东西都不会影响这个值。
然而,这并不意味着函数不能影响非局部变量或I / O流之类的事情。例如,如果上面add_three(number)的幂等版本在返回结果之前打印了结果,那么它仍然被认为是幂等的,因为当它访问I / O流时,这个访问与从函数返回的值无关。
调用print ( )只是一个副作用:除了返回值之外,还与程序的其他部分或系统本身进行了一些交互。
让我们把我们的add_three(number)示例再向前推进一步。我们可以编写下面的代码片段来确定调用add_three(number)的次数:
add_three_calls = 0
defadd_three(number):
"""Return *number* + 3."""
global add_three_calls
print(f'Returning {number + 3}')
add_three_calls += 1
return number + 3
defnum_calls():
"""Return the number of times *add_three* was called."""
return add_three_calls
我们现在正在打印到控制台(一个副作用)并修改一个非局部变量(另一个副作用),但是由于这两者都不影响函数返回的值,它仍然是幂等的。
纯函数没有副作用。它不仅不使用任何“外部数据”来计算值,除了计算和返回所述值之外,它与系统/程序的其余部分都没有交互。因此,虽然我们新的add_three(number)定义仍然是幂等的,但它不再是纯的。
纯函数没有日志语句或print ( )调用。它们不使用数据库或互联网连接。它们不访问或修改非局部变量。它们不调用任何其他非纯函数。
简而言之,它们无法做到爱因斯坦所说的“远距离幽灵般的行动”(在计算机科学环境中)。它们不会以任何方式修改程序或系统的其余部分。
在命令式编程(编写Python代码时所做的那种)中,它们是所有函数中最安全的函数。
它们也很容易被测试和维护,甚至比只是幂等函数更重要的是,测试它们基本上可以和执行它们一样快。
测试本身很简单:没有数据库连接或其他外部资源进行模拟,也不需要安装代码,之后也没有什么需要清理的。
明确地说,幂等性和纯函数只是一种期望,不是必需的。也就是说,由于好处很多,我们可能会希望只编写纯函数或幂等函数,但这不现实。
重要的是,我们要有意识开始写代码来隔离副作用和外部依赖性。这会使得我们编写的每一行代码都更容易被测试。