学习过程中,我们经常会阅读他人写的代码,如果注意观察就会发现,好的代码本身就是一份文档,解决同样的问题,不同的人编写的代码,其可读性千差万别。
有些人的设计风格和代码风格犹如热刀切黄油,从顶层到底层的代码看下来酣畅淋漓,注释详尽又精简;深入到细节代码,无需注释也能理解清清楚楚。而有些人,代码勉勉强强能跑起来,遇到稍微复杂的情况就会出崩溃,且代码中处处都是堆积在一起的变量、函数和类,很难理清代码的实现思路。
Python 创始人 Guido van Rossum(吉多·范罗苏姆)说过,代码的阅读频率远高于编写代码的频率。毕竟是在编写代码的时候,我们自己也需要对代码进行反复阅读和调试,来确认代码能够按照期望运行。
本节,在读者学会如何使用 Puython 函数的基础上,教大家怎么才能合理分解代码,提高代码的可读性。
首先,大家在编程过程中,一定要围绕一个中心思想:不写重复性的代码。因为,重复代码往往是可以通过使用条件、循环、构造函数和类(后续章节会做详细介绍)来解决的。
例如,仔细观察下面的代码:
- if i_am_rich:
- money = 100
- send(money)
- else:
- money = 10
- send(money)
这段代码中,同样的 send 语句出现了两次,其实它完全可以进行合并,把代码改造成下面这样:
- if i_am_rich:
- money = 100
- else:
- money = 10
- send(money)
与此同时,还要学会刻意地减少代码的迭代层数,尽可能让 Python 代码扁平化。例如:
- def send(money):
- if is_server_dead:
- LOG('server dead')
- return
- else:
- if is_server_timed_out:
- LOG('server timed out')
- return
- else:
- result = get_result_from_server()
- if result == MONEY_IS_NOT_ENOUGH:
- LOG('you do not have enough money')
- return
- else:
- if result == TRANSACTION_SUCCEED:
- LOG('OK')
- return
- else:
- LOG('something wrong')
- return
上面这段代码层层缩进,如果我们没有比较强的逻辑分析能力,理清这段代码是比较困难。其实,这段代码完全可以改成如下这样:
- def send(money):
- if is_server_dead:
- LOG('server dead')
- return
-
- if is_server_timed_out:
- LOG('server timed out')
- return
-
- result = get_result_from_server()
-
- if result == MONET_IS_NOT_ENOUGH:
- LOG('you do not have enough money')
- return
-
- if result == TRANSACTION_SUCCEED:
- LOG('OK')
- return
-
- LOG('something wrong')
可以看到,所有的判断语句都位于同一层级,同之前的代码格式相比,代码层次清晰了很多。
另外,在使用函数时,函数的粒度应该尽可能细,不要让一个函数做太多的事情。往往一个复杂的函数,我们要尽可能地把它拆分成几个功能简单的函数,然后合并起来。
如何拆分函数呢?这里,举一个二分搜索的例子。给定一个非递减整数数组,和一个 target 值,要求你找到数组中最小的一个数 x,满足 x*x > target,如果不存在,则返回 -1。
大家不妨先独立完成,写完后再对照着来看下面的代码,找出自己的问题:
- def solve(arr, target):
- l, r = 0, len(arr) - 1
- ret = -1
- while l <= r:
- m = (l + r) // 2
- if arr[m] * arr[m] > target:
- ret = m
- r = m - 1
- else:
- l = m + 1
- if ret == -1:
- return -1
- else:
- return arr[ret]
-
- print(solve([1, 2, 3, 4, 5, 6], 8))
- print(solve([1, 2, 3, 4, 5, 6], 9))
- print(solve([1, 2, 3, 4, 5, 6], 0))
- print(solve([1, 2, 3, 4, 5, 6], 40))
对于上面这样的写法,应付算法比赛和面试已经绰绰有余。但如果从工程的角度考虑,还需要进行深度优化:
- def comp(x, target):
- return x * x > target
-
- def binary_search(arr, target):
- l, r = 0, len(arr) - 1
- ret = -1
- while l <= r:
- m = (l + r) // 2
- if comp(arr[m], target):
- ret = m
- r = m - 1
- else:
- l = m + 1
- return ret
-
- def solve(arr, target):
- id = binary_search(arr, target)
- if id != -1:
- return arr[id]
- return -1
-
- print(solve([1, 2, 3, 4, 5, 6], 8))
- print(solve([1, 2, 3, 4, 5, 6], 9))
- print(solve([1, 2, 3, 4, 5, 6], 0))
- print(solve([1, 2, 3, 4, 5, 6], 40))
在这段代码中,我们把不同功能的代码单独提取出来作为独立的函数。其中,comp() 函数作为核心判断,提取出来之后,可以让整个程序更清晰;同时,还把二分搜索的主程序提取了出来,只负责二分搜索;最后的 solve() 函数拿到结果,决定返回不存在,还是返回值。这样一来,每个函数各司其职,阅读性也能得到一定提高。