学会合理分解代码,提高代码可读性
有句话说得好,好的代码本身就是一份文档。同样功能的一份程序,一个组件,一套系统,让不同的人来写,写出来的代码却是千差万别。
有些人的设计风格和代码风格犹如热刀切黄油,从顶层到底层的代码看下来酣畅淋漓,注释详尽而又精简;深入到细节代码,无需注释也能理解清清楚楚。
而有些人,代码勉勉强强能跑起来,遇到稍微复杂的情况可能就会出 bug;深入到代码中 debug,则发现处处都是魔术数、函数堆在一起。一个文件上千行,设计模式又是混淆不堪,让人实在很难阅读,更别提修改和迭代开发。
Guido van Rossum(吉多·范罗苏姆,Python 创始人 )说过,代码的阅读频率远高于编写代码的频率。毕竟,即使是在编写代码的时候,你也需要对代码进行反复阅读和调试,来确认代码能够按照期望运行。
PEP 8 规范
前面课程中我们简单提起过 PEP 8 ,本节课我们来简单复习一下。
PEP 是 Python Enhancement Proposal 的缩写,翻译过来叫“Python 增强规范”。正如我们写文章,会有句式、标点、段落格式、开头缩进等标准的规范一样,Python 书写自然也有一套较为官方的规范。PEP 8 就是这样一种规范,它存在的意义,就是让 Python 更易阅读,换句话,增强代码可读性。
事实上,Pycharm 已经内置了 PEP 8 规范检测器,它会自动对编码不规范的地方进行检查,然后指出错误,并推荐修改方式。下面这张图就是其界面。
因此,在学习今天的内容时,我推荐你使用 Pycharm IDE 进行代码检查,看一下自己的代码格式哪里有问题。尤其对于初学者,从某些程度来说,代码规范甚至是比代码准确更重要的事情,因为实际工作中,代码可读性的重要性一定比你想象的多得多。
缩进规范
空行规范
空格规范
换行规范
文档规范
注释规范
文档描述
命名规范
代码分解技巧
本节课我们再讲一些很实用的代码优化技巧。
编程中一个核心思想是,不写重复代码。重复代码大概率可以通过使用条件、循环、构造函数和类来解决。而另一个核心思想则是,减少迭代层数,尽可能让 Python 代码扁平化,毕竟,人的大脑无法处理过多的栈操作。
所以,在很多业务逻辑比较复杂的地方,就需要我们加入大量的判断和循环。不过,这些一旦没写好,程序看起来就是地狱了。
我们来看下面几个示例,来说说写好判断、循环的细节问题。先来看第一段代码:
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)
再来看一个例子:
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))
我给出的第一段代码这样的写法,在算法比赛和面试中已经 OK 了。不过,从工程角度来说,我们还能继续优化一下:
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() 函数拿到结果,决定返回不存在,还是返回值。这样一来,每个函数各司其职,阅读性也能得到一定提高。
最后,我们再来看一下如何拆分类。老规矩,先看代码:
class Person:
def __init__(self, name, sex, age, job_title, job_description, company_name):
self.name = name
self.sex = sex
self.age = age
self.job_title = job_title
self.job_description = description
self.company_name = company_name
你应该能看得出来,job 在其中出现了很多次,而且它们表达的是一个意义实体,这种情况下,我们可以考虑将这部分分解出来,作为单独的类。
class Person:
def __init__(self, name, sex, age, job_title, job_description, company_name):
self.name = name
self.sex = sex
self.age = age
self.job = Job(job_title, job_description, company_name)
class Job:
def __init__(self, job_title, job_description, company_name):
self.job_title = job_title
self.job_description = description
self.company_name = company_name
你看,改造后的代码,瞬间就清晰了很多。
总结
在我们学习了编程规范之后重要的是在你未来的开发中注重和准守编程规范
并在必要时合理分解代码,提高代码可读性