Skip to content

对象健身操

编写代码实现功能本身并不难,但要写出可维护、优雅的代码,却不容易。这种能力不是别人可以直接教会你的,它需要你用实践去积累,用时间去沉淀,用错误、教训乃至挫折去体会其中的奥妙。除了学习理论,还需要大量工程实践。每隔一段时间,就得重新回顾这些设计理念,反思、修正、再实践。就像品酒,初尝苦涩,回味甘醇

在不断实践和反思的过程中,我发现一些文章能让我豁然开朗。《软件开发沉思录:ThoughtWorks 文集》之 对象健身操详解 是 ThoughtWorks 团队的一篇经典文章,通过形象的“健身操”比喻,讨论了面向对象设计中的一些重要原则

我很喜欢这篇文章,已经读过多次,每次都有新的领悟。文中提到的“九诫”,几乎每一条都能立刻应用到日常编码中。以下是我反复思考后整理的几点体会:

简洁和留白的艺术

很多人觉得自己没有艺术天赋,貌似事实就是如此。看看大多数人做的 PPT,那个难看啊!不仅是 PPT,写文章、画画、写代码也一样。在代码里,这种“艺术感”体现为可读性

举个例子,小时候拆开黑白电视机的后盖,发现里面的线是乱七八糟的,那就是“不可读”。写代码也一样,可读的代码不复杂,简洁干净,它的显著特点是控制逻辑少,比如:程序的分支少。写代码是有分支的,满足某个逻辑走分支 A,不满足某个逻辑走分支 B,然后分支里面还有分支,分支之间可能还有循环和跳转。“好看的代码”就是要把所有分支、循环、跳转减少,让人能够看到执行的条理性,而不是一个迷宫

我相信,每个人都起码应该学习一点点的设计原理,这在任何地方都用得上。不需要成为专家,哪怕只是掌握一点常识,也能让内容瞬间提升几个档次。那么,设计的最少必要知识是什么?两个词:

简洁和留白

如何在编码中实践呢?在写代码时,每个方法尽量只用一级缩进 ,这样逻辑清晰,也能避免嵌套地狱;使用卫语句替代 else,可以让主流程更直观、扁平。代码嵌套、缩进越多,阅读时占用的脑力就越大,就像 8G 内存跑大模型一样,容易卡顿发热。你可能会说:“循环里还要嵌套循环怎么办?”这时可以把内部逻辑抽取成小函数 。写小函数多了,还可以进一步做抽象,把存在相同参数的小函数抽取到类里,让模块之间的职责更内聚

当然,这不是教条。不要手里拿着锤子,看什么都是钉子。始终记住你的目的:提升可读性与维护性。有时,双重嵌套循环反而更易理解,特别是在一些排序算法或复杂处理逻辑中,这种适度的嵌套是合理的

而留白是代码的节奏感,让逻辑块分明,就像文章段落和空行,让阅读更舒适。函数之间、逻辑块之间以及运算符两边适当留空行,这需要你平时通过阅读优质代码慢慢体会,几句话无法完全传达

通过减少缩进保持简洁,同时适当留白,就能让代码变得可读又优雅,品味瞬间提升一个档次

关注点分离

在这个世界上,看不到的东西,它的威力远胜过我们看得到的任何东西。地下的东西创造出地上的东西,看不见的东西创造出看得见的东西

正如看不见的力量能够决定可见世界的秩序,软件系统中那些隐藏的关注点和职责,也决定着代码能否清晰、可维护

关注点分离(SoC)就是让这些“看不见的力量”明确化把复杂问题拆解成独立、可管理的小块,每个模块只关注自己的责任。问题越复杂,要同时关注的点越多,而个人的能力有限,混在一起只会产生混乱

想象一下,所有的功能就像工具间里摆放着的各种各样的工具,等着上层业务来对它们进行编排。工具于工具之间、模块和模块之间互不依赖,如果它们要建立联系,就得像工业化生产一样,依赖于一个标准的协议或是接口,而不是具体的实现和数据去完成

函数级别的关注点分离

go
// 错误示例:逻辑混杂
func ProcessOrder(order Order) {
    // 验证库存
    // 计算价格
    // 更新数据库
    // 发送通知
}

这个函数把验证、计算、保存、通知全混在一起,阅读和维护时容易出错

go
// 改进示例:关注点分离
func ProcessOrder(order Order) {
    if !CheckStock(order) { // 只关心库存
		return
	}

    total := CalculatePrice(order) // 只关心价格计算

    SaveOrder(order, total) // 只关心数据存储

    NotifyCustomer(order) // 只关心通知逻辑
}

每个函数只做一件事,主流程清晰,使用者无需关心函数内部实现

类与模块级的关注点分离

抽象与接口:使用接口隔离实现,让调用者只关心功能,不关心细节

go
type PaymentProcessor interface {
    Charge(amount float64) error
}

type StripeProcessor struct {}

func (s StripeProcessor) Charge(amount float64) error {
    // 调用 Stripe API
    return nil
}

func Checkout(order Order, processor PaymentProcessor) {
    processor.Charge(order.Total)
}

Checkout 函数不关心 Stripe API 的实现,只依赖接口,这就是关注点分离

其实,最经典的例子就是三层架构,很多人都用过,但很少真正思考过其设计理念。Controller、Service、Repository 分别负责请求处理、业务逻辑、数据存储,职责清晰

小结

写代码时,可以自查这些点:

  • 函数级别。每个函数是否只做一件事?是否混杂了不同关注点?
  • 类/模块级别。类或模块是否只承担一个职责?是否通过接口或抽象隐藏实现细节?
  • 系统架构级别。不同模块是否有明确的边界?是否能单独测试、替换或复用?

关注点分离就像把复杂动作拆成健身操的小动作,每个动作只练特定肌群。函数、类、模块、服务都是你的“小动作单元”,只关注自己的职责,让整体系统清晰、可维护、可扩展

当你的代码让自己或团队一眼就能理解,同时内部实现可随时调整而不影响其他模块时,就说明你已经很好地实践了关注点分离

设计模式不是银弹:保持简单,避免过度设计

学习面向对象设计模式时,不要迷失在那 23 个设计模式里。概括下来,最重要的原则就两条:

  1. 面向接口编程
  2. 多用组合,少用继承

面向接口编程。使用者无需关心数据类型、结构或算法的具体实现。只要知道接口提供了什么功能即可。这种做法利于抽象与封装,支持动态绑定和多态,完全符合面向对象的思想

多用组合,少用继承。继承会让子类依赖父类的设计和实现细节,当父类修改时,子类往往也要跟着改。我们常误以为继承主要是为了重用代码,但实际上子类经常需要重写父类的方法。更合理的做法是通过组合实现复用,同时利用继承的真正价值,多态,让行为可扩展而不破坏原有结构

设计模式并不是越多越牛。它是软件复杂度到达一定程度后,对反复出现问题的一种抽象的总结

能把复杂逻辑用简洁、干净的代码表达出来,才是真正的高手

大多数场景起初都很简单,简单的功能、简单的对象关系、简单的流程。如果在简单场景下无缘无故堆满设计模式,往往不是提高代码质量,而是炫技。看似高大上,实际上却给自己增加了复杂度,甚至搬起石头砸自己的脚

正确的做法是先解决问题,再提炼模式。当系统足够复杂、可维护性成为瓶颈时,才去引入设计模式去优化、解耦和抽象。这样使用设计模式,不仅自然、合理,也能真正提升系统的稳定性和可扩展性

编程规约

在写代码时,参考已有的最佳实践非常重要。为什么要有编程规范和最佳实践,要让所有人按一定的规范来编程呢?

规范的代码整齐清晰,便于自己和他人理解,也更容易维护。效率来源于结构化,而不是杂乱。规范能帮助你避开常见坑,减少 Bug,让软件质量更高。当团队成员都遵循相同规范,协作就更顺畅,沟通成本也大幅降低。至于一些风格细节,比如左大括号换行与否、缩进用空格还是 Tab,这类问题没有绝对对错,只要团队统一就好

如果没有这类规范和最佳实践的积累,是很难成长为真正的工程师的