最近忙着一些项目上的事,搞得头发又秃了一点,突然感觉有点罪恶感。
前端时间我受邀帮忙面试一些AI岗位的候选人,除了考察岗位相关的职责外,还问了一些基础的编程问题。通过这些面试,我发现可以将Python程序员大致分为两类:
- 第一类:他们的代码能跑起来就行,完成任务是首要目标。
- 第二类:他们的代码不仅能顺利运行,还能经得起时间的考验,即使项目规模扩大或者团队成员更替,代码依然清晰、稳定。
这两类程序员的区别,不仅仅是代码写得熟不熟练,或者程序跑得快不快。真正的差距在于**"设计决策"**------那些看似不起眼的小选择,实际上会悄悄影响项目的未来:是能够轻松扩展、维护,还是在新人接手时直接崩溃。
在审查和编写了多年的Python代码后,我总结出了五个能明显看出"这是资深开发者写的"的设计选择。这些选择不仅体现了开发者的经验,也决定了代码的质量和项目的长期可维护性。

1、为什么返回对象,而不是字典?(可预测性比一时的方便更重要)
很多刚学 Python 的新手都喜欢用字典(dictionary)来返回数据。 为什么?因为写起来快、用起来简单。 但问题来了:六个月后,你就想打自己了。
python
# 新手常见的写法
def get_user_data():
# 直接返回一个包含各种信息的字典
return {"name": "Alice", "age": 29, "verified": True}
user = get_user_data()
print(user["name"]) # 现在能跑,但万一以后字段名变了呢?
现在想象一下,如果某个同事在未来的某天,为了让代码更清晰,把字典里的键名"verified"
改成了"is_verified"
。
这时,你代码里所有用到user["verified"]
的地方都会悄悄地"罢工",程序在运行时才会报错,让你调试起来非常痛苦。
因此,经验丰富的开发者更喜欢使用结构化的返回 ------ 这种方式返回的数据是可预测、类型安全并且自带说明的(自文档化)。
python
from dataclasses import dataclass
# 定义一个专门用来存放用户数据的"模具"
@dataclass
class User:
name: str
age: int
verified: bool
# 函数明确声明它将返回一个 User 类型的对象
def get_user_data() -> User:
return User("Alice", 29, True)
user = get_user_data()
print(user.name) # 通过属性访问,而不是键名
这样做的好处是什么?
- 编辑器自动补全 :你的代码编辑器(如 VS Code, PyCharm)会知道
user
对象有哪些属性(如name
,age
),并为你提供代码自动补全,大大减少拼写错误。 - 即时反馈 :如果你修改了
User
类里的属性名(比如把verified
改成is_verified
),那么所有还在使用旧名字user.verified
的地方,你的编辑器会立刻划线提示错误,而不是等到程序运行时才发现。 - 易于维护 :代码的可读性大大增强,任何人看到这个函数都知道它会返回一个结构清晰的
User
对象,后续的修改和维护工作变得轻松许多。
根据 JetBrains 的 2024 年 Python 开发者调查,61% 的生产环境 Python 项目现在使用类型提示和数据类(data classes)来提高代码的可维护性。这难道是巧合吗?当然不是。
2. 简洁的上下文管理器(总是优雅退出)
新手处理文件(或其他需要关闭的资源)通常是这样做的:
ini
file = open("data.txt")
data = file.read()
# oops, forgot to close()
如果在读取数据的过程中程序崩溃了,file.close()
这一步永远不会被执行。这会导致文件句柄一直被占用,造成资源泄漏。
有经验的程序员使用 Python 内置的with
语句来处理资源:
csharp
with open("data.txt") as file:
data = file.read()
仅仅管理文件是基础操作。更高级的用法是创建自定义的上下文管理器,用来管理任何需要在开始时设置、在结束时清理的操作。
看下面的例子,我们用它来计时:
python
from contextlib import contextmanager
import time
@contextmanager
def timed(label):
start = time.time()
yield
print(f"{label}: {time.time() - start:.3f}s")
with timed("Processing"):
[x**2 for x in range(10_000_000)]
输出结果:
makefile
Processing: 0.387s
这不仅简洁,而且是个优雅的监控 。 上下文管理器 (with
语句)是一种强大的工具,用于确保代码的开始和结束阶段都有可靠的设置和清理逻辑。
3. 避免"魔幻式"导入
你可能见过这种写法:
python
# 文件名: utils/__init__.py
from .string_ops import *
from .math_ops import *
这种写法使用了*
(星号),意思是"把另一个文件里的所有东西都导入进来"。
然后,其他人可能会这样写代码:
javascript
from utils import slugify
问题来了:slugify
这个函数到底是从string_ops.py
还是math_ops.py
来的?没人能立刻想起来。如果项目变得越来越大,想找到这个函数的原始位置就会非常困难,就像大海捞针。
有经验的程序员更喜欢"指名道姓":
python
# 文件名: utils/__init__.py
from .string_ops import slugify # 从 string_ops 文件中导入 slugify
from .math_ops import mean # 从 math_ops 文件中导入 mean
# 明确声明这个包(utils)对外提供哪些函数
__all__ = ["slugify", "mean"]
多敲几个字,换来的是 ------ 眼就知道有啥、改起来不踩坑、IDE 还能自动补全。
记住:
代码被阅读的次数是其被编写次数的10倍。不要为了少打几个字,而牺牲了更重要的可读性。
4. 代码设计要考虑可扩展性
程序员通常会想:"这个功能现在能跑就行。"
而有经验的开发者会想:"以后如果要添加新功能或改变,要怎么处理才不会出问题?"
举个例子:
python
def process_payment(method, amount):
if method == "paypal":
print("PayPal processing")
elif method == "stripe":
print("Stripe processing")
现在你想加入其他付款方式,比如加密货币或者礼品卡......一堆代码就会变得混乱。
有经验的开发者会怎么写呢?
他们会这样设计:
ruby
class PaymentProcessor:
def process(self, amount): ...
class PayPal(PaymentProcessor):
def process(self, amount): print("PayPal processing")
class Stripe(PaymentProcessor):
def process(self, amount): print("Stripe processing")
def handle_payment(processor: PaymentProcessor, amount):
processor.process(amount)
handle_payment(Stripe(), 100)
这样一来,添加新付款方式,只需要新写一个类,不需要改掉已有的代码。
这就是"扩展性"的例子:设计时考虑未知的变动,让未来加入新功能变得简单顺利。
5. 从新手到专业:日志记录的进化之路
每个程序员都是这样开始的:
bash
print("User created:", user_id)
然后项目上线了...突然发现到处都是 print 语句,和正常的程序输出混在一起,根本分不清哪些是信息,哪些是错误。
资深程序员是这样做的:
ini
import logging
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
)
logger = logging.getLogger(__name__)
logger.info("User created: %s", user_id)
logger.warning("Email not verified for user: %s", user_id)
区别在哪里?
使用专业的日志系统,你可以按重要程度筛选日志,比如只看错误信息或者只看警告。你还可以把日志输出到不同地方,比如保存到文件、发送到服务器,或者显示在控制台。最重要的是,系统会自动帮你记录时间戳、模块名称和相关信息。
最终感悟
当一个有经验的Python 开发者,不是靠记住一堆库或语法规则。 而是真正关心下一个看你代码的人------让他们觉得舒服。 你要做的,是在设计代码时,让它变得易预测、易理解、易扩展,不管以后谁来接手。 因为写 Python 代码很容易。 但写出那种经得起时间考验的代码? 那才叫一门手艺。