共计 6494 个字符,预计需要花费 17 分钟才能阅读完成。
简介:为什么要强调 书写标准 ?这其实并不关乎“美丑”,而是为了 更高的效率(代码浏览、开发、保护)与更不便的单干(全球通用的规范)。现在,不论什么方向的同学都要进行“写代码”这项工作,惋惜的是,很多敌人并没有意识到:破费 1 小时理解代码书写标准,能够为本人节俭 100+ 小时的写代码的工夫。 代码标准的魅力在于 实实在在地简化问题,并不需要咱们奉为圭臬或引起争执。本文咱们次要以 python 为例,以 pep8 为次要参考资料,分三个档次进行探讨。
本文三个档次:
- 不留神这些,你写的基本不是代码
- 这些标准,本质是尊重程序的逻辑
- 一些我会疏忽的标准,更多的思考
不留神这些,你写的基本不是代码
1/2 来看看我两年前的代码
import tkinter | |
import tkinter.messagebox | |
def message_box(error_content): | |
top = tkinter.Tk() # ********* | |
top.withdraw() # **** 实现主窗口暗藏 | |
top.update() # ********* 须要 update 一下 | |
txt=tkinter.messagebox.showinfo("提醒",error_content) | |
top.destroy() | |
return txt |
上述是在用 python
的 tkinter
做一个桌面利用,看起来仿佛没什么问题?大问题,比方我的项目逻辑 / 设计架构 确实难以通过这么几行代码看进去;然而,下面这几行代码的书写习惯,反映了一个人的思维不够成熟:
- 正文不须要
# ****
这么书写,齐全没必要 须要 update 一下
这是废话,后面不就是update()
函数吗?("提醒",error_content)
两头应该打空格txt=tkinter
左右两边应该加空格
如果让当初的我来写,我会如下实现:
import tkinter | |
from tkinter import messagebox as msg | |
def show_message_box(error_content): | |
""" | |
Piper 蛋窝:输出错误信息,messagebox 显示信息(皮一下:欢送关注 Piper 蛋窝~)""" | |
tk = tkinter.Tk() | |
tk.withdraw() | |
tk.update() | |
txt = msg.showinfo("提醒", error_content) | |
tk.destroy() | |
return txt |
如上:
- 我改掉了些小故障,比方有没有空格等,但这其实不是重点
- 我把函数名从
message_box
改为了show_message_box
,因为message_box
看起来像一个名词,并不是动词(去执行一项工作),在我的项目结构复杂后,咱们可能有很多函数、类、实例,如果 不做动名词辨别,咱们本人都可能混同,还要翻回源码进行查阅,节约大量工夫 - 我把正文(这个函数负责干什么)放在了
"""正文"""
里,这样解释器与 InelliSense 会在咱们调用时,做主动的阐明,如下图
2/2 最根本的:缩进、命名与空间
敌人,如果你写代码时连 缩进、命名与空间
这三点都不会留神到,那祝贺你,这篇文章很有可能让你晋升一个阶段。
我就见过刚刚学 c 的大一小朋友,在机房的 VC 6 里一个一个地敲下字符:
#include <stdio.h> | |
int main() | |
{int a=5,b=7,c; | |
printf("a=%d,b=%d,c=%d",a,b,c); | |
return 0;} |
很显然,这位小朋友的代码真的仅仅是“敲进电脑的字符”而已,ta 心里齐全没有程序的逻辑、档次。这代码是死的,不是活的。我仅仅加一些空格和回车,来解释,为什么这些缩进、命名与空间让代码成为真正的代码。
#include <stdio.h> | |
int main() | |
{ | |
int a = 5, b = 7, c; | |
printf("a = %d, b = %d, c = %d", a, b, c); | |
return 0; | |
} |
如上:
- 我把
{}
独立,并对其中代码块做了缩进,示意这些代码是函数main()
外部的逻辑 - 我加了空格,如把
a=5
变成了a = 5
,是因为程序员也是人,也须要读看得清晰的货色 - 我在
#include <stdio.h>
与int main()
间加了空行,因为这二者是两件事:前者负责引入 io 规范库,后者负责执行逻辑。在写代码时,不要悭吝空行,来辨别不同的逻辑与工作
下面的探讨是不是过于根底?上面咱们以 python 以及其官网文档 pep 8 为例,来看看更多体现程序逻辑、加强代码可读性的官网倡议。
如果下回有人再让你看 ta 写的“死代码”,你先把这篇文章扔给 ta,让 ta 改好!
这些标准,本质是尊重程序的逻辑
1/4 缩进与空格:体现逻辑
Indentation 缩进
驰名美剧《硅谷》里有个情节,程序员会因为应用制表符还是空格吵得不可开交,仿佛是一个原则性问题?
这当然只是玩笑。
在 python 中,举荐应用 4 个空格来进行缩进。我在打 kdd cup 是见过 2 个空格示意缩进的(官网 start toolkit 里)。我感觉这是无所谓的,要害是,你要在我的项目里进行对立。
此外,缩进是用来体现程序结构的,如果你的构造不是蕴含关系,仅仅是换行,那么也用 4 个空格缩进将很愚昧。 如下。
# 举荐 | |
foo = long_function_name(var_one, var_two, | |
var_three, var_four) | |
# 或者 | |
foo = long_function_name(var_one, var_two, | |
var_three, var_four) | |
# 或者 | |
foo = long_function_name(var_one, var_two, | |
var_three, var_four) | |
# 或者 | |
def long_function_name( | |
var_one, var_two, var_three, | |
var_four): | |
print(var_one) | |
# 愚昧 | |
foo = long_function_name(var_one, var_two, | |
var_three, var_four) | |
# 愚昧 | |
def long_function_name( | |
var_one, var_two, var_three, | |
var_four): | |
print(var_one) |
为什么?咱们以最上面的 def long_function_name
为例,留神到了吗:
print(var_one)
应该是def long_function_name
的内部结构,因而必须比def long_function_name
前多一个缩进- 然而
var_one, var_two, var_three
这些参数是def long_function_name
的一部分,与def long_function_name
同级 - 函数参数与函数内部结构对齐,就很容易让人混同
因而,你只须要 制作区分度 就能够。至于你是多加两个空格,还是缩小两个,这个无所谓(除非你们组织做了特定的规定), 能让你和他人一眼看出逻辑构造就好。
Should a line break before or after a binary operator?
此外,pep 8 还举荐了 operator 的地位,这个我以前真的没有留神到。
# No: operators sit far away from their operands | |
income = (gross_wages + | |
taxable_interest + | |
(dividends - qualified_dividends) - | |
ira_deduction - | |
student_loan_interest) | |
# Yes: easy to match operators with operands | |
income = (gross_wages | |
+ taxable_interest | |
+ (dividends - qualified_dividends) | |
- ira_deduction | |
- student_loan_interest) |
此外,为了避免编译谬误,我会在换行时加上 \\
示意将换行键本义。
income = (gross_wages \ | |
+ taxable_interest \ | |
+ (dividends - qualified_dividends) \ | |
- ira_deduction \ | |
- student_loan_interest) |
当初看来,可能这种状况下没有这个必要。
回绝无意义的空格
Yes: spam(ham[1], {eggs: 2}) | |
No: spam(ham[ 1], {eggs: 2} ) | |
Yes: if x == 4: print x, y; x, y = y, x | |
No: if x == 4 : print x , y ; x , y = y , x |
如上是 Pet Peeves 中的倡议,很显然,过于涣散的构造,也不利于咱们浏览和看出逻辑。这是一种所有编程语言通用的习惯,值得养成。
函数变量省缺值
# Yes | |
def complex(real, imag=0.0): | |
return magic(r=real, i=imag) | |
# No | |
def complex(real, imag = 0.0): | |
return magic(r = real, i = imag) |
如上,在定义某个省缺值时,咱们激励去掉 =
左右的空格。
# Yes | |
def munge(sep: AnyStr = None): ... | |
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ... | |
# No | |
def munge(input: AnyStr=None): ... | |
def munge(input: AnyStr, limit = 1000): ... |
然而,如上,值得注意的是,如果定义了函数的数据类型(如 AnyStr
),则咱们须要揭示开发者留神辨别,不要去掉 =
左右的空格。
2/4 import
Yes: import os | |
import sys | |
No: import sys, os | |
# Yes | |
i = i + 1 | |
submitted += 1 | |
x = x*2 - 1 | |
hypot2 = x*x + y*y | |
c = (a+b) * (a-b) | |
# No | |
i=i+1 | |
submitted +=1 | |
x = x * 2 - 1 | |
hypot2 = x * x + y * y | |
c = (a + b) * (a - b) |
对于 import 的标准有几条,我这里着重强调一个老手可能都会有的“坏习惯”:把毫不相干的库放在一个 import 下。
图什么呢?没有任何意义。比方下面,sys
其实与 os
没有任何容纳关系,咱们何必悭吝这一行 import
呢?且放在一起,不利于 formatter
帮咱们整顿书写。
3/4 变量、函数名称
选一个好的命名规定
不同的企业 / 组织,尤其是大型的企业,会有一套本人的命名标准。对于 java,我记得大家最罕用的应该是“驼峰”式命名:
public void printUserName(int index) {...}
在 python,激励各种通用模式的命名,如:
printUserName
print_user_name
我感觉大家在 python 中最罕用的是 下划线 + 小写 的模式。
在 pep 8 的 Descriptive: Naming Styles 有个标注,很搞笑:
Capitalized_Words_With_Underscores (ugly!)
为什么说 Capitalized_Words_With_Underscores
“丑”呢?
我感觉是 信息冗余 了:下划线或者大写首字母,都是用于距离单词的,两个都应用,真的不简洁、不优雅。
“公有”变量
在 C++ 或者 java 中,咱们都会接触到 private
这类概念。初学者可能会一头雾水:为什么变量要分为公有的、公共的、受爱护的?
python 让初学者避开了这部分可能产生的费解,然而又没有去掉公有变量等性能,我感觉这正是 pythonic 的体现。 而且,当初的语言都有此趋势,比方 go
,限度首字母大小写辨别变量公有共有,简洁优雅,又对立了社区开发标准。
对于 python,咱们在变量前加了两个下划线,则其变为公有了。公有变量为了我的项目的标准与平安,不能被内部调用,我写了一段程序如下。
如上,间接调用 Foo.__a
或者 foo.__b
会产生 AttributeError 谬误。然而 python 给了个“后门”,你依然能够通过 _类名__变量名
调用公有变量。
4/4 尊重“人类的思维”
# Yes | |
if foo is not None: | |
# No | |
if not foo is None: |
如上,或者老手会感觉 not ... is
这种构造逻辑来了个反转,很好玩,只管其作用与 is not
完全相同。
还是那句话,程序员也是人,大家都喜爱简略清晰的逻辑。除非是很奇妙的技巧,否则,没有必要玩文字游戏,升高效率。
# Yes | |
try: | |
value = collection[key] | |
except KeyError: | |
return key_not_found(key) | |
else: | |
return handle_value(value) | |
# No | |
try: | |
# Too broad! | |
return handle_value(collection[key]) | |
except KeyError: | |
# Will also catch KeyError raised by handle_value() | |
return key_not_found(key) |
如上,在应用 try catch 时,咱们想捕获 KeyError,那就不要在 try 中进行别的操作。为什么?
- 如果
handle_value()
自身有错,那么咱们很难通过handle_value(collection[key])
捕获其本人的谬误,因为其与collection[key]
可能呈现的谬误混同在了一起
# Yes | |
def foo(x): | |
if x >= 0: | |
return math.sqrt(x) | |
else: | |
return None | |
def bar(x): | |
if x < 0: | |
return None | |
return math.sqrt(x) | |
# No | |
def foo(x): | |
if x >= 0: | |
return math.sqrt(x) | |
def bar(x): | |
if x < 0: | |
return | |
return math.sqrt(x) |
如上,x
小于 0 时,无奈开平方,天然也就无奈输出 math.sqrt()
。这里咱们应该让程序更加清晰一些,只管咱们晓得如果一个函数什么都不做返回的是 None
,然而也不要不写 return None
。用 pep 8 的话说,咱们应该 be consistent in return statements.
一些我会疏忽的标准,更多的思考
1/3 每行最多字符数?I say NO.
咱们晓得,pep 8 心愿咱们每行 最多 79 个字符。
我感觉对于我这种开发者来说,真的没必要。而且,我读过很多优良的开源框架,其也没有尊重这个规范。
起因很简略,咱们的生产环境不同。
我喜爱大字体,而且我只有一个小小的笔记本电脑,连工位都没有。很多敌人领有好几块屏幕 / 加长屏幕,而我只能把一块小小的笔记本显示器竖着一分为二。如下图。
无论是 79 个字符,还是 109 个字符,我的编辑器都不能一行显示下来,因而这条标准对我来说意义不大。
或者开发 python 规范库时会用上。但不是当初。
2/3 正文文件规范?我复议
pep 8 与 pep 257 中,都对正文形式进行了标准化,如下。
def complex(real=0.0, imag=0.0): | |
"""Form a complex number. | |
Keyword arguments: | |
real -- the real part (default 0.0) | |
imag -- the imaginary part (default 0.0) | |
""" | |
if imag == 0.0 and real == 0.0: | |
return complex_zero | |
... |
我并不是很感兴趣。起因:
- 我记得 pyCharm 中,默认的正文并不是这样的,阐明规范并不是惟一的
- 具体应用什么规范,我感觉要 就事论事,比方 MkDocs 会帮咱们主动地把正文编译成文档并公布在网上,因而咱们想要应用 MkDocs 时,去学习 MkDocs 的标准便好
如上是 thu-ml/tianshou 源码,其正文应用 markdown 写的,如果去拜访阐明文档,你会发现阐明文档就是依据源代码正文主动生成的。
Amazing. 这才是“写代码的”该有的美德:懈怠,不做反复的动作,能自动化就自动化。
3/3 读标准文档不如多读好我的项目
最初,光说不练假花样。
在我看来,多读优良的代码、我的项目,无意识地留神高手的书写标准和其怎么安顿我的项目构造,意义远比只读 pep 8 要大很多。
祝各位变得更强。欢送关注公众号:Piper 蛋窝
,回复 微信
加我微信。欢送 点赞
、点击 在看
激励我。