编写可重用代码
我发现自己经常需要反复修改旧代码,以适应不同的需求,有时候甚至是为了实现两次之前的同样功能。
举个例子,有一个处理质数的函数。有时候我需要的是前n个质数的列表,有时候我需要的是第n个质数。也许将来我还会遇到第三种需求。
无论我怎么做,我都得执行相同的过程,只是返回不同的值。我在想,难道没有比不断修改同一段代码更好的方法吗?我想到了一些可能的替代方案:
返回一个元组或列表,但这样看起来有点乱,因为里面会有各种数据类型,包括成千上万的列表。
使用输入语句来引导代码,不过我更希望在点击运行时它能自动完成所有操作。
想办法利用类的特性来返回类的属性,并在需要的地方访问它们。对我来说,这似乎是最干净的解决方案,但我不太确定,因为我还是个新手。
干脆为每个可重用的函数做五个版本。
我不想成为一个糟糕的程序员,那么哪个选择是正确的呢?或者说,也许还有我没想到的其他方法。
3 个回答
尽量把你的函数简化到最小,并且多次使用它们。
比如,你可能有一个叫做 next_prime
的函数,它会被 n_primes
和 n_th_prime
这两个函数反复调用。
这样做还能让你的代码更容易维护,如果你想出一种更高效的方法来计算质数,你只需要改动 next_prime
里的代码就行了。
此外,你的输出应该尽量保持中立。如果你的函数返回多个值,最好返回一个 list
或者生成器,而不是用逗号分隔的字符串。
我的猜测是,创建一个模块,里面包含:
- 一个私有的核心功能(比如:返回前n个质数的列表,或者更通用的功能)
- 几个包装/工具函数,这些函数使用核心功能,并以不同的方式准备输出。(比如:获取第n个质数)
模块化、可重用的代码
你的问题确实很重要。这是程序员日常工作中常常会遇到的问题。这个问题是:
我的代码可重用吗?
如果不可重用,你就会遇到代码重复的问题,也就是在多个地方写了相同的代码。这是产生错误的最佳起点。想象一下,如果你想改变某个功能,比如发现了潜在的问题。你在一个地方做了修改,但可能会忘记在另一个地方也要改。尤其是当你的代码行数达到1,000、10,000或100,000行时,这个问题会更加严重。
这就是单一职责原则(Single-Responsibility-Principle)的核心思想。它的意思是每个类(函数也是如此)应该只负责一件事情,也就是“只做一件事”。如果一个函数做了多件事,你应该把它拆分成更小的部分,处理更小的任务。
每当你遇到(或写)一个超过10或20行实际代码的函数时,你应该保持怀疑态度。这样的函数很少能遵循这个原则。
在你的例子中,你可以将任务分解为以下几个部分:
- 逐个生成质数(生成意味着使用
yield
) - 收集
n
个质数。使用第1步的结果并放入一个列表中 - 获取第
n
个质数。使用第1步的结果,但不保存每个数字,只等待第n
个。这样占用的内存比第2步少。 - 寻找质数对:使用第1步,记住上一个数字,如果当前数字与上一个数字的差是2,就返回这一对
- 收集所有质数对:使用第4步的结果并放入一个列表中
- ...
- ...
这个列表是可以扩展的,你可以在任何层面上重用它。每个函数不会超过10行代码,这样你就不会每次都重新发明轮子。
把它们放到一个模块里,从每个与质数相关的欧拉问题脚本中使用它。
总的来说,我为我的欧拉问题脚本开始了一个小库。你真的可以习惯在“Project Euler”中编写可重用的代码。
关键字参数
你没有提到的另一个选项(就我理解的)是使用可选的关键字参数。如果你觉得小而简单的函数太复杂(虽然我真的建议你习惯使用),你可以添加一个关键字参数来控制返回值。例如,在一些scipy
函数中,有一个参数full_output
,它是一个布尔值。如果是False(默认值),只返回最重要的信息(例如,一个优化后的值);如果是True,还会返回一些补充信息,比如优化的效果如何,以及收敛需要多少次迭代。
你可以定义一个参数output_mode
,可能的值有"list"
、"last"
或其他。
建议
坚持使用小而可重用的代码块。习惯这个是你在“Project Euler”中能学到的最有价值的事情之一。
备注
如果你尝试实现我提议的可重用函数模式,你可能会在第一点遇到一个问题:如何为此创建一个生成器风格的函数?例如,如果你使用筛法。但这并不是太大的问题。