5 python refactoring tips to improve your code

[Introduction] Refactoring is the process of improving code quality without changing the code function. Spending some time to refactor the code before submitting it can make the code more readable and therefore easier to maintain. Continue to perform this operation will find errors and add new features from the nightmarish hell scene to the park walk.

Ideally, unit tests will completely cover your code, allowing you to make changes with confidence. If not, you should add tests before refactoring whenever possible. However, if you cannot add tests, you can still achieve many goals by using automated, safe refactoring tools.

So where do you start? This article contains 5 simple things that you can do right away to make your code more readable and more beautiful.

1. Delete the commented out code

This is the simplest refactoring, and can produce excellent results for your code base.

The commented out code is confusing. It bloats the code base and makes it difficult to follow the true execution path of the code. It will also hinder the actual comments, and these comments may be lost in a large number of outdated lines of code.

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

In such an example, you may lose important information when reading the code because you try to skip comments.

If you are worried that the commented out code will be useful someday, storing it in source control is the correct approach. If you need it, it will be in the commit history. Just give the submission a suitable name in the place where the comment is deleted, they are easy to find.

Most importantly, as long as you carefully delete only commented out lines, this refactoring will never break your code.

Second, extract numbers and strings

During the development process, it is usually easier to write string literals and numbers directly into the code. However, leaving them is a good way to solve the problem.

If the number or string changes later, you need to find every instance of the text, check whether it needs to be changed, and then change it. Repeatedly changing the repetitive code in this way is one of the main causes of errors, because it is easy to miss one of the usages. Replacing them with constants means that the text is stored in one place and only needs to be changed in that place.

Another problem is that a number like the number in this example cannot tell you why it has a value or purpose.

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

Replace it with a constant and let you give it a descriptive name, which makes the code easier to read and understand.

MAX_LINE_LENGTH = 80


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

When it comes to actually making changes, many IDEs will help you extract the text with the "Extract Constants" option.

Three, delete duplicates

DRY (don't repeat yourself) is one of the basic principles of software development. Repetition makes the code harder to understand, and when the repeated code starts to create ambiguities, it often leads to errors.

An easy way to remove duplicates is if you find an opportunity to improve the code. This is where the code is repeatedly executed on the two branches of the condition, so it can be used externally.

Example 01 There are many repetitions in variables, as follows:

  • Before reconstruction
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
  • After reconstruction
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

Example 02 It is more common that the code is repeated in different parts of a function or in two different functions.

Here, you need to extract the code into another function, give it a meaningful name, and then call it instead of the extracted code. as follows:

  • Before reconstruction
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

Take a closer look. Below is a clear repetitive code that pops up in three places:

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

This should be extracted into a function. Many IDEs allow you to select code snippets and extract them automatically, and some PyCharm etc. will also scan your code to see which parts can be replaced by calling new functions. Let's extract this method and call it next_player, because it will move on current_player to the next valid value.

  • After reconstruction
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

There is still a lot of refactoring to be done on this code, but it can definitely be improved. It can be read more clearly. If next_player() must change the function, we only need to change the code in one place instead of three places.

As you do more coding and refactoring, you will learn more and more opportunities to delete duplicates. Typically, you will need to process multiple pieces of code to make them more similar, and then extract common elements into functions.

Fourth, split large functions

The longer the function, the more difficult it is to read and understand. An important aspect of well-written functions is that they should do one thing and have a name that accurately reflects their function-this is the so-called "single responsibility principle." Longer functions tend to do many different things.

Opinions differ on how long it should be kept, but in general the shorter the better. The rule of cast iron is that it should definitely fit on a page of your code editor. You must scroll up and down to see what the function does, which increases the cognitive burden required to understand the function.

In order to split a function, you need to extract parts of it into other functions, as described in the previous section.

Let's look at some code to understand how to proceed:

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

Here we can see that the calls are divided into three categories-those that call methods on the kettle, teapot and cup respectively. According to our expertise (the British may be helpful for this), these also correspond to the three stages of making a cup of tea-boiling the kettle, brewing the tea and then pouring it into the cup and eating it.

Let's start from the end and extract content related to pouring a cup of tea and serving it to the table. You can do this manually, or you can use the "Extract Method" feature of the 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

At this stage, multiple levels of abstraction are mixed in our make_tea function-compared to the multiple steps involved in executing the pour_tea function, opening the kettle is a simpler operation. Ideally, each line in our function should be at a similar level, making them easier to parse into a coherent statement. To achieve this, let's go ahead and extract the other two functions.

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

Check out the top make_tea function, you can now read three short stories, just like making tea. If you are interested in the details of any stage, you can learn more about the relevant methods.

When splitting functions, it is best to identify logically consistent code bits that can perform something together. A rule of thumb is that if you can think of a good name related to the domain name for the new function, it might be a good idea to extract it.

Five, move the local variable declaration to a position close to its usage

It is important to ensure that as few variables as possible must be processed when reading any particular code segment. Keeping the tightness and scope of variable declarations and their usage helps this. Basically, the closer a variable is declared to its usage, the fewer times it will scan up and down when reading the code later.

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

When reading this code, you must hunger the variable in mind from the beginning to the end of the function, because it can be used or changed anywhere.

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

Refactoring to move it to the scope where it is used can solve this problem. Now we only need hunger to consider within the appropriate scope.

Guess you like

Origin blog.csdn.net/Lin_Hv/article/details/110823579