1. 什么是Clean-Code
Clean-Code 是一组规则和原则,有助于保持我们的代码可读、可维护和可扩展。我们花在阅读代码上的时间比实际编写代码的时间多得多,这就是为什么编写易于维护的代码很重要。
1.1 Clean-Code的特点
- 容易理解
- 更有效率
- 易于维护, 拓展, 调试
- 需要更少的文档
2. 代码标准
代码标准是编码规则、指南和最佳实践的集合。从以下几个方面介绍一下代码标准
- 编程实践和原则
- 文件组织
- 代码格式(缩进、声明、语句)
- 命名约定
- 注释
2.1 代码原则
2.1.1 DRY(Don't Repeat Yourself)
系统中的每一部分,都必须有一个单一的、明确的、权威的代表
这是最简单的编码原则之一。它唯一的规则是代码不应重复。简单来说就是写代码的时候,如果出现雷同片段,就要想办法把他们提取出来。
2.1.2 KISS (Keep It Simple, Stupid)
大多数系统在保持简单而不是变得复杂时效果最好。
KISS 原则指出,大多数系统如果保持简单而不是变得复杂,则效果最佳。简单应该是设计的一个关键目标,应该避免不必要的复杂性。
2.1.3 SoC(Separation of Concerns)
SoC 是一种设计原则,用于将计算机程序分成不同的部分,以便每个部分解决一个单独的问题。关注点是影响计算机程序代码的一组信息。
SoC 的一个很好的例子是MVC(模型 - 视图 - 控制器)。
如果您决定采用这种方法,请注意不要将您的应用程序分成太多模块。您应该只在有意义的时候创建一个新模块。
2.1.4 SOLID
SOLID 是五个设计原则的助记首字母缩写词,旨在使软件设计更易于理解、灵活和可维护。
SOLID 在编写 OOP 代码时非常有用。它谈到将您的类拆分为多个子类、继承、抽象、接口等。
它由以下五个概念组成:
- The Single-responsibility principle: 有且仅有一个原因使类变更(一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中)。
- The Open–closed principle: 实体应该对扩展是开放的,对修改是封闭的。即可扩展(extension),不可修改(modification)。
- The Liskov substitution principle: 一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。
- The Interface segregation principle: 接口隔离原则表明客户端不应该被强迫实现一些他们不会使用的接口(应该把臃肿的接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块)
- The Dependency inversion principle: 依赖抽象而不应该依赖实现。换言之,要针对抽象(接口)编程,而不是针对实现细节编程。
2.2 文件组织-模块和包
为了使代码尽可能有条理,您应该将其拆分为多个文件,然后将这些文件拆分为不同的目录。
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
2.3 代码格式
Python 代码应该遵循 PEP8 编码标准的风格指南, 一下是最重要的一些约定.
① PEP 8 命名约定 | ② PEP 8 行格式 |
类名应该是 CamelCase ( MyClass) | 使用 4 个空格缩进(空格优先于制表符) |
变量名应该是snake_case并且全部小写(first_name) | 行不应超过 79 个字符 |
函数名应该是snake_case并且全部小写(quick_sort()) | 避免在同一行上出现多个语句 |
常量应该是snake_case并且全部大写( PI = 3.14) | 顶级函数和类定义被两个空行包围 |
模块应该有简短的、snake_case 名称和全部小写 (pandas) | 类中的方法定义被一个空行包围 |
单引号和双引号的处理方式相同(只需选择一个并保持一致) | 进口应该在单独的行上 |
③ PEP 8 空格 | ④ PEP 8 评论 |
避免括号或大括号内的额外空格 | 注释不应与代码相矛盾 |
避免在任何地方尾随空格 | 评论应该是完整的句子 |
总是在二元运算符两边用空格 | 评论应该在# 号后面有一个空格,第一个单词大写 |
包含不同优先级的运算符,在优先级最低的运算符周围添加空格 | 函数(文档字符串)中使用的多行注释应该有一个简短的单行描述,后跟更多的文本 |
当用于指示关键字参数时,不要在 = 符号周围使用空格 |
2.4 ⭐ 命名约定
命名约定是编写 Clean-Code 的最重要方面之一。
2.4.1 变量
变量命名除了应该遵循PEP8, snake_case小写, 还应该遵循以下原则:
①. 使用名词作为变量名
play = 4 # Bad ❌
play_times = 4 # Good ✔️
②. 避免歧义缩写
mec = 15 # Bad ❌
message_expose_count = 15 # Good ✔️
③. 避免双重否定
要避免出现 "是否不能够不为非空" 这种难以理解的双重否定变量。
尽量使用正向逻辑,即bool=True的时候为可以,为False的为不可以。
is_not_invalid = False # Bad ❌
is_valid = False # Good ✔️
④. 使用描述性的单词
变量名应该完整描述出变量数据的含义
expire = '2021-08-17 11:25:37.223283' # Bad ❌
expiration_time = '2021-08-17 11:25:37.223283' # Good ✔️
⑤. 与历史规则保持一致
# 已经存在的代码
def function_1(req, resp, var):
pass
def function_2(req, resp, data):
pass
# Bad ❌
def function_new(request, response, data):
pass
# Good ✔️
def function_new(req, resp, data):
pass
⑥. 不要使用“幻数”
幻数是出现在代码中的没有明确的含义的, "奇怪"的数字.
import random
# Bad ❌
def roll():
return random.randint(0, 36) # 为什么是36
# Good ✔️
POCKET_COUNT = 36
def roll():
return random.randint(0, POCKET_COUNT)
⑦. 有必要标识出数据类型
players = ["Jordan", "Bryant", "James"] # Bad ❌
player_list = ["Jordan", "Bryant", "James"] # Good ✔️
⑧. 去除冗余的信息
不要在变量名中添加不必要的数据
# Bad ❌
class Player:
def __init__(self, player_name, player_number):
self.player_name = player_name
self.player_number = player_number
def player_shoot(self):
pass
# Good ✔️
class Player:
def __init__(self, name, number):
self.name = name
self.number = number
def shoot(self):
pass
2.4.2 函数
①. 使用动词作为函数名
def user_score(): # Bad ❌
pass
def compute_user_score(): # Good ✔️
pass
②. 同一个概念使用相同的词
# Bad ❌
def get_name():
pass
def fetch_age():
pass
# Good ✔️
def get_name():
pass
def get_age():
pass
③. 尽量减少你的参数
如果需要为函数提供多个参数,可以创建一个数据对象将其传递给函数, 或函数其拆分为多个函数.
# Bad ❌
def set_first_team(name, number, position, jersey, offensive_score, defensive_score):
pass
# Good ✔️
@dataclass
class Player:
name: str
number: int
position: str
jersey: str
offensive_score: float
defensive_score: float
def set_first_team(player):
pass
④. 一个函数只做一件事
如果函数名包含关键字“and”,可以考虑将其拆分为两个函数。
# Bad ❌
def training_freethrow_and_dribble():
pass
# Good ✔️
def training_freethrow():
pass
def training_dribble():
pass
⑤. 不要在函数中使用flag
flag 是传递给函数的变量(通常是布尔值),函数通过判断它来确定其行为。函数应该只执行一项任务。避免标志的最简单方法是将您的函数拆分为更小的函数。
# Bad ❌
def transform(text, uppercase):
if uppercase:
return text.upper()
else:
return text.lower()
uppercase_text = transform(text, True)
lowercase_text = transform(text, False)
# Good ✔️
def uppercase(text):
return text.upper()
def lowercase(text):
return text.lower()
uppercase_text = uppercase(text)
lowercase_text = lowercase(text)
2.5 注释
即是使用了自解释的函数和变量名, 程序的某些部分仍然需要额外的解释, 这时候就需要写注释。
良好的注释可以在后续的代码维护中,使其他开发人员(也可能未来的自己)了解为什么我们以这种方式编码。
类型 | 关注点 | 关注人员 |
---|---|---|
文档 | 何时以及如何使用(Where, When) | 使用者 |
代码注释 | 为什么(How) | 开发者 |
干净的代码 | 是什么(What) | 开发者 |
①. 编写自解释的代码而不是用注释去解释
我们应该尝试在描述性和自读变量中捕获复杂的逻辑.
# Bad ❌
# 判断会议已预约, 且已经开始, 用户有权限参加
if meeting and (current_time > meeting.start_time) and (user.permission in ('admin', 'moderator')) and (not meeting.is_cancelled):
print('# Do something...')
# Good ✔️
is_meeting_scheduled = meeting and not meeting.is_cancelled
has_meeting_started = current_time > meeting.start_time
has_user_permission = user.permission in ('admin', 'moderator')
if is_meeting_scheduled and has_meeting_started and has_user_permission:
print('# Do something...')
def training_freethrow_and_dribble():
pass
②. 不要添加噪音注释
numbers = [1, 2, 3, 4, 5]
# Bad ❌
# This variable stores the average of list of numbers.
average = sum(numbers) / len(numbers)
③. 解释为什么
注释的目的是为了解释我们为什么要这样做. 如下例子, 如果没有注释, 可能会产生疑问,为什么这么切分字符串
# Good ✔️
# user_info格式为 "Magic Johnson|magic32@example.com"
email = user_info.split('|', maxsplit=2)[-1]
结论
除了上面提到的原则, 善用 装饰器、上下文管理器、迭代器和生成器也可以使代码更加Pythonic
编写干净的代码很难, 要想掌握它需要时间和经验的积累。我能给的建议就是尝试编写易于测试的简单代码。还有就是阅读其他人写的老代码时, 遇到的困惑的地方, 在自己写代码的时候都尽力要避免。