- Software Architecture with Python
- Anand Balachandran Pillai
- 772字
- 2021-07-02 23:29:56
Refactoring code
Now that we have seen how static checkers can be used to report a wide range of errors and issues in our Python code, let's do a simple exercise of refactoring our code. We will take our poorly written metric test module as the use case (the first version of it) and perform a few refactoring steps.
Here are the rough guidelines to follow when refactoring software:
- Fix complex code first: This will get a lot of code out of the way as typically, when a complex piece of code is refactored, we end up reducing the number of lines of code. This overall improves the code quality and reduces code smells. You may be creating new functions or classes here, so it always helps to perform this step first.
- Do an analysis of the code: It is a good idea to run the complexity checkers at this step and see how the overall complexity of the code—class/module or functions—has been reduced. If not, iterate again.
- Fix code smells next: Fix any issue with code smells—class, function, or module—next. This gets your code into a much better shape and improves the overall semantics.
- Run checkers: Run checkers such as Pylint on the code now, and get a report on the code smells. Ideally, they should be close to zero or reduced very much from the original.
- Fix low-hanging fruits: Fix low-hanging fruits, such as code style and convention errors, last. This is because, in the process of refactoring, when trying to reduce complexity and code smells, you typically would introduce or delete a lot of code. So, it doesn't make sense to try and improve the code convention issues at earlier stages.
- Perform a final check using the tools: You can run Pylint for code smells, Flake8 for PEP-8 conventions, and Pyflakes for catching the logic, syntax, and missing variable issues.
Here is a step-by-step demonstration of fixing our metric test module using this approach in the next section.
Refactoring code – fixing complexity
Most of the complexity is in the office route function, so let's try and fix it. Here is the rewritten version (showing only that function here):
def find_optimal_route_to_my_office_from_home(start_time, expected_time, favorite_route='SBS1K', favorite_option='bus'): d = (expected_time - start_time).total_seconds()/60.0 if d<=30: return 'car' elif d<45: return ('car', 'metro') elif d<60: # First volvo,then connecting bus return ('bus:335E','bus:connector') elif d>80: # Might as well go by normal bus return random.choice(('bus:330','bus:331',':'.join((favorite_option, favorite_route)))) # Relax and choose favorite route return ':'.join((favorite_option, favorite_route))
In the preceding rewrite, we got rid of the redundant if...else
conditions. Let's check the complexity now:

mccabe metric of metric test program after refactoring step 1
We were able to reduce the complexity from 7
to 5
. Can we do better?
In the following piece of code, the code is rewritten to use ranges of values as keys, and the corresponding return value as values. This simplifies our code a lot. Also, the earlier default return at the end would never have got picked, so it is removed now, hence getting rid of a branch and reducing complexity by one. The code has become much simpler:
def find_optimal_route_to_my_office_from_home(start_time, expected_time, favorite_route='SBS1K', favorite_option='bus'): # If I am very late, always drive. d = (expected_time – start_time).total_seconds()/60.0 options = { range(0,30): 'car', range(30, 45): ('car','metro'), range(45, 60): ('bus:335E','bus:connector') } if d<80: # Pick the range it falls into for drange in options: if d in drange: return drange[d] # Might as well go by normal bus return random.choice(('bus:330','bus:331',':'.join((favorite_option, favorite_route))))

mccabe metric of metric test program after refactoring step #2
The complexity of the function is now reduced to 4
, which is manageable.
Refactoring code – fixing code smells
The next step is to fix code smells. Thankfully, we have a very good list from the previous analysis, so this is not too difficult. Mostly, we need to change function names and variable names and fix the contracts from child class to parent class.
Here is the code with all of the fixes:
""" Module metrictest.py - testing static quality metrics of Python code """ import random def sum_fn(xnum, ynum): """ A function which performs a sum """ return xnum + ynum def find_optimal_route(start_time, expected_time, favorite_route='SBS1K', favorite_option='bus'): """ Find optimal route for me to go from home to office """ # Time difference in minutes - inputs must be datetime instances tdiff = (expected_time - start_time).total_seconds()/60.0 options = {range(0, 30): 'car', range(30, 45): ('car', 'metro'), range(45, 60): ('bus:335E', 'bus:connector')} if tdiff < 80: # Pick the range it falls into for drange in options: if tdiff in drange: return drange[tdiff] # Might as well go by normal bus return random.choice(('bus:330', 'bus:331', ':'.join((favorite_option, favorite_route)))) class MiscClassC(object): """ A miscellaneous class with some utility methods """ def __init__(self, xnum, ynum): self.xnum = xnum self.ynum = ynum def compare_and_sum(self, xnum=0, ynum=0): """ Compare local and argument variables and perform some sums """ if self.xnum > xnum: return self.xnum + self.ynum else: return xnum + self.ynum class MiscClassD(MiscClassC): """ Sub-class of MiscClassC overriding some methods """ def __init__(self, xnum, ynum=0): super(MiscClassD, self).__init__(xnum, ynum) def some_func(self, xnum, ynum): """ A function which does summing """ if xnum > ynum: return xnum - ynum else: return xnum + ynum def compare_and_sum(self, xnum=0, ynum=0): """ Compare local and argument variables and perform some sums """ if self.xnum > ynum: return self.xnum + ynum else: return ynum - self.xnum
Let's run Pylint on this code and see what it outputs this time:

Pylint output of refactored metric test program
You see that the number of code smells has boiled down to near zero except a complaint of lack of public
methods, and the insight that the some_func
method of the MiscClassD
class can be a function, as it does not use any attributes of the class.
Note
We have invoked Pylint with the –reports=n
option in order to avoid Pylint printing its summary report, as it would make the entire output too long to display here. These reports can be enabled by calling Pylint without any argument.
Refactoring code – fixing styling and coding issues
Now that we have fixed the major code issues, the next step is to fix code style and convention errors. However, in order to shorten the number of steps and the amount of code to be printed in this book for this exercise, this was already merged along with the last step, as you may have guessed from the output of Pylint.
Except for a few whitespace warnings, all of the issues are fixed.
This completes our Refactoring exercise.
- Vue 3移動Web開發與性能調優實戰
- Node.js 10實戰
- PHP 從入門到項目實踐(超值版)
- ASP.NET Core 2 and Vue.js
- 云原生Spring實戰
- Learn Scala Programming
- Java程序設計
- 零基礎學Python網絡爬蟲案例實戰全流程詳解(入門與提高篇)
- 大數據分析與應用實戰:統計機器學習之數據導向編程
- C語言程序設計簡明教程:Qt實戰
- Serverless Web Applications with React and Firebase
- C指針原理揭秘:基于底層實現機制
- Ionic3與CodePush初探:支持跨平臺與熱更新的App開發技術
- C/C++程序設計教程
- 微信公眾平臺開發最佳實踐