5个python重构技巧以改进您的代码

[导语] 重构是在不更改代码功能的情况下提高代码质量的过程。在提交代码之前花一些时间来重构代码可以使代码更具可读性,从而更易于维护。持续执行此操作会发现错误,并从噩梦般的地狱场景添加新功能到公园散步。

理想情况下,单元测试将完全覆盖您的代码,使您可以放心地进行更改。如果不是,那么应该尽可能在重构之前添加测试。但是,如果您无法添加测试,则仍然可以通过使用自动化,安全的重构工具来实现很多目标。

那么从哪里开始呢?本文包含5件简单的事情,您可以立即做,以使您的代码更易读,更美观。

一、删​​除注释掉的代码

这是最简单的重构,并且可以为您的代码库产生出色的结果。

注释掉的代码令人困惑。它会使代码库膨胀,并使其难以遵循代码的真实执行路径。它也会妨碍真正的注释,而这些注释可能会在大量过时的代码行中丢失。

# lots of
#
# commented out lines of code
do_something_important()
#
# even more
# commented out code

在这样的示例中,由于试图跳过注释,因此在阅读代码时可能会丢失重要信息。

如果您担心注释掉的代码有一天会有用,则将其存储在源代码管理中是正确的方法。如果您需要它,它将在提交历史记录中。只需在删除注释的地方给提交一个合适的名称即可,它们很容易找到。

最重要的是,只要您谨慎删除仅注释掉的行,这种重构就永远不会破坏您的代码。

二、提取数字和字符串

在开发过程中,将字符串文字和数字直接写入代码中通常会更容易。但是,离开他们却是解决问题的良方。

如果数字或字符串以后更改,则需要查找文字的每个实例,检查是否需要更改然后更改它。以这种方式重复更改重复代码是导致错误的主要原因之一,因为很容易错过其中一种用法。用常量替换它们意味着文字存储在一个地方,只需要在那个地方进行更改即可。

另一个问题是,像本例中的数字这样的数字不能告诉您为什么它具有值或用途。

扫描二维码关注公众号,回复: 12346763 查看本文章
def update_text(text):
    if len(text) > 80:
        wrap(text)

用常量代替它,让您为它指定一个描述性名称,这使代码更易于阅读和理解。

MAX_LINE_LENGTH = 80


def update_text(text):
    if len(text) > MAX_LINE_LENGTH:
        wrap(text)

当涉及到实际进行更改时,许多IDE将通过“提取常量”选项来帮助您提取文字。

三、删除重复项

DRY(不要重复自己)是软件开发的基本原则之一。重复使代码更难理解,并且当重复的代码开始产生歧义时,通常会导致错误。

删除重复项的一种简单方法是,如果您发现有提升代码的机会。这是在条件的两个分支上重复执行代码的位置,因此可以在外部使用。

例子01 变量存在很多重复的地方,如下:

  • 重构前
def get_bm_page(self, account, proxy, page_id, user_id):
    url_owned = f"https://test.com/v8.0/{user_id}/owned_pages?fields=id,name&access_token=Easas..&limit=100"
    owner_page= self.check_page_id(url_owned, account, proxy, page_id, user_id)
    if not owner_page:
        url_client = f"https://test.com/v8.0/{user_id}/client_pages?fields=id,name&access_token=Easas..&limit=100"
        owner_page self.check_page_id(url_owned, account, proxy, page_id, user_id)
    return owner_page
  • 重构后
def get_bm_page_id(account, proxy, page_id, user_id):
    base_url = "https://test.com/v8.0/{user_id}/%s?fields=id,name&access_token={access_token}&limit=100".format(bm_id='12',access_token='Easas..')
    owner_page = False
    for page_type in ['owned_pages', 'client_pages']:
        owner_page = check_page_id(base_url % page_type, account, proxy, page_id, user_id)
        if owner_page:
            break
    return owner_page

例子02 更常见的是,代码在函数的不同部分或两个不同的函数中重复。

在这里,您需要将代码提取到另一个函数中,为其指定一个有意义的名称,然后调用它而不是提取的代码。如下:

  • 重构前
class Game:

    # ...

    def was_correctly_answered(self):
        if self.not_penalised():
            print('Answer was correct!!!!')
            self.purses[self.current_player] += 1
            print(
                self.players[self.current_player]
                + ' now has '
                + str(self.purses[self.current_player])
                + ' Gold Coins.'
            )

            winner = self._did_player_win()
            self.current_player += 1
            if self.current_player == len(self.players):
                self.current_player = 0

            return winner
        else:
            self.current_player += 1
            if self.current_player == len(self.players):
                self.current_player = 0
            return True

    def wrong_answer(self):
        print('Question was incorrectly answered')
        print(self.players[self.current_player] + " was sent to the penalty box")
        self.in_penalty_box[self.current_player] = True

        self.current_player += 1
        if self.current_player == len(self.players):
            self.current_player = 0
        return True

仔细看一下,下面是一段清晰的重复代码,它在三个地方弹出:

self.current_player += 1
if self.current_player == len(self.players):
    self.current_player = 0

这应该提取到一个函数中。许多IDE可以让您选择代码片段并自动将其提取,而某些PyCharm等也将扫描您的代码,以查看可以通过调用新功能替换哪些部分。让我们提取此方法并调用它next_player,因为它将继续前进current_player到下一个有效值。

  • 重构后
class Game:

    # ...

    def was_correctly_answered(self):
        if self.not_penalised():
            print('Answer was correct!!!!')
            self.purses[self.current_player] += 1
            print(
                self.players[self.current_player]
                + ' now has '
                + str(self.purses[self.current_player])
                + ' Gold Coins.'
            )

            winner = self._did_player_win()
            self.next_player()

            return winner
        else:
            self.next_player()
            return True

    def wrong_answer(self):
        print('Question was incorrectly answered')
        print(self.players[self.current_player] + " was sent to the penalty box")
        self.in_penalty_box[self.current_player] = True

        self.next_player()
        return True

    def next_player(self):
        self.current_player += 1
        if self.current_player == len(self.players):
            self.current_player = 0

在此代码上还有很多重构需要完成,但是绝对可以改善。它的读取更清晰,如果next_player()必须更改函数,我们只需要在一个位置而不是三个位置更改代码即可。

随着您进行更多的编码和重构,您将学到越来越多的机会删除重复项。通常,您将需要处理多段代码以使它们更加相似,然后将通用元素提取到函数中。

四、拆分大型功能

功能越长,阅读和理解就越困难。编写良好功能的一个重要方面是,它们应该做一件事,并且要具有一个准确反映其功能的名称-这就是所谓的“单一责任原则”。较长的功能更倾向于做很多不同的事情。

关于应保留多长时间的意见不一,但总的来说越短越好。铸铁的规则是,它绝对应该适合您的代码编辑器的一页。必须上下滚动以查看功能的作用,这会增加理解该功能所需的认知负担。

为了拆分功能,您需要将其部分提取到其他功能中,如上一节所述。

让我们看一些代码,以了解如何进行:

def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
    kettle.fill(tap)
    kettle.switch_on()
    kettle.wait_until_boiling()
    boiled_kettle = kettle.pick_up()
    teapot.add(tea_bag)
    teapot.add(boiled_kettle.pour())
    teapot.wait_until_brewed()
    full_teapot = teapot.pick_up()
    cup.add(full_teapot.pour())
    cup.add(milk)
    cup.add(sugar)
    cup.stir()
    return cup

在这里我们可以看到调用分为三类-分别调用水壶,茶壶和杯子上的方法的那些。根据我们的专业知识(英国人可能会对此有所帮助),这些也对应于制作一杯茶的三个阶段-将水壶煮沸,冲泡茶然后倒入杯子中并食用。

让我们从结尾开始,并提取与倒出一杯茶并上桌有关的内容。您可以手动执行此操作,也可以使用IDE的“提取方法”功能。

def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
    kettle.fill(tap)
    kettle.switch_on()
    kettle.wait_until_boiling()
    boiled_kettle = kettle.pick_up()
    teapot.add(tea_bag)
    teapot.add(boiled_kettle.pour())
    teapot.wait_until_brewed()
    full_teapot = teapot.pick_up()
    return pour_tea(cup, full_teapot, milk, sugar)


def pour_tea(cup, full_teapot, milk, sugar):
    cup.add(full_teapot.pour())
    cup.add(milk)
    cup.add(sugar)
    cup.stir()
    return cup

在此阶段,我们的make_tea功能中混合了多个抽象级别-与执行pour_tea功能中涉及的多个步骤相比,打开水壶是一个更简单的操作。理想情况下,我们函数中的每一行都应该处于相似的级别,从而使它们更易于解析为连贯的叙述。为了实现这一点,让我们继续并提取其他两个功能。

def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
    boiled_kettle = boil_water(kettle, tap)
    full_teapot = brew_tea(boiled_kettle, tea_bag, teapot)
    return pour_tea(cup, full_teapot, milk, sugar)


def boil_water(kettle, tap):
    kettle.fill(tap)
    kettle.switch_on()
    kettle.wait_until_boiling()
    return kettle.pick_up()


def brew_tea(boiled_kettle, tea_bag, teapot):
    teapot.add(tea_bag)
    teapot.add(boiled_kettle.pour())
    teapot.wait_until_brewed()
    return teapot.pick_up()


def pour_tea(cup, full_teapot, milk, sugar):
    cup.add(full_teapot.pour())
    cup.add(milk)
    cup.add(sugar)
    cup.stir()
    return cup

查看顶级make_tea功能,您现在可以阅读三个小故事,就像泡茶一样。如果您对任何阶段的细节都感兴趣,则可以深入了解相关方法。

拆分功能时,最好确定逻辑上一致的代码位,这些位可以一起执行某件事。一条经验法则是,如果您可以为新函数想到一个与域名相关的好名称,那么提取它可能是一个好主意。

五、将局部变量声明移至与其用法接近的位置

确保在读取任何特定代码段时必须处理尽可能少的变量非常重要。保持变量声明的紧密性和范围及其用法有助于此。基本上,变量声明得越接近其用法,在以后阅读代码时向上和向下扫描的次数就越少。

def assess_fruit(self, fruit):
    happiness = 0
    hunger = time_since_breakfast() / size_of_breakfast()
    some_other_code()
    # work out some things
    do_some_other_things()
    if is_snack_time() and isinstance(fruit, Apple):
        yumminess = fruit.size * fruit.ripeness ** 2
        happiness += hunger * yumminess
    return happiness

阅读此代码时,您必须hunger从函数的开头到结尾牢记变量,因为可以在任何地方使用或更改它。

def assess_fruit(self, fruit):
    happiness = 0
    some_other_code()
    # work out some things
    do_some_other_things()
    if is_snack_time() and isinstance(fruit, Apple):
        hunger = time_since_breakfast() / size_of_breakfast()
        yumminess = fruit.size * fruit.ripeness ** 2
        happiness += hunger * yumminess
    return happiness

重构以将其移至使用它的作用域即可解决此问题。现在我们只需要hunger在适当的范围内考虑。

猜你喜欢

转载自blog.csdn.net/Lin_Hv/article/details/110823579