丰富的经验带来了哪些独特的视角
当我刚开始学习编程时,常听到这样的话:
“成为程序员需要天赋”
“你将会整天沉浸在代码中”
“你会参与到创新的项目中”
“编写代码是一件非常有趣的事情”
但随着时间的流逝,我逐渐认识到,这些都不尽然是真的。
作为一个十多年一线程序员,我深刻理解了软件开发的复杂性,这是每个开发者都要面对的挑战。这也是为什么我们这些开发者对默认规则如此执着。
在这里,我想和大家分享四个重要的原则。
1. 不要将共享代码提取成库
在很多针对程序员的文章中,我们经常被告知要避免重复代码,“不要重复自己”是我们经常听到的忠告。所以,我们应该尽可能多地重用已有的代码。
这正是为什么很多开发者热衷于把共享代码提取成库,以便重复利用。
然而,这种习惯有时候可能会导致不好的后果。
我注意到,一旦开始提取共享代码,依赖性就会增加。
你可能会发现自己需要频繁地维护那些几乎不再使用的库。这些库其实成了一种额外的负担,你不得不管理更多的代码。
例子
来看一个例子:我创建了一个库,用于计算偶数和奇数。在这个例子中,我们使用了一个专门的库。
# odd_even_library.py
def is_odd(num):
return num % 2 != 0
def is_even(num):
return num % 2 == 0
# Main code
from odd_even_library import is_odd, is_even
number = 7
if is_odd(number):
print(f"{number} is an odd number.")
else:
print(f"{number} is an even number.")
现在,让我们来看另一个例子,在这个例子中,我们不使用库来实现同样的功能。通过这两个例子,我们可以更好地理解在实际的开发过程中,何时应该提取库,何时不应该这么做。
def is_odd(num):
return num % 2 != 0
def is_even(num):
return num % 2 == 0
# Main code
number = 7
if is_odd(number):
print(f"{number} is an odd number.")
else:
print(f"{number} is an even number.")
当我们谈论代码重用和依赖性时,让我以 odd_even_library.py 为例来说明这一点。
在第一个案例中,我们创建了一个名为 odd_even_library.py 的库。这个库包含了一些用于计算奇数和偶数的函数。如果我们想在主代码中使用这个库,就必须导入它,这样就引入了对 odd_even_library 模块的依赖。这种情况下,如果库本身还有其他依赖,那些依赖也会成为我们主代码的一部分。
我用一个简单的例子向你展示这一点,但你也可以考虑更复杂的情况。当一个库有自己的依赖时,它可能导致一个非常复杂的依赖树。
在第二个例子中,我们没有外部依赖。奇数和偶数的相关代码直接包含在主代码中,无需引入任何外部模块。
我们应该怎么做?
回到 odd_even_library.py 这个例子。当我们最初创建这个库时,我们并不清楚未来会如何使用它。如果你在创建一个库,而在那一刻你不确定将来的使用场景,那么最好是不要创建它。因为这只会在未来给你带来不必要的麻烦。
2. 使用领域语言
回想我刚开始写代码的时候,我习惯于遵循导师的建议。他们总是使用领域特定的语言。当时,我并不理解这样做的重要性,甚至以为这只是为了显得更专业。
然而,当我开始独立开发项目时,我并没有使用领域特定的语言。比如,在构建图书馆管理系统时,我不会使用 add_book
、find_book_by_title
和 list_all_books
这样的函数名,而是倾向于使用更通用的名称。
因为我是唯一编码的人,所以最初并没有遇到任何问题。
这对我的思维有什么影响?
这种做法让我开始相信,使用领域特定语言是不必要的,因为我可以在没有它的情况下完成许多个人项目。当我阅读其他人为开源软件编写的代码时,他们都使用了领域特定的语言,我曾以为这是在浪费时间。
什么时候我的看法发生了改变?
现在,当我写这篇文章时,我已经忘记了具体的代码。但我仍然记得那个情况。我记得当时我在构建一个博客平台,故意不使用领域语言,而是选择了通用的名称。让我给你举一个当时代码的例子。
def calculate(a, b):
x = a + b
y = x * 2
z = y / 3
return z
result = calculate(5, 7)
print("Result:", result)
在我们的示例中,我们有一个简单的计算函数。这个函数接受两个参数:a 和 b。在函数内部,我们定义了三个变量:x、y 和 z。但是,这些变量的命名并没有提供任何关于它们存在目的的上下文信息。要想正确地理解这段代码,你需要对整个代码进行详细的分析。
在修复代码中的错误时,如果变量命名不够清晰,你会遇到困难。你需要牢记代码库中每个变量存在的原因。如果你是在团队中工作,你的团队成员在理解这些代码时同样会面临挑战。
在我的经验中,当我回到我的项目并尝试对其进行修改时,我遇到了困难。为了做出单一的更改,我不得不跟踪这些变量的用途和变化。我使用笔和纸来记录每个变量的功能和它们的变化。在那时,我开始批评我自己的方法。
通过这次经验,我学到了一个非常宝贵的教训:在编写代码时,清晰、具有描述性的命名是非常重要的。
如果我们回头看一下之前的代码:
def calculate_sum_and_average(first_number, second_number):
sum_of_numbers = first_number + second_number
double_sum = sum_of_numbers * 2
average = double_sum / 3
return average
result = calculate_sum_and_average(5, 7)
print("Average:", result)
因此,使用有意义的变量和函数名称让我们在上述代码中轻松地进行了必要的更改。
3.编写具有单一抽象级别的函数
在我阅读其他程序员编写的代码时,我经常发现一种普遍现象:一些程序员在编码时不会坚持单一的抽象级别。
用电视遥控器来理解抽象
在我们探讨程序员在这方面的常见错误之前,让我们先借助一个简单的例子来理解什么是抽象——电视遥控器。
想象一下,当我们使用电视遥控器时,我们并不需要了解电视内部的所有复杂机制。例如,我们不需要知道电视扬声器的音量是如何变化的,或者遥控器上的某个按钮是如何改变电视屏幕显示的。遥控器使我们能够控制电视的复杂功能,而不需要完全理解其内部工作原理。
浏览器示例
以任何网络浏览器为例,比如 Google Chrome。当你在浏览器中输入 youtube.com,网站就会打开。在这个过程中,你不需要知道 HTTP 协议是如何工作的,什么是服务器。它们背后的复杂性对用户来说是隐藏的,但功能却是有效的。
程序员犯了什么错误?
很多程序员在编写代码时,经常忽视了这种抽象级别的重要性。他们可能会混合使用不同层次的抽象,导致代码难以理解和维护。这就像在不理解电视内部原理的情况下,试图直接操作电视的内部组件,而不是使用遥控器。在编程中,我们应该努力保持函数和组件在同一抽象级别上,这样代码才会更加清晰、易于理解。
#Function to calculate area
def calculate_rectangle_area(length, width):
return length * width
# Function to ask the user for dimensions
def get_and_calculate_rectangle_area():
length = float(input("Enter the length of the rectangle: "))
width = float(input("Enter the width of the rectangle: "))
area = calculate_rectangle_area(length, width)
print(f"The area of the rectangle is: {area}")
def main():
print("Welcome to the Rectangle Area Calculator")
get_and_calculate_rectangle_area()
在我们之前的例子中,有一个函数叫做 “get_and_calculate_rectangle_area”,这个函数既负责处理用户输入,又负责计算矩形的面积。这种做法增加了代码的复杂性。
如果我们能够将这些职责拆分成两个不同的函数,那么代码就会变得更加简洁明了。
更好的实践方法应该是这样的:
# Function to calculate the area
def calculate_rectangle_area(length, width):
return length * width
# Main function
def main():
print("Welcome to the Rectangle Area Calculator")
length = float(input("Enter the length of the rectangle: "))
width = float(input("Enter the width of the rectangle: "))
area = calculate_rectangle_area(length, width)
print(f"The area of the rectangle is: {area}")
在前面的代码例子中,我们设计了一个名为 “calculate_rectangle_area” 的函数,它的唯一职责是计算矩形面积。这个函数不包含任何额外的抽象层次。
主函数则处理整个流程。它负责显示信息并调用 “calculate_rectangle_area” 来进行面积计算。通过这种方式,我们把抽象级别降低到了一级,使代码更加清晰和易于维护。
4. 编程中没有固定的答案
在第一点中,我提到了不应该轻易把共享代码提取到库中。在第三点中,我建议编写具有单一抽象级别的函数。这两个原则看似矛盾,但实际上并不冲突。
我并没有说什么?
我并不是说你永远不应该把共享代码放入库中。只是要注意,当你尝试将代码提取到单独的库时,确保这段代码不仅仅适用于当前项目,而且对其他项目也有用。如果提取出来的代码能帮助你更好地组织代码,并提高可维护性,那么你应该去创建一个库。这其实是与编写单一抽象级别的函数原则相一致的。
在考虑是否将共享代码提取到库中时,你需要评估各种权衡因素。如果项目需要的代码行数不多,就不要通过添加不必要的额外代码来增加项目的复杂性。
事物将继续变化
编程是一个不断演进的领域。新的框架、原则和工具不断出现。在某些情况下被认为是最佳实践的方法,在其他情况下可能就不适用了。
因此,你应该尽量了解所有的最佳实践,但在做决策时需要根据项目需求和具体情况来定。面对同一个问题,可能有多种解决方案,关键在于根据具体的情况选择最合适的方法。