在这篇文章中,我们将谈论清洁代码的好处,不同的代码标准和原则,以及如何编写清洁代码的一般准则。
什么是干净的代码?
清洁代码是一套规则和原则,有助于保持我们的代码可读、可维护和可扩展。它是编写高质量软件的最重要方面之一。我们(开发人员)花在阅读代码上的时间远远多于实际写代码的时间,这就是为什么我们要写好代码的原因。
编写代码很容易,但编写好的、干净的代码却很难。
我们写的代码应该是简单的,有表现力的,并且没有几个重复的。富有表现力的代码意味着,尽管我们只是向计算机提供指令,但它仍然应该是可读的,并在人类阅读时清楚地传达其意图。
清洁代码的重要性
编写干净的代码有很多好处。例如,干净的代码是。
- 易于理解
- 更有效率
- 更容易维护、扩展、调试和重构
它也倾向于需要更少的文档。
代码标准
代码标准是编码规则、指南和最佳实践的集合。每种编程语言都有自己的编码标准,为了写出更干净的代码,应该遵循这些标准。它们通常涉及
- 文件组织
- 编程实践和原则
- 代码格式(缩进、声明、语句)。
- 命名规则
- 评论
PEP 8 (Python增强建议)
PEP 8是一份描述 Python 编码标准的风格指南。它是Python社区内最受欢迎的指南。最重要的规则说明了以下几点。
PEP 8的命名规则。
- 类名应该是CamelCase (
MyClass) - 变量名应该是snake_case和全小写 (
first_name) - 函数名应该是snake_case和所有小写字母 (
quick_sort()) - 常量应该是snake_case和所有大写字母 (
PI = 3.14159) - 模块应该有短的、蛇形大写的名字和所有小写字母 (
numpy) - 单引号和双引号的处理方法相同(只需选择一个并保持一致)。
PEP 8行的格式。
- 使用4个空格缩进(空格比制表符更适合)。
- 行的长度不应超过79个字符
- 避免在同一行中出现多个语句
- 顶层函数和类的定义用两个空行包围
- 类中的方法定义用一个空行包围。
- 导入应该是在不同的行上
PEP 8的空白。
- 避免在大括号或小括号中出现额外的空格
- 避免任何地方的尾部空白
- 总是在二进制运算符的两侧加上一个空格
- 如果使用了具有不同优先级的运算符,考虑在优先级最低的运算符周围添加空格。
- 当用于表示关键字参数时,不要在=符号周围使用空格。
PEP 8注释。
- 注释不应该与代码相矛盾
- 注释应该是完整的句子
- 注释应该在#号之后有一个空格,并且第一个词要大写。
- 在函数中使用的多行注释(docstrings)应该有一个简短的单行描述,后面是更多的文字。
如果你想了解更多,请阅读官方的PEP 8参考资料。
Pythonic代码
Pythonic代码是一套习语,被Python社区采用。它简单地意味着你很好地使用了Python的习语和范式,以使你的代码更简洁、可读性强、性能高。
Pythonic代码包括。
- 变量技巧
- 列表操作 (初始化、分片)
- 处理函数
- 显式代码
写Python代码和写Pythonic代码之间有很大区别。要写Pythonic代码,你不能只是习惯性地将另一种语言(如Java或C++)翻译成Python;你需要用Python来思考。
让我们看一个例子。我们必须把前10个数字加在一起,像这样1 + 2 + ... + 10 。
一个非Pythonic的解决方案会是这样的。
n = 10
sum = 0
for i in range(1, n + 1):
sum = sum + i
print(sum) # 55
一个更像Pythonic的解决方案可能是这样的。
n = 10
sum = sum(range(1, n + 1))
print(sum) # 55
这第二个例子对于一个有经验的Python开发者来说要容易得多,但它确实需要对Python的内置函数和语法有更深的理解。写Pythonic代码的最简单方法是在写代码时牢记Python的禅,并逐步学习Python的标准库。
Python 之禅
The Zen of Python 是用 Python 编写计算机程序的 19 项 "指导原则 "的集合。这本集子是由软件工程师Tim Peters在1999年编写的。它作为一个复活节彩蛋包含在 Python 解释器中。
你可以通过执行以下命令看到它。
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
如果你对 "诗 "的含义感到好奇,可以查看《Python的禅,解释》,它提供了逐行的解释。
编码原则
为了写出更好的代码,你可以遵循许多编码原则,每个原则都有自己的优点/缺点和权衡因素。 这篇文章涵盖了四个比较流行的原则。DRY, KISS, SoC, 和 SOLID。
DRY(不要重复自己
每一个知识点在一个系统中都必须有一个单一的、明确的、权威的表述。
这是最简单的编码原则之一。它的唯一规则是,代码不应该被重复。与其重复行,不如找一个使用迭代的算法。DRY代码是很容易维护的。你可以通过模型/数据的抽象化来进一步贯彻这一原则。
DRY原则的缺点是,你可能最终会有太多的抽象,外部依赖性的产生,以及复杂的代码。如果你试图改变你的代码库的更大块的内容,DRY也会导致复杂化。这就是为什么你应该避免过早地对你的代码进行DRY。有一些重复的代码部分总是比错误的抽象要好。
KISS (保持简单,愚蠢)
大多数系统如果保持简单,而不是变得复杂,那么它们的工作效果最好。
KISS原则指出,大多数系统如果保持简单,而不是变得复杂,那么它们的工作效果最好。简单应该是设计中的一个关键目标,而不必要的复杂应该被避免。
SoC (关注点分离)
SoC是一个将计算机程序分成不同部分的设计原则,这样每个部分都能解决一个单独的问题。一个关注点是一组影响计算机程序代码的信息。
SoC的一个好例子是MVC(模型-视图-控制器)。
如果你决定采用这种方法,请注意不要将你的应用程序分成太多的模块。你应该只在有意义的时候创建一个新模块。更多的模块就等于更多的问题。
SOLID
SOLID是五个设计原则的缩写,旨在使软件设计更易理解、更灵活、更可维护。
在编写OOP代码时,SOLID是非常有用的。它谈到了将你的类分割成多个子类、继承、抽象、接口等等。
它由以下五个概念组成。
代码格式化
代码格式化器通过自动格式化来执行编码风格,并帮助实现和维护干净的代码。它们中的大多数允许你创建一个可以与你的同事分享的风格配置文件。
最流行的Python代码格式化器是。
大多数现代集成开发环境也包括linters,它们在你输入时在后台运行,帮助识别小的编码错误、误差、危险的代码模式,并保持你的代码格式化。有两种类型的提示器:逻辑的和风格的。
最流行的Python提示器是。
关于提示和代码格式化的更多信息,请查看Python代码质量。
命名规则
编写干净代码的最重要的方面之一是命名规则。你应该总是使用有意义的、能揭示意图的名字。使用长的、描述性的名字总是比带注释的短名字好。
# This is bad
# represents the number of active users
au = 55
# This is good
active_user_amount = 55
我们将在接下来的两节中看更多的例子。
变量
1.使用名词作为变量名称
2.2.使用描述性/意向性的名字
其他开发者应该能够通过阅读变量的名称来了解其存储的内容。
# This is bad
c = 5
d = 12
# This is good
city_counter = 5
elapsed_time_in_days = 12
3.3.使用可发声的名字
你应该总是使用可读的名字;否则,你将很难大声解释你的算法。
from datetime import datetime
# This is bad
genyyyymmddhhmmss = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')
# This is good
generation_datetime = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')
4.4.避免使用模棱两可的缩略语
不要试图想出你自己的缩略语。对一个变量来说,有一个较长的名字比一个混乱的名字要好。
# This is bad
fna = 'Bob'
cre_tmstp = 1621535852
# This is good
first_name = 'Bob'
creation_timestamp = 1621535852
5.始终使用相同的词汇
命名变量时避免使用同义词。
# This is bad
client_first_name = 'Bob'
customer_last_name = 'Smith'
# This is good
client_first_name = 'Bob'
client_last_name = 'Smith'
6.不要使用 "神奇的数字"
魔术数字是出现在代码中的奇怪数字,它没有明确的含义。让我们来看看一个例子。
import random
# This is bad
def roll():
return random.randint(0, 36) # what is 36 supposed to represent?
# This is good
ROULETTE_POCKET_COUNT = 36
def roll():
return random.randint(0, ROULETTE_POCKET_COUNT)
与其使用神奇的数字,我们可以把它们提取到一个有意义的变量中。
7.使用解决方案的域名
如果你在你的算法或类中使用了很多不同的数据类型,而你无法从变量名本身弄清楚它们,不要害怕在你的变量名中加入数据类型后缀。比如说。
# This is good
score_list = [12, 33, 14, 24]
word_dict = {
'a': 'apple',
'b': 'banana',
'c': 'cherry',
}
这是一个不好的例子(因为你无法从变量名称中弄清楚数据类型)。
# This is bad
names = ["Nick", "Mike", "John"]
8.不要添加多余的上下文
不要给变量名添加不必要的数据,尤其是当你在处理类的时候。
# This is bad
class Person:
def __init__(self, person_first_name, person_last_name, person_age):
self.person_first_name = person_first_name
self.person_last_name = person_last_name
self.person_age = person_age
# This is good
class Person:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
我们已经在Person 类里面了,所以没有必要给每个类的变量添加person_ 前缀。
职能
1.在函数名称中使用动词
2.2.不要对同一概念使用不同的词
为每个概念选择一个词,并坚持使用它。对同一概念使用不同的词会造成混乱。
# This is bad
def get_name(): pass
def fetch_age(): pass
# This is good
def get_name(): pass
def get_age(): pass
3.写短而简单的功能
4.函数应该只执行一个任务
如果你的函数包含关键字 "and",你可能可以把它分成两个函数。我们来看看一个例子。
# This is bad
def fetch_and_display_personnel():
data = # ...
for person in data:
print(person)
# This is good
def fetch_personnel():
return # ...
def display_personnel(data):
for person in data:
print(person)
函数应该只做一件事,作为读者,它们做的是你期望它们做的事。
一个好的经验法则是,任何给定的函数都不应该花费超过几分钟的时间来理解。回头看看你几个月前写的一些旧代码。你也许应该重构任何需要超过5分钟才能理解的函数。这毕竟是你的代码。想一想另一个开发者需要多长时间才能理解它。
5.将你的参数保持在最低限度
你的函数中的参数应该保持在最低限度。理想情况下,你的函数应该只有一到两个参数。如果你需要给函数提供更多的参数,你可以创建一个配置对象,并将其传递给函数,或者将其拆分成多个函数。
例子。
# This is bad
def render_blog_post(title, author, created_timestamp, updated_timestamp, content):
# ...
render_blog_post("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")
# This is good
class BlogPost:
def __init__(self, title, author, created_timestamp, updated_timestamp, content):
self.title = title
self.author = author
self.created_timestamp = created_timestamp
self.updated_timestamp = updated_timestamp
self.content = content
blog_post1 = BlogPost("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")
def render_blog_post(blog_post):
# ...
render_blog_post(blog_post1)
6.不要在函数中使用flags
标志是传递给函数的变量(通常是布尔值),函数用它来决定其行为。它们被认为是不好的设计,因为函数应该只执行一项任务。避免使用标志的最简单的方法是将你的函数分成更小的函数。
text = "This is a cool blog post."
# This is bad
def transform(text, uppercase):
if uppercase:
return text.upper()
else:
return text.lower()
uppercase_text = transform(text, True)
lowercase_text = transform(text, False)
# This is good
def uppercase(text):
return text.upper()
def lowercase(text):
return text.lower()
uppercase_text = uppercase(text)
lowercase_text = lowercase(text)
7.避免副作用
如果一个函数除了输入一个值并返回另一个或多个值之外,还做了其他事情,那么它就会产生副作用。例如,一个副作用可能是写到一个文件或修改一个全局变量。
无论我们如何努力编写干净的代码,你的程序中仍然会有一些需要额外解释的部分。注释允许我们快速地告诉其他开发者(以及未来的自己)为什么我们要以这样的方式来写它。请记住,添加太多的注释会使你的代码比没有注释时更混乱。
代码注释和文档之间有什么区别?
| 类型 | 答案 | 利益相关者 |
|---|---|---|
| 文档 | 何时和如何 | 用户 |
| 代码评论 | 为什么? | 开发人员 |
| 清洁代码 | 什么 | 开发人员 |
关于代码注释和文档之间的更多区别,请回顾Python代码和项目的文档化文章。
注释坏的代码 -- 即# TODO: RE-WRITE THIS TO BE BETTER -- 只能在短期内帮助你。迟早你的一个同事会不得不使用你的代码,他们会在花了多个小时试图弄清它的作用之后,最终重写它。
如果你的代码有足够的可读性,你就不需要注释。添加无用的注释只会让你的代码更难读。这里有一个不好的例子。
# This checks if the user with the given ID doesn't exist.
if not User.objects.filter(id=user_id).exists():
return Response({
'detail': 'The user with this ID does not exist.',
})
一般来说,如果你需要添加注释,它们应该解释 "为什么 "你做了什么,而不是 "什么 "正在发生。
不要添加那些对代码没有任何价值的注释。这是很糟糕的。
numbers = [1, 2, 3, 4, 5]
# This variable stores the average of list of numbers.
average = sum(numbers) / len(numbers)
print(average)
这也是不好的。
大多数编程语言都有不同的注释类型。了解它们的区别并相应地使用它们。你还应该学习注释文档的语法。一个好的例子。
def model_to_dict(instance, fields=None, exclude=None):
"""
Returns a dict containing the data in ``instance`` suitable for passing as
a Form's ``initial`` keyword argument.
``fields`` is an optional list of field names. If provided, return only the
named.
``exclude`` is an optional list of field names. If provided, exclude the
named from the returned dict, even if they are listed in the ``fields``
argument.
"""
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
if not getattr(f, 'editable', False):
continue
if fields is not None and f.name not in fields:
continue
if exclude and f.name in exclude:
continue
data[f.name] = f.value_from_object(instance)
return data
你能做的最糟糕的事情就是留下代码注释。在推送到版本控制系统之前,所有的调试代码或调试信息都应该被删除,否则,你的同事会害怕删除它,你的注释代码将永远留在那里。
装饰器、上下文管理器、迭代器和生成器
在这一节中,我们将看看一些Python概念和技巧,我们可以用它们来编写更好的代码。
装饰器
装饰器是 Python 中一个非常强大的工具,它允许我们为一个函数添加一些自定义功能。在其核心部分,它们只是在函数内部调用的函数。通过使用它们,我们可以利用 SoC (Separation of concerns) 原则,使我们的代码更加模块化。学会了它们,你就能在Pythonic代码的道路上走得更远!
假设我们有一个服务器,它受到密码的保护。我们可以在每个服务器方法中询问密码,或者创建一个装饰器,像这样保护我们的服务器方法。
def ask_for_passcode(func):
def inner():
print('What is the passcode?')
passcode = input()
if passcode != '1234':
print('Wrong passcode.')
else:
print('Access granted.')
func()
return inner
@ask_for_passcode
def start():
print("Server has been started.")
@ask_for_passcode
def end():
print("Server has been stopped.")
activate() # decorator will ask for password
deactivate() # decorator will ask for password
现在我们的服务器在每次调用activate() 或deactivate() 时都会要求输入密码。
上下文管理器
上下文管理器简化了我们与外部资源的交互方式,如文件和数据库。最常见的用法是with 语句。它们的好处是,它们会自动地在其区块之外去分配内存。
让我们看一个例子。
with open('wisdom.txt', 'w') as opened_file:
opened_file.write('Python is cool.')
# opened_file has been closed.
如果没有上下文管理器,我们的代码会是这样的。
file = open('wisdom.txt', 'w')
try:
file.write('Python is cool.')
finally:
file.close()
迭代器
迭代器是一个包含可计数的值的对象。迭代器允许对一个对象进行迭代,这意味着你可以遍历所有的值。
比方说,我们有一个名字的列表,我们想循环浏览它。我们可以使用next(names) 循环通过它。
names = ["Mike", "John", "Steve"]
names_iterator = iter(names)
for i in range(len(names)):
print(next(names_iterator))
或者使用一个增强型循环。
names = ["Mike", "John", "Steve"]
for name in names:
print(name)
在增强型循环中,避免使用
item或value这样的变量名,因为它使人很难分辨一个变量存储的内容,特别是在嵌套的增强型循环中。
生成器
生成器是 Python 中的一个函数,它返回一个迭代器对象而不是一个单一的值。普通函数和生成器的主要区别在于,生成器使用yield 关键字而不是return 。迭代器中的每个下一个值都是用next(generator) 来获取的。
假设我们想生成n 的第一个倍数x 。我们的生成器将看起来像这样。
def multiple_generator(x, n):
for i in range(1, n + 1):
yield x * i
multiples_of_5 = multiple_generator(5, 3)
print(next(multiples_of_5)) # 5
print(next(multiples_of_5)) # 10
print(next(multiples_of_5)) # 15
模块化和类
为了使你的代码尽可能的有条理,你应该把它分割成多个文件,然后再分割成不同的目录。如果你用面向OOP的语言编写代码,你也应该遵循基本的OOP原则,如封装、抽象、继承和多态性。
将代码分割成多个类会使你的代码更容易理解和维护。一个文件或一个类应该有多长并没有固定的规则,但尽量让它们小一点(最好在200行以内)。
Django的默认项目结构是一个很好的例子,说明你的代码应该如何结构化。
awesomeproject/
├── main/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog/
│ ├── migrations/
│ │ └── __init__.py
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── templates
Django是一个MTV(Model-Template-View)框架,与我们之前讨论的MVC框架类似。这种模式将程序逻辑划分为三个相互关联的部分。你可以看到,每个应用程序都在一个单独的目录中,每个文件都是为一个特定的东西服务的。如果你的项目被分割成多个应用,你应该确保这些应用不会过多地相互依赖。
测试
优质的软件离不开测试。测试软件使我们能够在软件部署之前发现软件中的错误和瑕疵。测试与生产代码具有同样的重要性,你应该花相当多的时间来处理这些问题。
关于测试干净的代码和编写干净的测试代码的更多信息,请查阅以下文章。
结论
编写干净的代码是困难的。要写出好的、干净的代码,没有单一的秘诀可循。它需要时间和经验来掌握。我们已经看了一些编码标准和一般准则,可以帮助你写出更好的代码。我可以给你的最好的建议之一是保持一致,并尝试写一些容易测试的简单代码。如果你发现你的代码很难测试,那么它可能就很难使用。