srp 的本质是“一个变化原因对应一处修改”,而非机械拆分类;小型项目中应聚焦修改点收敛,通过提取常量、封装变化、协调式api等轻量方式落地,警惕隐性职责污染。

SRP 不是“一个类只能做一件事”
很多人一看到单一职责原则,就下意识把类拆得特别碎,比如 User 类里不能有保存逻辑,非得抽出个 UserRepository,哪怕项目总共就三个类、两百行代码。这反而增加认知负担,不是在用原则,是在套公式。
小型项目里,SRP 的真实意思是:**当某个变化原因出现时,只有一处代码需要改**。比如用户头像格式变了,你只改头像处理相关逻辑,而不是翻遍 User、UserService、ApiHandler 去找哪几行在解析图片。
- 判断依据不是“功能数量”,而是“变化频率和原因”——UI 层改了、数据库换字段、导出格式调整,这些都可能是不同变化原因
- 如果
User类同时负责数据结构定义、JSON 序列化规则、校验逻辑,那 JSON 库升级(如从 Jackson 换成 Gson)就可能牵连三处 - 小项目中,把校验逻辑直接写在
setAvatarUrl()里比单独建UserValidator更直接;但若校验规则要复用于 API 入参和后台任务,那就该抽出来
怎么识别“职责混杂”的信号
别等重构时才发现,写代码当下就能观察到几个典型征兆:
- 方法名里带“and”或“or”:比如
saveUserAndSendNotification(),说明它在响应两个变化原因(存储失败 or 推送失败) - 单元测试用例描述里反复出现不同主语:“when user is created…”, “when email format changes…”, “when SMS gateway fails…”——这是多个职责在抢同一个测试文件
- 加个日志、换种序列化方式、改个字段名,都要打开三个以上文件——说明职责边界被手动“粘合”了
-
User类里出现了HttpClient、FileOutputStream、new ObjectMapper()这类强外部依赖,大概率在干不止一件类型的事
小型项目里怎么落地 SRP 而不搞复杂
不做大手术,只做“切口最小的分离”。重点不是拆类,而是让修改点收敛。
柏顿企业网站管理系统(免费版)秉承了东莞柏顿软件的一惯原则(致力于打造简洁、实用、绿色的管理系统)而推出的一款适合广大中小型企业的网站管理系统。主要功能如下:1.基本设置:联系方式、关键字、版权信息等等;2.菜单管理:用户可以在线增加、删除、修改和隐藏前台的菜单栏目和菜单项3.新闻系统:支持二级分类,可分类查看新闻、修改新闻、批量推荐、删除新闻,可设置是否推荐、新闻点击等4.产品系统: 产品类别新
立即学习“Java免费学习笔记(深入)”;
- 把硬编码的格式规则提成常量或配置项:
MAX_AVATAR_SIZE = 2 * 1024 * 1024,而不是散落在 if 判断里 - 用私有方法封装变化点:比如所有时间格式化统一走
formatTimestamp(long),而不是各处 newSimpleDateFormat - 对外暴露的 public 方法只做协调,具体动作交给参数或策略:比如
processUser(User user, UserProcessor processor),而不是在方法内部 new 各种工具类 - 避免在实体类里写业务逻辑:如果
User.isActive()依赖当前日期、配置开关、第三方状态,那就别放进去——改成UserStatusChecker.isActive(User)
容易被忽略的“隐性职责”
最常踩坑的是把“可测试性”“可调试性”当成理所当然,结果让职责悄悄膨胀。
- 为了方便单元测试,在
User里加了setCreatedAtForTest()方法——这其实引入了“测试支持”这个新职责 - 日志里打印了完整对象(
log.info("user: {}", user)),而User.toString()里拼接了数据库连接信息或敏感字段——序列化行为污染了领域模型 - 使用 Lombok 的
@Data时没注意它自动生成了equals()/hashCode(),而这两个方法实际依赖了哪些字段,决定了“什么算同一个用户”——这其实是领域一致性职责,不该由工具全权决定
小型项目节奏快,最容易在“先跑起来再说”的瞬间,把临时方案变成隐性契约。SRP 真正难的不是拆分,是每次加一行代码前,问一句:这一行,到底是因为哪个原因才可能被改?









