学习过程中,我们经常会阅读他人写的代码,如果注意观察就会发现,好的代码本身就是一份文档,解决同样的问题,不同的人编写的代码,其可读性千差万别。
有些人的设计风格和代码风格犹如热刀切黄油,从顶层到底层的代码看下来酣畅淋漓,注释详尽又精简;深入到细节代码,无需注释也能理解清清楚楚。而有些人,代码勉勉强强能跑起来,遇到稍微复杂的情况就会出崩溃,且代码中处处都是堆积在一起的变量、函数和类,很难理清代码的实现思路。
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() 函数拿到结果,决定返回不存在,还是返回值。这样一来,每个函数各司其职,阅读性也能得到一定提高。