历史重构方法:

Python重构(一)Python重构(二)
编写干净的,Python式的代码就是要尽可能地使其易于理解,但又要简明扼要。以下是有关Python重构的系列文章的第三部分,第一部分见此,第二部分见此。本系列的重点是为什么这样做是好的做法,而不仅仅是教你如何做。

1. 以卫语句替代嵌套条件表达式

深度嵌套的函数很难理解。在阅读它们时,您必须记住每个嵌套级别所包含的条件。这在Python中可能更加困难,因为没有方括号来定义条件块。减少嵌套的一种简单方法是将条件转换为保护子句。

条件表达式通常有两种表现形式。第一种是所有分支都属于正常行为,第二种规则是条件表达式提供的答案只有一种是正常行为,其它都属于不常见的情况。

这两类条件表达式有不同的用途,这一点通过代码可以表现出来。如果两条分支都是正常行为,就应该使用if-then-else的条件表达式,如果某个条件极其罕见,就应该单独检查该条件,并应该在条件为真时立刻从函数中返回。这样的单独检查常常被称为”卫语句“(guard clauses)

以卫语句替代嵌套条件表达式的精髓就是:给某一个分支以特别的重视。如果使用if-then-else结构,你对if分支和else分支重视是同等的。这样的代码结构传递给阅读者的意思就是:各个分支有同样的重要性。卫语句就不同的,它告诉读者:”这种情况很罕见,如果它真的发生了,请做一些必要的清理动作,然后退出。“

作为一个例子,让我们看看这个函数:

def should_i_wear_this_hat(self, hat):
    if isinstance(hat, Hat):
        current_fashion = FASHION_API.get_fashion(FASHION_TYPE.HAT)
        weather_outside = self.look_out_of_window()
        is_stylish = self.evaluate_style(hat, current_fashion)
        if weather_outside.is_raining:
            print("Damn.")
            return True
        else:
            print("Great.")
            return is_stylish
    else:
        return False

考虑到嵌套的两层,这是很难阅读与理解。当我到达底部的else时,我需要在它和顶部的if测试之间来回弹来弹去,然后我才明白发生了什么。这个if条件是检查边界情况,其中传入的不是hat。因为我们在这里返回了False,所以这是引入卫语句的好地方:

def should_i_wear_this_hat(self, hat):
    if not isinstance(hat, Hat):
        return False

    current_fashion = get_fashion()
    weather_outside = self.look_out_of_window()
    is_stylish = self.evaluate_style(hat, current_fashion)
    if weather_outside.is_raining:
        print("Damn.")
        return True
    else:
        print("Great.")
        return is_stylish

减少了嵌套级别后,函数的其余部分现在更容易阅读了。是否添加卫语句有时是主观的。对于非常短的函数,可能不值得这样做。当函数较长或较复杂时,它通常是一个有用的重构工具。

2. 交换if/else以移除空的if body

我们有时看到的一种模式是条件句,其中没有在主体中发生任何事情,而所有的动作都在else子句中。

if location == OUTSIDE:
    pass
else:
    take_off_hat()

在这种情况下,我们可以通过交换主体和其他内容使代码更短、更简洁。我们必须确保将条件倒转,然后else子句中的逻辑移到主体中。

if location != OUTSIDE:
   take_off_hat()
else:
   pass

然后,我们有一个不执行任何操作的else子句,因此可以删除它。

if location != OUTSIDE:
    take_off_hat()

这样更容易阅读,条件的意图也更清晰。当阅读代码时,我不需要在头脑中转换它来理解它,因为我已经这样做了。

3. 合并并追加值到list中

当声明一个列表并需要填充值时,一种很自然的方法是声明它为空,然后向它追加内容。

hats_i_own = []
hats_i_own.append('panama')
hats_i_own.append('baseball_cap')
hats_i_own.append('bowler')

这可以就地完成,缩短代码并使代码意思更加明确。现在我只需要浏览一行代码,就可以看到我用hats填充了一个变量,而不是用四个。

hats_i_own = ['panama', 'baseball_cap', 'bowler']

这样做还稍微提高了性能,因为它避免了频繁调用append方法。对于填充其他集合类型(如集合和字典)也是如此。

4. 将赋值操作移到离使用它更近的地方(就近原则)

局部变量的作用域应严格限制。这意味着:

  • 在函数中不需要变量的地方,也就不需要声明变量。这将减少理解代码的负担。
  • 如果变量的声明和使用它的代码在一起,那么拆分函数更容易。这样函数的方法代码更短,也容易理解。
  • 如果变量的声明远离使用的地方,那么它们可能会被搁置在那儿。如果使用这些变量的代码在以后被更改或删除,那么不使用的变量就会闲置在那里,使代码不必要的负责化。

让我们再看看之前的示例。

def should_i_wear_this_hat(self, hat):
    if not isinstance(hat, Hat):
        return False

    current_fashion = get_fashion()
    weather_outside = self.look_out_of_window()
    is_stylish = self.evaluate_style(hat, current_fashion)
    if weather_outside.is_raining:
        print("Damn.")
        return True
    else:
        print("Great.")
        return is_stylish

如果天是下雨的,直接返回True,其实不需要is_stylish变量。可以将它移到else子句中。那么current_fashion变量也可以移动,它只在这里使用。您确实需要检查这些变量是否在稍后的函数中使用,如果函数保持简短和简洁,这将变得更容易。

将赋值移动到current_fashion还可以避免在天气下雨时调用函数,如果这是一个开销很大的调用,则可以提高性能。

def should_i_wear_this_hat(self, hat):
    if not isinstance(hat, Hat):
        return False

    weather_outside = self.look_out_of_window()
    if weather_outside.is_raining:
        print("Damn.")
        return True
    else:
        print("Great.")
        current_fashion = get_fashion()
        is_stylish = self.evaluate_style(hat, current_fashion)
        return is_stylish

实际上,我们可以更进一步,内联is_stylish变量。这表明,小的重构通常可以相互构建,并导致进一步的改进。

def should_i_wear_this_hat(self, hat):
    if not isinstance(hat, Hat):
        return False

    weather_outside = self.look_out_of_window()
    if weather_outside.is_raining:
        print("Damn.")
        return True
    else:
        print("Great.")
        current_fashion = get_fashion()
        return self.evaluate_style(hat, current_fashion)

5. 使用items()直接解包dictionary值

在遍历dictionary时一个很好的小技巧是使用items()同时访问键和值。请看下面两段代码的示例。
Before:

hats_by_colour = {'blue': ['panama', 'baseball_cap']}
for hat_colour in hats_by_colour:
    hats = hats_by_colour[hat_colour]
    if hat_colour in self.favourite_colours:
        think_about_wearing(hats)

After:

hats_by_colour = {'blue': ['panama', 'baseball_cap']}
for hat_colour, hats in hats_by_colour.items():
    if hat_colour in self.favourite_colours:
        think_about_wearing(hats)

上面的代码节省了我们用于分配给hats的代码行,并将其合并到for循环中。现在,代码读起来更自然,重复更少。

6. 简化序列比较

在实现某些业务时经常会遇到检查一个列表或者序列中是否有该元素

if len(list_of_hats) > 0:
    hat_to_wear = choose_hat(list_of_hats)

在Python中,如果列表和序列中有元素,那么它们的计算结果为True,否则为False。

那么上面的代码可以简化为下面的代码:

if list_of_hats:
    hat_to_wear = choose_hat(list_of_hats)

这已经是约定俗成的写法,成为Python的PEP8风格指南中推荐做法。一旦习惯了这样做,它确实会使代码更容易阅读,也不会那么混乱