其他
设计模块/函数/组件时用 SOLID 原则实现可维护…
在实现阶段设计模块、函数和组件,需要可维护、灵活架构时,用 SOLID 五原则管理职责划分与依赖方向。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
设计模块/函数/组件时用 SOLID 原通用
解决思路
- 应用 SOLID 设计原则,管理依赖与职责,而不是单纯增加代码。
- 单一职责原则(SRP):让一个模块只有一个变更原因。Elixir 中不要把创建用户、发送欢迎邮件、记录分析、更新缓存都放进 UserManager;拆成 User.create/1、UserNotifier.send_welcome_email/1、UserAnalytics.track_signup/1 等单一职责模块。TypeScript 中不要把 render、fetchData、formatDate、validateInput 都放进 UserComponent;拆成 UserProfile、useUserData、formatUserDate、校验函数。问自己:这个模块做的那一件事是什么?
- 开闭原则(OCP):让软件实体对扩展开放、对修改关闭。Elixir 中用 behaviour 定义 PaymentProvider 的 @callback process_payment(amount :: Money.t(), token :: String.t()),再让 StripeProvider、PayPalProvider 实现它;调用方 charge(provider_module, amount, token) 只依赖行为接口。TypeScript 中用 renderers 映射选择卡片渲染器,而不是在 renderItem 中堆 if item.type === 'gig' 和 else if item.type === 'shift';新增类型时只添加渲染器,不修改 renderItem。问自己:能否不改现有代码就添加新功能?
- 里氏替换原则(LSP):子类型必须能替换基类型,实现必须遵守契约。Elixir 中 PaymentCalculator.calculate_total(items) 要对空列表返回 0(Enum.sum(items)),不要因缺少空列表子句而抛错。TypeScript 中用 Bird 接口的 move(),让 FlyingBird 的 move() 调用 fly()、SwimmingBird 的 move() 调用 swim(),不要让 Penguin 继承 Bird 后让 fly() 抛错。问自己:能否在不破坏行为的情况下替换为父类型/接口?
- 接口隔离原则(ISP):客户端不应被迫依赖不使用的接口。Elixir 中不要设计包含 work()、take_break()、eat_lunch()、clock_in()、clock_out() 的胖接口;拆分成 Workable、Breakable、TimeTrackable,让 ContractUser 只实现 Workable.work()。TypeScript 中拆分成 Workable、TimeTrackable、BenefitsEligible,并用 type FullTimeUser = Workable & TimeTrackable & BenefitsEligible、type ContractUser = Workable & TimeTrackable、type TaskUser = Workable 组合。问自己:这个接口是否强迫实现定义未使用的方法?
- 依赖倒置原则(DIP):依赖抽象,不依赖具体实现。Elixir 中让 UserService.create_user(attrs, repo \\ YourApp.Repo) 支持注入任意 Repo 实现,或定义 UserService behaviour 后由 PostgresUserService 实现,并通过 config :yourapp, :user_service, PostgresUserService 选择实现。TypeScript 中让 UserManager 构造函数接收 PaymentAPI 接口(如 StripeAPI),而不是在内部 new StripeAPI()。问自己:能否不改依赖代码就替换实现?
- 写新代码前:识别单一职责。
- 写新代码前:设计扩展点(behaviours、接口)。
- 写新代码前:先定义抽象,再写实现。
- 写新代码前:保持接口最小且聚焦。
- 实现期间:确保每个模块只有一个变更原因(SRP)。
- 实现期间:新功能通过扩展实现,不修改现有代码(OCP)。
- 实现期间:所有实现遵守契约(LSP)。
- 实现期间:接口保持最小(ISP)。
- 实现期间:依赖通过注入或配置提供(DIP)。
- 代码审查期间:检查职责是否清晰分离。
- 代码审查期间:检查能否不改现有代码就添加功能。
- 代码审查期间:检查所有实现是否履行契约。
- 代码审查期间:检查接口是否聚焦且最小。
- 代码审查期间:检查依赖是否已抽象化。
- 修复 SRP 违规:GraphQL resolver 不要同时包含业务逻辑,改用 command handlers;组件不要同时获取数据和渲染,改用 hooks + 展示组件。
- 修复 OCP 违规:不要用长 if/else 或 case 区分类型,改用 behaviours/多态;不要硬编码 provider 逻辑,改用依赖注入。
- 修复 LSP 违规:不要在实现中抛基类型不会抛的异常;不要在不同实现之间改变返回类型。
- 修复 ISP 违规:不要用要求所有字段的胖 GraphQL 类型,改用 fragments;不要用单体组件 props,拆成聚焦接口。
- 修复 DIP 违规:不要直接调用外部服务,包装为 behaviours;不要硬编码 Repo 调用,注入 repository。
- 与其他技能集成:应用 boy-scout-rule 改进代码时应用 SOLID;用 test-driven-development 为每个职责写测试;用 elixir-code-quality-enforcer 的 Credo 强制部分 SOLID 原则;用 typescript-code-quality-enforcer 的 TypeScript 接口支持 ISP/DIP。
- 务实应用 SOLID:它管理依赖和职责,不是创建更多代码;好设计来自务实应用,而不是教条。
适用边界 · 注意事项
- 不要教条地应用 SOLID,避免为了原则而创建更多代码或过度抽象;原文强调务实,而非 dogmatic。