![]()
3年前我在Medium连发两篇SOLID系列,阅读量还行,评论区有人夸"讲清楚了"。最近重读,发现至少3处硬伤——不是表述不清,是根本理解错了。
这感觉像翻出大学写的代码:能跑,但不敢承认是自己写的。区别是,代码可以删,文章删了也会被人截图。
所以有了这次重写。不是更新,是推翻重来。
为什么SOLID值得被"过度关注"
SOLID是Robert C. Martin(圈内叫Uncle Bob)提出的5条设计原则,专门对付一个老问题:代码今天能跑,明天改不动。
5个字母各管一摊:单一职责(S)、开闭(O)、里氏替换(L)、接口隔离(I)、依赖倒置(D)。名字像咒语,实际都是程序员日常踩的坑。
我最初学的时候,把SOLID当"最佳实践清单"—— check一遍,代码就干净了。后来发现这是误读。它不是检查表,是识别坏味道的嗅觉训练。
比如"单一职责",新手爱理解为"一个类只做一件事"。但什么事算"一件"?有人把用户注册拆成Validator、Hasher、Notifier三个类,结果改个字段要跨5个文件。过细和过粗一样臭。
Uncle Bob的原话是:「一个类应该只有一个引起它变化的原因。」关键词是"变化",不是"功能"。
![]()
我3年前错在哪
翻旧文,最明显的问题是举例失真。为了讲"依赖倒置",我虚构了一个"数据库层抽象"的例子,逻辑自洽,但脱离真实场景。
实际项目中,依赖倒置的痛点不在"要不要抽象",而在"什么时候抽象"。过早抽象是灾难——你预判的扩展点,可能3年都没用到,但每层抽象都在消耗理解成本。
另一个错误是把5条原则当平等关系。其实S和O是目标,L、I、D是手段。里氏替换(L)保证继承不出幺蛾子,接口隔离(I)和依赖倒置(D)帮你控制模块间的耦合方式。
我当年按字母顺序平铺直叙,读者看完记得住缩写,但串不起来。
这次重写的结构是:先讲S和O——这是你想达到的代码状态;再讲L、I、D——这是到达状态的路径。有目标有地图,比背口诀有用。
Swift实现里的隐藏陷阱
用Swift写SOLID,有些坑是语言特性带来的。
比如协议(protocol)让"依赖倒置"变得太容易——你随手就能定义一个接口,但Swift的协议可以带默认实现(extension),这模糊了"抽象"和"实现"的边界。
![]()
我见过代码库里有200+个协议,其中一半只有一个实现类。这不是解耦,是考古现场——你得挖三层才能知道实际跑的是哪段逻辑。
里氏替换在Swift里更隐蔽。子类覆写父类方法时,编译器不检查前置后置条件。你写一个Bird类,fly()方法默认返回true;子类Penguin继承Bird,覆写fly()返回false。语法合法,语义爆炸。
Swift的struct和enum让"组合优于继承"更容易实践,但也带来新问题:值类型没有多态,你没法把struct塞进异构数组里统一处理。选struct还是class,本身就是设计决策。
这次系列怎么写
每篇会带一个真实场景——不是玩具示例,是我从现有代码库里摘的、脱敏后的片段。你可以直接复制到Playground跑,改几行看哪里会崩。
顺序按理解难度排:S(单一职责)→ I(接口隔离)→ D(依赖倒置)→ L(里氏替换)→ O(开闭原则)。O放最后,因为它最难——"对扩展开放"说起来简单,但扩展点选在哪,考验的是对业务变化方向的预判。
Uncle Bob今年70多了,还在推特上跟人吵TDD有没有用。他的原则写了20年,争议没停过。但争议本身说明问题还在——代码腐化的速度,永远快于我们清理它的速度。
我3年前的文章有人收藏,现在看是误人子弟。这次重写,3年后可能还是错的。但至少,错得更深一点了。
你最近一次推翻自己写的技术文章,是什么时候?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.