写出能长期使用的代码:一些实用的经验
每个程序员都记得自己第一次让代码成功运行时的感觉。屏幕上出现结果,问题得到解决,那种兴奋感让人难忘。但是,能运行的代码和好的代码之间,有很大的差别。从编写功能到编写软件,关键在于学会写出不仅能解决当前问题,还能长期使用的代码。
经过多年编写、检查和维护软件,我注意到一些区别业余代码和专业代码的模式。下面是一些我希望在职业生涯早期就有人告诉我的经验。
代码是写给人看的
首先要明白,代码主要是写给人看的,其次才是给机器运行。编译器不在乎你起的变量名,但半夜两点调试你代码的程序员一定在乎。
让代码自己讲故事
好的代码应该像好文章一样容易理解。使用足够清晰的命名,这样就不需要太多注释来解释代码在做什么。
看看下面的例子:
修改前:
def calc(c, p):
# 计算总价
total = 0
for id in p:
total += db.get_price(id)
return total修改后:
def calculate_order_total_for_customer(customer: Customer, product_ids: list[int]) -> float:
total_price = 0.0
for product_id in product_ids:
total_price += db.get_price_for_product(product_id)
return total_price第二个版本自己就讲清楚了故事。新团队成员不用查文档或问别人,就能理解业务逻辑。
注释要解释原因,而不是重复代码
既然代码本身已经说明了它在做什么,注释就应该用来解释为什么这样写:
def is_user_eligible_for_discount(user: User) -> bool:
# 市场部决定:旧版VIP身份(2021年前系统)
# 不符合新版白金折扣计划资格
if user.is_legacy_vip:
return False
return user.membership_level >= PLATINUM这个注释记录了业务背景,否则这些信息很容易丢失。
清晰比巧妙更重要
不要为了展示聪明而写那些需要高深知识才能理解的一行代码。你未来的自己会感谢你这个决定:
修改前:
def is_valid(id_val: int) -> bool:
return bin(id_val & 0x7FFF).count('1') % 2 == (id_val & 0x8000) >> 15修改后:
def is_id_valid(id_val: int) -> bool:
"""验证ID的奇偶校验位"""
VALUE_MASK = 0x7FFF
PARITY_MASK = 0x8000
value_bits = id_val & VALUE_MASK
encoded_parity = (id_val & PARITY_MASK) >> 15
calculated_parity = bin(value_bits).count('1') % 2
return calculated_parity == encoded_parity构建可靠的系统
代码能运行还不够,专业软件必须能妥善处理各种意外情况。
错误不是特殊情况
在生产系统中,错误是不可避免的。网络调用会失败,文件会丢失,用户会输入意外内容。设计错误处理要像设计正常流程一样仔细:
有问题的做法:
def get_user_age(user_id: int) -> int:
age = db.query_age(user_id)
return age if age is not None else -1 # 使用魔法值!更好的做法:
class UserNotFoundError(Exception):
pass
def get_user_age(user_id: int) -> int:
age = db.query_age(user_id)
if age is None:
raise UserNotFoundError(f"用户 {user_id} 不存在")
return age第二种方法强制调用者明确处理错误情况,避免了难以调试的静默失败。
不变的数据更安全
可变状态是许多难以复现bug的根源。当数据可能意外改变时,理解程序行为就变得困难得多:
from dataclasses import dataclass, replace
@dataclass(frozen=True) # 不可变!
class UserSettings:
theme: str
notifications_enabled: bool
def create_preview_settings(base_settings: UserSettings) -> UserSettings:
# 创建新对象而不是修改原对象
return replace(base_settings, theme="dark_preview")使用不可变对象可以消除整类bug,让并发编程更安全。
为未来做设计
今天写的代码会在未来多年被修改、扩展和调试。要考虑到变化来组织代码。
依赖注入让代码更灵活
不要硬编码依赖,而是把它们作为参数接收。这样代码既容易测试,也容易适应变化:
不灵活的做法:
class ReportService:
def __init__(self):
self._generator = CsvReportGenerator() # 永远只能用CSV
def create_report(self, data):
return self._generator.generate(data)灵活的做法:
class ReportService:
def __init__(self, generator: ReportGenerator):
self._generator = generator
def create_report(self, data):
return self._generator.generate(data)现在你可以轻松切换到JSON报告,注入假的生成器进行测试,或者同时支持多种格式。
组合优于继承
继承感觉很自然,它反映了我们对现实世界的分类方式。但在软件中,组合通常更灵活:
继承方式:
class ElectricCar(Car):
def charge(self): ...
def drive(self): ...组合方式:
class Vehicle:
def __init__(self, engine: Engine, fuel_system: FuelSystem):
self._engine = engine
self._fuel_system = fuel_system
def start(self):
self._engine.start()
def refuel(self):
self._fuel_system.refuel()使用组合,你可以轻松创建混合动力车辆、电动摩托车或氢燃料汽车,而不需要重构整个类层次结构。
测试是预测未来的利器
测试不只是为了发现bug,更是为了预测代码在不同条件下的行为。
测试行为,而不是实现
关注测试代码完成了什么,而不是它怎么完成的:
关注实现的测试(脆弱):
def test_user_service_calls_database():
service.create_user(user_data)
mock_db.insert.assert_called_once() # 测试内部机制关注行为的测试(健壮):
def test_creating_user_makes_them_findable():
user_id = service.create_user(user_data)
found_user = service.get_user(user_id)
assert found_user.email == user_data["email"] # 测试最终结果第二个测试在重构后仍然有效,因为它关注的是核心行为。
尽量使用真实对象
模拟对象和桩对象有其用处,但实现真实行为的假对象能让测试更有信心:
class InMemoryUserRepository:
def __init__(self):
self._users = {}
self._next_id = 1
def save(self, user: User) -> int:
user_id = self._next_id
self._next_id += 1
self._users[user_id] = user
return user_id
def find(self, user_id: int) -> User:
return self._users.get(user_id)这个假仓库的行为像真的一样,但不需要数据库,让你的测试既快速又可靠。
思维方式的转变
这些原则代表了一种根本的思维转变。不要只问"我怎么能让这个工作?",开始问:
"别人怎么理解这段代码?"
"失败时会发生什么?"
"未来这个需要怎么改变?"
"我怎么验证这个是正确的?"
专业软件开发是一种共情能力的体现,对你未来的自己、你的团队成员、依赖你代码的用户都要有共情。你写的每一行代码都是为他人考虑的小小举动。
能运行的代码和专业软件之间的区别,不在于知道更多高级技术或框架,而在于培养写出既能满足当前需求、又能适应未来变化的代码的纪律性。当你开始这样思考时,你会发现好代码不仅更容易维护,实际上在一开始写起来也更容易。
在代码质量上的投入,会在软件运行的每一天带来回报。从今天开始建立这种投入吧。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!