从“能用”到“好用”:5个设计决策判断是不是有经验的程序员

最近忙着一些项目上的事,搞得头发又秃了一点,突然感觉有点罪恶感。

前端时间我受邀帮忙面试一些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 代码很容易。 但写出那种经得起时间考验的代码? 那才叫一门手艺。

相关推荐
喜欢吃豆5 小时前
一份面向研究人员的强化学习对齐指南:为自定义语言模型实施与评估 PPO 和 DPO
人工智能·语言模型·自然语言处理·架构·大模型
IT小番茄5 小时前
铁路智能运维系统
架构
thginWalker6 小时前
《架构设计精讲》学习笔记
架构
文火冰糖的硅基工坊6 小时前
《投资-113》价值投资者的认知升级与交易规则重构 - 复利故事终止的前兆
重构·架构·投资
妖孽白YoonA7 小时前
NestJS - 循环依赖地狱及其避免方法
架构·nestjs
云澈ovo8 小时前
RISC-V 架构适配:开源 AI 工具链的跨平台编译优化全流程(附实战指南)
架构·开源·risc-v
kkk123449 小时前
GPU微架构
架构
程序员卷卷狗10 小时前
JVM实战:从内存模型到性能调优的全链路剖析
java·jvm·后端·性能优化·架构
道可到10 小时前
直接可以拿来的面经 | 从JDK 8到JDK 21:一次团队升级的实战经验与价值复盘
java·面试·架构