如果写劣质代码是犯罪,那我该判无期
导读
程序员痛恨遇到质量低劣的代码,但在高压环境下,我们常为了最快解决当下需求而忽略代码规范,在无意识中堆积大量债务。我们还观察到许多开发者被迫加班的罪魁祸首便是写低效代码、不重视代码优化。编程路上,欲速则不达。 接下来,我将为各位列举9种我个人工作中高频遇到的不整洁代码行为,并提出针对性优化建议。继续阅读~
目录
1 代码风格和可读性
2 注释
3 错误处理和异常处理
4 代码复用和模块化
5 硬编码
6 测试和调试
7 性能优化
8 代码安全性
9 版本控制和协作
10 总结
01、代码风格和可读性
- 错误习惯
1.1 变量命名不规范
在编程中,变量命名是非常重要的,良好的变量命名能够提高代码的可读性和可维护性。不规范的命名会增加理解难度,以下是一个不规范命名的例子:
int a, b, c; // 不具有描述性的变量名
float f; // 不清楚变量表示的含义
这样的变量命名不仅会降低代码的可读性,还可能会导致变量混淆,增加代码维护的难度。正确的做法应该使用有意义的名称来命名变量。例如:
int num1, num2, result; // 具有描述性的变量名
float price; // 清晰明了的变量名
1.2 长函数和复杂逻辑
长函数和复杂逻辑是另一个常见的错误和坏习惯。长函数难以理解和维护,而复杂逻辑可能导致错误和难以调试。以下是一个长函数和复杂逻辑的案例:
def count_grade(score):
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
elif score >= 60:
grade = 'D'
else:
grade = 'F'
if grade == 'A' or grade == 'B':
result = 'Pass'
else:
result = 'Fail'
return result
在这个例子中,函数 count_grade 包含了较长的逻辑和多个嵌套的条件语句,使得代码难以理解和维护。正确的做法是将逻辑拆分为多个小函数,每个函数只负责一个简单的任务,例如:
def count_grade(score):
grade = get_grade(score)
result = pass_or_fail(grade)
return result
def get_grade(score):
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
def pass_or_fail(grade):
if grade == 'A' or grade == 'B':
return 'Pass'
else:
return 'Fail'
通过拆分函数,我们使得代码更加可读和可维护。
1.3 过长的行
代码行过长,会导致代码难以阅读和理解,增加了维护和调试的难度。例如:
def f(x):
if x>0:return 'positive' elif x<0:return 'negative'else:return 'zero'
这段代码的问题在于,它没有正确地使用空格和换行,使得代码看起来混乱,难以阅读。正确的方法是,我们应该遵循一定的代码规范和风格,使得代码清晰、易读。下面是按照 PEP 8规范改写的代码:
def check_number(x):
if x > 0:
return 'positive'
elif x < 0:
return 'negative'
else:
return 'zero'
这段代码使用了正确的空格和换行,使得代码清晰、易读。
02、注释
- 错误习惯
错误的注释
注释是非常重要的,良好的注释可以提高代码的可读性和可维护性。以下是一个不规范的例子:
int num1, num2; // 定义两个变量
上述代码中,注释并没有提供有用的信息,反而增加了代码的复杂度。
03、错误处理和异常处理
- 错误的习惯
3.1 忽略错误
我们往往会遇到各种错误和异常。如果我们忽视了错误处理,那么当错误发生时,程序可能会崩溃,或者出现不可预知的行为。例如:
def divide(x, y):
return x / y
这段代码的问题在于,当 y 为0时,它会抛出 ZeroDivisionError 异常,但是这段代码没有处理这个异常。下面是改进的代码:
def divide(x, y):
try:
return x / y
except ZeroDivisionError:
return 'Cannot divide by zero!'
3.2 过度使用异常处理
我们可能会使用异常处理来替代条件判断,这是不合适的。异常处理应该用于处理异常情况,而不是正常的控制流程。例如:
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
result = float('inf')
return result
在这个示例中,我们使用异常处理来处理除以零的情况。正确做法:
def divide(a, b):
if b == 0:
result = float('inf')
else:
result = a / b
return result
在这个示例中,我们使用条件判断来处理除以零的情况,而不是使用异常处理。
3.3 捕获过于宽泛的异常
捕获过于宽泛的异常可能导致程序崩溃或隐藏潜在的问题。以下是一个案例:
try {
// 执行一些可能抛出异常的代码
} catch (Exception e) {
// 捕获所有异常,并忽略错误}
在这个例子中,异常被捕获后,没有进行任何处理或记录,导致程序无法正确处理异常情况。正确的做法是根据具体情况,选择合适的异常处理方式,例如:
try {
// 执行一些可能抛出异常的代码
} catch (FileNotFoundException e) {
// 处理文件未找到异常
logger.error("File not found", e);
} catch (IOException e) {
// 处理IO异常
logger.error("IO error", e);
} catch (Exception e) {
// 处理其他异常
logger.error("Unexpected error", e);}
通过合理的异常处理,我们可以更好地处理异常情况,增加程序的稳定性和可靠性。
04、错误处理和异常处理
- 错误的习惯
4.1 缺乏复用性
代码重复是一种非常常见的错误。当我们需要实现某个功能时,可能会复制粘贴之前的代码来实现,这样可能会导致代码重复,增加代码维护的难度。例如:
def calculate_area_of_rectangle(length, width):
return length * width
def calculate_volume_of_cuboid(length, width, height):
return length * width * height
def calculate_area_of_triangle(base, height):
return 0.5 * base * height
def calculate_volume_of_cone(radius, height):
return (1/3) * 3.14 * radius * radius * height
上述代码中,计算逻辑存在重复,这样的代码重复会影响代码的可维护性。为了避免代码重复,我们可以将相同的代码复用,封装成一个函数或者方法。例如:
def calculate_area_of_rectangle(length, width):
return length * width
def calculate_volume(length, width, height):
return calculate_area_of_rectangle(length, width) * height
def calculate_area_of_triangle(base, height):
return 0.5 * base * height
def calculate_volume_of_cone(radius, height):
return (1/3) * 3.14 * radius * radius * height
这样,我们就可以避免代码重复,提高代码的可维护性。
4.2 缺乏模块化
缺乏模块化是一种常见的错误,这样容易造成冗余,降低代码的可维护性,例如:
class User:
def __init__(self, name):
self.name = name
def save(self):
# 保存用户到数据库的逻辑
def send_email(self, content):
# 发送邮件的逻辑
class Order:
def __init__(self, user, product):
self.user = user
self.product = product
def save(self):
# 保存订单到数据库的逻辑
def send_email(self, content):
# 发送邮件的逻辑
```
此例中,User 和 Order 类都包含了保存和发送邮件的逻辑,导致代码重复,耦合度高。我们可以通过将发送邮件的逻辑提取为一个独立的类,例如:
class User:
def __init__(self, name):
self.name = name
def save(self):
# 保存用户到数据库的逻辑
class Order:
def __init__(self, user, product):
self.user = user
self.product = product
def save(self):
# 保存订单到数据库的逻辑
class EmailSender:
def send_email(self, content):
# 发送邮件的逻辑
通过把发送邮件单独提取出来,实现了模块化。现在 User 和 Order 类只负责自己的核心功能,而发送邮件的逻辑由 EmailSender 类负责。这样一来,代码更加清晰,耦合度降低,易于重构和测试。
05、硬编码
- 错误的习惯
5.1 常量
在编程中,我们经常需要使用一些常量,如数字、字符串等。然而,直接在代码中硬编码这些常量是一个不好的习惯,因为它们可能会在未来发生变化,导致维护困难。例如:
def calculate_score(score):
if (score > 60) {
// do something}
这里的60就是一个硬编码的常量,导致后续维护困难,正确的做法应该使用常量或者枚举来表示。例如:
PASS_SCORE = 60;
def calculate_score(score):
if (score > PASS_SCORE) {
// do something }
这样,我们就可以避免硬编码,提高代码的可维护性。
5.2 全局变量
过度使用全局变量在全局范围内都可以访问和修改。因此,过度使用全局变量可能会导致程序的状态难以跟踪,增加了程序出错的可能性。例如:
counter = 0
def increment():
global counter
counter += 1
这段代码的问题在于,它使用了全局变量 counter,使得程序的状态难以跟踪。我们应该尽量减少全局变量的使用,而是使用函数参数和返回值来传递数据。例如:
def increment(counter):
return counter + 1
这段代码没有使用全局变量,而是使用函数参数和返回值来传递数据,使得程序的状态更易于跟踪。
06、测试和调试
- 错误的习惯
6.1 单元测试
单元测试是验证代码中最小可测试单元的方法,下面是不添加单元测试的案例:
def add_number(a, b):
return a + b
在这个示例中,我们没有进行单元测试来验证函数 add_number
的正确性。正确示例:
import unittest
def add_number(a, b):
return a + b
class TestAdd(unittest.TestCase):
def add_number(self):
self.assertEqual(add(2, 3), 5)
if __name__ == '__main__': unittest.main()
在这个示例中,我们使用了 unittest
模块进行单元测试,确保函数 add
的正确性。
6.2 边界测试
边界测试是针对输入的边界条件进行测试,以验证代码在边界情况下的行为下面是错误示例:
def is_even(n):
return n % 2 == 0
在这个示例中,我们没有进行边界测试来验证函数 is_even
在边界情况下的行为。正确示例:
import unittest
def is_even(n):
return n % 2 == 0
class TestIsEven(unittest.TestCase):
def test_even(self):
self.assertTrue(is_even(2))
self.assertFalse(is_even(3))
if __name__ == '__main__': unittest.main()
在这个示例中,我们使用了 unittest
模块进行边界测试,验证函数 is_even
在边界情况下的行为。
6.3 可测试性
代码的可测试性我们需要编写测试来验证代码的正确性。如果我们忽视了代码的可测试性,那么编写测试将会变得困难,甚至无法编写测试。例如:
def get_current_time():
return datetime.datetime.now()
这段代码的问题在于,它依赖于当前的时间,这使得我们无法编写确定性的测试。我们应该尽量减少代码的依赖,使得代码更易于测试。例如:
def get_time(now):
return now
这段代码不再依赖于当前的时间,而是通过参数传入时间,这使得我们可以编写确定性的测试。
07、性能优化
- 错误的习惯
7.1 过度优化
我们往往会试图优化代码,使其运行得更快。然而,过度优化可能会导致代码难以理解和维护,甚至可能会引入新的错误。例如:
def sum(numbers):
return functools.reduce(operator.add, numbers)
这段代码的问题在于,它使用了 functools.reduce 和 operator.add 来计算列表的和,虽然这样做可以提高一点点性能,但是这使得代码难以理解。我们应该在保持代码清晰和易读的前提下,进行适度的优化。例如:
def sum(numbers):
return sum(numbers)
这段代码使用了内置的 sum 函数来计算列表的和,虽然它可能比上面的代码慢一点,但是它更清晰、易读。
7.2 没有使用合适的数据结构
选择合适的数据结构可以提高代码的性能。使用不合适的数据结构可能导致代码执行缓慢或占用过多的内存。例如:
def find_duplicate(numbers):
duplicates = []
for i in range(len(numbers)):
if numbers[i] in numbers[i+1:]:
duplicates.append(numbers[i])
return duplicates
在这个示例中,我们使用了列表来查找重复元素,但这种方法的时间复杂度较高。我们可以使用集合来查找元素。例如:
def find_duplicate(numbers):
duplicates = set()
seen = set()
for num in numbers:
if num in seen:
duplicates.add(num)
else:
seen.add(num)
return list(duplicates)
我们使用了集合来查找重复元素,这种方法的时间复杂度较低。
08、代码安全性
- 错误的习惯
8.1 输入验证
没有对用户输入进行充分验证和过滤可能导致恶意用户执行恶意代码或获取敏感信息。例如:
import sqlite3
def get_user(username):
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
user = cursor.fetchone()
conn.close()
return user
在这个示例中,我们没有对用户输入的 username
参数进行验证和过滤,可能导致 SQL 注入攻击。正确示例:
import sqlite3
def get_user(username):
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (username,))
user = cursor.fetchone()
conn.close()
return user
在这个示例中,我们使用参数化查询来过滤用户输入,避免了 SQL 注入攻击。
8.2 不正确的密码存储
将明文密码存储在数据库或文件中,或使用不安全的哈希算法存储密码都是不安全的做法。错误示例:
import hashlib
def store_password(password):
hashed_password = hashlib.md5(password.encode()).hexdigest()
# 存储 hashed_password 到数据库或文件中
在这个示例中,我们使用了不安全的哈希算法 MD5 来存储密码。正确示例:
import hashlib
import bcrypt
def store_password(password):
hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# 存储 hashed_password 到数据库或文件中
在这个示例中,我们使用了更安全的哈希算法 bcrypt 来存储密码。
8.3 不正确的权限控制
没有正确验证用户的身份和权限可能导致安全漏洞。错误示例:
def delete_user(user_id):
if current_user.is_admin:
# 执行删除用户的操作
else:
raise PermissionError("You don't have permission to delete users.")
在这个示例中,我们只检查了当前用户是否为管理员,但没有进行足够的身份验证和权限验证。正确示例:
def delete_user(user_id):
if current_user.is_authenticated and current_user.is_admin:
# 执行删除用户的操作
else:
raise PermissionError("You don't have permission to delete users.")
在这个示例中,我们不仅检查了当前用户是否为管理员,还检查了当前用户是否已经通过身份验证。
09、版本控制和协作
- 错误的习惯
9.1 版本提交信息
不合理的版本提交信息可能导致代码丢失、开发人员难以理解等问题。错误示例:
git commit -m "Fixed a bug"
在这个例子中,提交信息没有提供足够的上下文和详细信息,导致其他开发人员难以理解和追踪代码的变化。正确的做法是提供有意义的提交信息,例如:
$ git commit -m "Fixed a bug in calculate function, which caused grade calculation for scores below 60"
通过提供有意义的提交信息,我们可以更好地追踪代码的变化,帮助其他开发人员理解和维护代码。
9.2 忽略版本控制和备份
忽略使用版本控制工具进行代码管理和备份是一个常见的错误。错误示例:
$ mv important_code.py important_code_backup.py
$ rm important_code.py
在这个示例中,开发者没有使用版本控制工具,只是简单地对文件进行重命名和删除,没有进行适当的备份和记录。正确示例:
$ git clone project.git
$ cp important_code.py important_code_backup.py
$ git add .
$ git commit -m "Created backup of important code"
$ git push origin master
$ rm important_code.py
在这个示例中,开发者使用了版本控制工具进行代码管理,并在删除之前创建了备份,确保了代码的安全性和可追溯性。
10、总结
好的代码应该如同一首好文,让人爱不释手。优雅的代码,不仅是功能完善,更要做好每一个细节。
最后,引用韩磊老师在《代码整洁之道》写到的一句话送给大家:
“
细节之中自有天地,整洁成就卓越代码。
以上是本文全部内容,欢迎分享。
-End-
原创作者|孔垂航
技术责编|刘银松
来源:juejin.cn/post/7257894053902565433