写代码这件事,到底该讲究点什么?

程序员圈子里流传着一句老话,代码写得出来不难,难的是半年后自己还看得懂。这句玩笑背后藏着软件工程最核心的痛点------如何让代码在需求不断变化的现实世界里,依然保持清晰、可维护、不崩溃

SOLID 原则就是为了应对这个痛点而生的。它由 Robert C. Martin(业内人称 Uncle Bob)在 2000 年前后系统整理提出,五个字母分别对应五条面向对象设计的黄金法则 。这套东西流传了二十多年依然是各大公司代码评审的标准,不是因为它多高深,而是因为它踩过的坑实在太真实了。

下面就把这五条原则掰开揉碎,逐一用 Python 代码演示给你看,顺便聊聊 Python 世界里那些同样重要、却常被忽略的编程哲学。


🧭 SOLID 到底是哪五个字母

先给个全景图,心里有个谱再往下看会轻松很多。

字母 全称 一句话理解
S Single Responsibility Principle(单一职责原则) 一个类只干一件事
O Open/Closed Principle(开闭原则) 加新功能靠扩展,不靠改老代码
L Liskov Substitution Principle(里氏替换原则) 子类要能无缝顶替父类
I Interface Segregation Principle(接口隔离原则) 别强迫别人实现用不到的方法
D Dependency Inversion Principle(依赖倒置原则) 依赖抽象,别依赖具体实现

这五条原则并不是孤立存在的,它们之间互相呼应,共同指向一个目标------降低耦合,提高代码的可扩展性和可测试性


🔍 逐条拆解,配上真实代码

1. 单一职责原则(SRP)

核心思想很朴素,一个类应该只有一个引起它变化的原因。听起来抽象,落地到代码就是别让一个类身兼数职。

反面教材长这样,一个 Employee 类既管员工信息,又管薪资计算,还顺带管数据库存储:

python 复制代码
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def calculate_pay(self):
        return self.salary * 1.1  # 加薪逻辑

    def save_to_database(self):
        # 直接把数据库操作也塞进来
        print(f"Saving {self.name} to DB...")

问题在于,一旦薪资计算规则变了,或者数据库换了个存储方案,都要来改这一个类,风险叠加,谁改谁提心吊胆。拆开就清爽多了:

python 复制代码
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary


class PayCalculator:
    def calculate_pay(self, employee):
        return employee.salary * 1.1


class EmployeeRepository:
    def save(self, employee):
        print(f"Saving {employee.name} to DB...")

每个类只对一类变化负责,改薪资算法不会碰到存储逻辑,改存储方式也不会牵连薪资计算 。

2. 开闭原则(OCP)

这条原则说的是,软件实体应该对扩展开放,对修改关闭。翻译成人话就是,新增需求时应该靠加代码 而不是改代码

假设你在写一个图形面积计算器,一开始只有矩形:

python 复制代码
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height


class AreaCalculator:
    def calculate(self, shape):
        if isinstance(shape, Rectangle):
            return shape.width * shape.height

后来产品经理说要加圆形。你的第一反应可能是在 calculate 里加个 elif,但这样每加一种图形都要回头改这个方法,改一次就有一次出 bug 的风险。更好的做法是让每种图形自己知道怎么算面积:

python 复制代码
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2


class AreaCalculator:
    def calculate(self, shape: Shape):
        return shape.area()

以后不管加三角形还是加菱形,AreaCalculator 这个类一行都不用改 ,直接新写一个类继承 Shape 就行 。

3. 里氏替换原则(LSP)

这条最容易被误解,也最容易被写错。它要求的是,任何父类能出现的地方,子类都必须能无缝替换,不能出现行为异常。

用更严谨一点的说法,子类的前置条件不能比父类更严格,后置条件不能比父类更宽松,写成公式大概是这个意思:

P子类≤P父类,Q子类≥Q父类 P_{子类} \leq P_{父类}, \quad Q_{子类} \geq Q_{父类} P子类≤P父类,Q子类≥Q父类

经典的翻车案例是正方形继承矩形。数学上正方形是特殊的矩形没错,但代码里这么继承会出大问题:

python 复制代码
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def area(self):
        return self.width * self.height


class Square(Rectangle):
    def set_width(self, width):
        self.width = width
        self.height = width  # 强行同步,破坏了矩形的语义

    def set_height(self, height):
        self.width = height
        self.height = height

如果有一段代码假设 Rectangle 的宽高可以独立设置,一旦传进来的是 Square,行为就悄悄变了,这种隐藏 bug 排查起来能让人抓狂。正确做法是不要强行建立继承关系,让两者各自独立实现共同的接口:

python 复制代码
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

这样两个类各自独立,谁也不会因为对方的语义假设而莫名其妙出错 。

4. 接口隔离原则(ISP)

这条讲的是,不要设计臃肿的大接口,把很多互不相关的方法硬塞在一起,逼着实现类去写一堆用不到的空方法。

想象一个 Worker 接口,既要求实现 work() 又要求实现 eat()

python 复制代码
from abc import ABC, abstractmethod

class Worker(ABC):
    @abstractmethod
    def work(self):
        pass

    @abstractmethod
    def eat(self):
        pass


class RobotWorker(Worker):
    def work(self):
        print("机器人在工作")

    def eat(self):
        raise NotImplementedError("机器人不需要吃饭")  # 尴尬了

机器人根本不吃饭,却被迫实现一个毫无意义的方法。拆分成两个小接口就干净多了:

python 复制代码
class Workable(ABC):
    @abstractmethod
    def work(self):
        pass


class Eatable(ABC):
    @abstractmethod
    def eat(self):
        pass


class HumanWorker(Workable, Eatable):
    def work(self):
        print("人类在工作")

    def eat(self):
        print("人类在吃饭")


class RobotWorker(Workable):
    def work(self):
        print("机器人在工作")

接口要小而专一,谁需要什么就实现什么,不该被无关的方法拖累。

5. 依赖倒置原则(DIP)

这是五条里最能提升架构质量的一条,简单说就是高层模块不应该依赖低层模块,两者都应该依赖抽象。

反面例子,一个 NotificationService 直接写死依赖某个具体的邮件发送类:

python 复制代码
class EmailSender:
    def send(self, message):
        print(f"发送邮件: {message}")


class NotificationService:
    def __init__(self):
        self.sender = EmailSender()  # 写死了具体实现

    def notify(self, message):
        self.sender.send(message)

想加个短信通知,只能去改 NotificationService 的内部代码,耦合得死死的。改成依赖抽象接口,就灵活多了:

python 复制代码
class MessageSender(ABC):
    @abstractmethod
    def send(self, message):
        pass


class EmailSender(MessageSender):
    def send(self, message):
        print(f"发送邮件: {message}")


class SMSSender(MessageSender):
    def send(self, message):
        print(f"发送短信: {message}")


class NotificationService:
    def __init__(self, sender: MessageSender):  # 依赖抽象,而非具体类
        self.sender = sender

    def notify(self, message):
        self.sender.send(message)


# 使用时想换渠道,直接换个实现类传进去就行
service = NotificationService(SMSSender())
service.notify("你的验证码是123456")

这种写法在 Python 里还有个专属名字叫依赖注入,测试的时候可以轻松传入一个假的 Mock 对象,不用真的发邮件发短信,测试效率直接起飞 。


📊 用一张图理清五者的关系

SOLID 五条原则并不是各自为战,它们环环相扣,最终都服务于同一个目标------让代码在变化面前依然稳如老狗

可以看到,SRP 管的是类内部的职责边界,OCP 管的是扩展方式,LSP 管的是继承体系的行为正确性,ISP 管的是接口的粒度,DIP 管的是模块间的依赖方向。五条拧成一股绳,最终指向低耦合、高内聚这个大目标。


💡 Python 里还有哪些同样重要的编程原则

SOLID 是面向对象设计的原则,但 Python 语言本身还有一套自己的哲学,甚至可以说这套哲学比 SOLID 更早刻进了 Python 程序员的骨子里。

DRY(Don't Repeat Yourself)

别重复造轮子,同一段逻辑不要在代码里到处复制粘贴。一旦需求变了,改一处漏改十处,简直是自找麻烦。

python 复制代码
# 反面: 重复计算折扣逻辑
def calculate_price_a(price):
    return price * 0.9

def calculate_price_b(price):
    return price * 0.9

# 正面: 抽成一个函数复用
def apply_discount(price, rate=0.9):
    return price * rate

KISS(Keep It Simple, Stupid)

能用简单方案解决的问题,别炫技上复杂设计模式。Python 社区特别推崇这一点,代码追求的是可读性优先,而不是秀技巧。

python 复制代码
# 反面: 过度设计一个简单的求和
class SumCalculatorFactory:
    def create_calculator(self):
        return SumCalculator()

class SumCalculator:
    def calculate(self, numbers):
        return sum(numbers)

# 正面: 一行搞定
total = sum(numbers)

YAGNI(You Aren't Gonna Need It)

别为了想象中可能永远不会发生的需求,提前写一堆用不上的扩展接口。这条和 KISS 是一对好搭档,都在提醒你别过度设计。

PEP 8 与 Zen of Python

Python 官方风格指南 PEP 8 规定了命名规范、缩进、行长度这些细节,让整个社区的代码风格高度统一 。而 Python 之禅(在解释器里敲 import this 就能看到)用一句句诗意的话总结了 Python 的设计哲学,比如明确胜于隐晦简单胜于复杂可读性很重要

Composition over Inheritance(组合优于继承)

这条其实和 LSP 相辅相成。继承容易导致层层嵌套的类体系,一旦父类改动,子类可能莫名其妙出问题。组合的做法是把功能拆成独立的小组件,通过持有对象 而不是继承对象来复用逻辑。

python 复制代码
# 继承方式容易导致体系僵化
class FlyingCar(Car, Airplane):
    pass

# 组合方式更灵活
class Car:
    def __init__(self, engine, wheels):
        self.engine = engine
        self.wheels = wheels

    def drive(self):
        self.engine.start()

🧷 写在最后

SOLID 原则和 DRY、KISS、YAGNI 这些理念说到底都在回答同一个问题------代码是写给人看的,顺便让机器执行。刚开始写代码的时候,可能会觉得这些原则太多讲究、太啰嗦,但等到项目跑到几万行代码、几十个人协作维护的时候,你就会真切体会到,当初那份克制和规范,救了自己一命。

Python 这门语言本身骨子里就崇尚简洁和优雅,把 SOLID 的设计智慧和 Python 的哲学结合起来用,写出来的代码往往既好读又耐用。与其说这是一堆规则,不如说这是一群踩过无数坑的前辈,替你把弯路提前标好了。


参考资料

Wikipedia. SOLID . en.wikipedia.org/wiki/SOLID

Splunk. SOLID Design Principles: Hands-On Examples . www.splunk.com/en_us/blog/...

DigitalOcean. SOLID: The First Five Principles of Object-Oriented Design . www.digitalocean.com/community/c...

Real Python. SOLID Principles: Improve Object-Oriented Design in Python . realpython.com/solid-princ...

GeeksforGeeks. SOLID Principles with Real Life Examples . www.geeksforgeeks.org/system-desi...

相关推荐
卷无止境1 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1231 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102162 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb002 小时前
一个项目带你入门AI应用开发01
python
0566463 小时前
Python康复训练——常用标准库
开发语言·python·学习
昆曲之源_娄江河畔3 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql
IT_陈寒3 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端
0566463 小时前
Python康复训练——控制流与函数
开发语言·python·学习
天使day3 小时前
FastAPI快速入门
python·fastapi