设计方案考量:准则与细则

没有完美的方案。所有方案都有利弊,在于适用场景以及权衡取舍。Think Deeper, Design Better.

设计沟通

问题发起者,建立文档说明痛点,采用方案能够得到的优点与可能的弊端,利弊权衡。


明确痛点

每一个需求/优化/重构,总能追溯到某个痛点。痛点主要有如下:

  • 功能诉求: 竞争对手有拼团功能,赚了好多钱好多粉丝,我也要有!【新功能】

  • 稳定性优化: 时不时出现xx报错,真是影响生意!同时多个大流量导出,系统波动有点大啊!【稳定性】

  • 性能提升:怎么这么慢啊 ! 这么多订单,得处理到什么时候?【响应速度与吞吐量】

  • 维护成本: 这方案得占双倍的存储资源,还有两个同步,理解起来多费劲!这个报错,没法看出问题在哪里,还得再打个日志看看。商家在等着修复问题,真急人!【资源/时间】

  • 弹性: 明年订单量要增加5倍,现在这个方案貌似扛不住啊!【容量扩展】

  • 数据: 这待发货订单数显示为2,怎么点进去没订单?【对比分析有困惑】

  • 体验: 要做完一个批量操作,要好多步骤,还容易出错,真耗费时间啊!【步骤繁琐,易错】

  • 及时: 更新一个内容,要马上生效,而不需要重新修改代码部署系统。【即时更新】

  • 安全: 啊啊,不小心把DB/重要文件目录数据删除了!【安全性提醒】

  • 扩展:实现一个需求,要改这么多代码?

  • 重构: 要实现新的业务需求,真没法改了!非得动大手术了!

痛点是否足够痛? 避免为了解决/优化问题而解决/优化问题,避免为了尝试新技术而引入新技术。一定是为了解决痛点。因为事情是做不完的,顾此则失彼,要对做的事情进行仔细规划。明确痛点,才能真正对症下药,药到病除。


设计准则

自然清晰

  • 设计自然、直观、清晰;

  • 没有拐弯抹角的地方。

  • 域的划分清晰。

  • 分层清晰。

  • 所见即所得与形式的一致性。


案例:

  • API 嵌套、继承导致 API 使用很迷晕。【反例】

  • 要导出零售总店的网店订单,需要传 head_shop_id, 并将 shop_id 置为空。Workaround 方案。【反例】

  • 将大量逻辑放在一个类里。代码分层不清晰。【反例】

  • 使用策略模式分离输出标准报表和自定义报表的报表字段列表。


容错处理

  • 减少了错误发生的可能性。

  • 错误发生时,更安全友好地处理。

  • 不会因为次要局部影响整体。

  • 减少了故障可能性,或可能故障级别。

  • 故障发生时,能够更快更安全地处理和恢复正常。


案例:

  • 对每个订单的处理进行异常捕获打日志,且对每个订单的每个字段的处理进行异常捕获打日志。避免某个订单的某个字段错误影响该订单的其他字段的导出,或者避免某个订单的数据错误,影响了其他订单的导出。【容错、隔离】

  • 重试机制。

  • 针对可能导致故障的点,进行重点防护。


扩展能力

  • 底层模型统一。

  • 核心简洁而稳定,外围可扩展。

  • 适当地分离关注点,更容易组合和组织关注点。

  • 组件化、配置化,通过增减插件来支持需求。


弹性扩展

  • 当业务量增长时,可以自然应对而无需额外改动。

  • 可以即时加机器,解决临时高并发吞吐量问题。

  • 可以即时减机器,去掉不必要的资源空闲。


维护成本

  • 减少了存储资源占用。

  • 减少了多处同步。

  • 能更快速地定位问题,大幅减少了排查和解决问题的时间(秒/分钟/小时/天)。


案例:

  • 更明显的错误原因指明和建议措施,利于快速定位问题和解决。


可复用

  • 以小见大,从一个需求点看到一类需求。

  • 建立可重复使用的方法、机制和流程,更容易地解决相似问题。


案例:

  • 建立一个可复用的 HBase 详情获取插件,来解决导出商品编码的问题;同时又能为其他字段导出需求所使用。


配置化

  • 解决一个需求时,建立相应的配置,当后续可能发生细节变更时,只需要修改配置即可即时生效。


案例:

  • 工程里的枚举的配置化,避免每次加一个都要修改代码发布系统。

  • 当要针对不同业务过滤可配置的字段列表时,使用配置来指定要过滤的字段列表。


一劳常逸

  • 建立良好的约定,解决一次,出问题只追溯源头。


案例:

  • 零售订单的导购员姓名取下单表的扩展字段XXX 。建立这个约定后,推进和完善各个场景下这个字段的落库。


依赖弱化

  • 减少了不必要的依赖(API,apollo,NSQ, KV 等),或者至少不引入新的依赖。

  • 对外部依赖进行降级或熔断。


案例:

  • 订单详情接口去除对粉丝接口的依赖。

  • 订单导出任务完成后,直接更新DB里的任务记录,不再依赖消息中间件。


最小复杂

  • 总是首先寻找简单、改动最小、比较彻底的方案。

  • 复杂度衡量: 少量顺序代码 < 一些条件分支代码 < 增加少量apollo配置 < 增加DB < 增加DB和缓存 < 增加一个模块。


举一反三

  • 发现一处,解决多处类似的问题,而不是发现一个解决一个。


案例:

  • 订单详情接口的商品图片URL字段未输出,可以借此梳理下还有哪些字段需要输出。因为每改一次的发布成本很大。


整合能力

  • 发现多个需求点的关联,综合考虑和解决,避免来一个解决一个,导致解决方案比较松散。


权衡取舍

  • 没有完美的方案,只有合适的权衡取舍。

  • 针对不同的场景,衡量收益和代价。

  • 要综合思考,避免线性思考;避免为了解决一个次要的问题引入更大的问题。

优先级:稳定性 > 清晰性 > 灵活性。


通常可以认为:

  • 如果能够达到建立新功能、避免故障、弹性扩展、大幅降低维护成本、可复用, 其收益将是非常高的,此时,增加少许依赖、复杂度,其实是可以接受的。

  • 为扩展留下实现空间,但可以暂时不实现。

  • 一劳常逸/举一反三,相比只是解决当前问题,更有价值;多往前走一步。

  • 自然清晰,是非常不容易达到的;但很值得为之一步步接近。


设计细则

设计细则是在准则的基础上,针对具体事项、场景而建议采用的方案。

  • 文档说明: 当设计变更涉及较大变动时,建立文档说明改动点及缘由。

  • API :继承不可超过两层,避免嵌套;避免将不相关的东西混杂在 API 参数中;避免将底层实现细节暴漏在API 参数与传参中。

  • 分层: 提炼出一系列关注点,分离到不同的语义层次,分离到多个类的单一职责中。

  • 组件化: 将工程里的代码与功能实现抽象为组件接口与实现。

  • 策略模式: 使用策略模式分离同一个接口的不同实现,并根据场景选择适宜的实现。

  • 插件流程: 如果流程是可变的,那么将单个流程节点变成可配置的插件,并进行编排。

  • 启动检查: 当应用启动时,加载所有必要组件,任一不满足时及时报错退出,避免错上加错。

  • 使用切面: 当多个功能要复用同一个前置或后置逻辑时,使用切面来实现这些前置或后置逻辑。

  • 受控线程池: 切忌在应用里动态创建单个线程或线程池;使用全局受控的线程池来执行任务。

  • 重载函数: 使用重载函数建立适合的工具类。

  • 无状态: 除非必要,不要在实例间共享状态;不要让请求的处理结果依赖于某个状态。

  • 快速失败: 当前置要件不满足时,快速失败胜于自以为的智能容错处理。

猜你喜欢

转载自www.cnblogs.com/lovesqcc/p/10188326.html