最近看到这段代码,你觉得有没有问题呢?
def process_data(data, flag1, flag2, flag3):
if flag1:
data = [item for item in data if item.isdigit()]
if flag2:
data = [item.upper() for item in data]
if flag3:
data = sorted(data)
result = "Processed: " + ", ".join(data)
return result
乍一看,这段代码似乎并不太糟糕,对吧?但我却不这么人为。
1. 太多标志
它使用了太多标志(flag1、flag2、flag3)来控制行为,这使得函数有点令人困惑。
每次我们调用函数时,都得弄清楚哪种标志组合会产生期望的结果。
让我用这个例子来解释:
res = process_data(my_data, True, False, True)
你明白True, False, True是什么意思吗?上面的函数也是如此。必须细看逻辑,再三调用调试之后才敢调用。
2. 缺乏单一职责
这个函数试图同时做很多事情,比如过滤数字、将文本转换为大写、排序数据以及将数据连接成字符串。
我们可以为每个任务编写单独的函数。因为单一职责的函数更容易阅读、测试和重用。
3. 缺乏类型注解
在这个函数中,没有任何解释。数据是什么类型?是列表、字符串还是字典?
我们得猜。
4. 扩展困难
例如,如果我们想添加另一个操作,比如修剪空白或任何其他操作,那么我们就得添加另一个标志。
这会使函数变得更加混乱。
如何优化
我尝试以清晰简单的方式重写这个函数。
1. 使用描述性的函数名
而不是为每个任务使用标志,我创建了单独的函数,如下所示:
def filter_digits(data):
return [item for item in data if item.isdigit()]
def to_uppercase(data):
return [item.upper() for item in data]
def sort_data(data):
return sorted(data)
def format_result(data):
return "Processed: " + ", ".join(data)
现在每个函数只做一件事。
2. 创建一个管道
然后,我尝试将这些函数连接在一起,采用类似管道的方法。这使得它更加清晰。
def process_data_pipeline(data):
data = filter_digits(data)
data = to_uppercase(data)
data = sort_data(data)
return format_result(data)
3. 使其更加灵活
我们可以使用一个函数列表来动态应用。如果我们要跳过某些步骤,这将非常有用。
def process_data(data, steps):
for step in steps:
data = step(data)
return data
steps = [filter_digits, to_uppercase, sort_data, format_result]
result = process_data(my_data, steps)
现在我们完全控制了步骤。我们可以包含或排除步骤,而无需使用标志。
4. 添加类型注解
我只是添加了类型注解,使代码更易读,减少错误。
from typing import List, Callable
def process_data(data: List[str], steps: List[Callable[[List[str]], List[str]]]) -> str:
for step in steps:
data = step(data)
return data
为什么这么改
-
每个函数都应该只有一个明确的目的。以增加函数的可读性。
-
如果我们想在其他地方过滤数字,我们只需调用filter_digits。这有助于函数的可重用性。
-
如果我们想测试这个函数,现在更容易了。测试单个函数比测试重载函数更容易。
-
现在,它的可扩展性提高了。如果我们想添加新步骤,我们可以简单地编写一个新函数并将其附加到列表中。
总结
所以,下次你写函数时,问问自己这些问题:
它是不是试图做太多事情?如果别人要理解这段代码,他是否不需要详细的解释就能理解?
如果答案是**“不”**,那么你需要重构这段代码。
你是否也有类似心得,可以说来听听。