文章目录
- 设计规约的意义
- 设计规约
🙊前言:本文章为瑞_系列专栏之《Java开发手册》的设计规约篇。由于博主是从阿里的《Java开发手册》学习到Java的编程规约,所以本系列专栏主要以这本书进行讲解和拓展,有需要的小伙伴可以点击链接下载。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!
本系列第一篇链接:(一)编程规约
本系列第二篇链接:(二)异常日志
本系列第三篇链接:(三)单元测试
本系列第四篇链接:(四)安全规约
本系列第五篇链接:(五)MySQL数据库
本系列第六篇链接:(六)工程结构
本系列第七篇链接:(七)设计规约
设计规约的意义
在Java开发手册中,《设计规约》是一个非常重要的部分,它为开发者提供了一系列关于系统设计的指导原则和约定。这些规约旨在确保设计的一致性、可维护性和可扩展性,从而提高软件的质量和可靠性。
设计规约的意义主要如下:
- 确保设计的一致性:通过制定统一的设计规约,可以确保团队成员遵循相同的标准和约定,从而在整个项目中保持设计的一致性。这有助于减少因个人偏好或习惯导致的差异,降低维护成本。
- 提升代码质量:规约中明确规定了设计的各个方面,如类设计、接口设计、数据结构设计等。通过遵循这些规约,可以减少代码中的缺陷、提高代码的可读性和可维护性,并使代码更加健壮。
- 降低维护成本:设计规约为开发人员提供了一个清晰的指南,指导如何进行系统设计和实现。当系统变得复杂时,遵循规约可以使维护工作变得更加简单和高效。开发人员可以快速理解代码的结构和设计意图,降低维护成本。
- 促进团队合作:设计规约可以帮助团队成员更好地协作和沟通。当所有人遵循相同的规约时,可以更加顺利地交流和合作,避免因误解或不一致导致的开发冲突。
- 提高软件可扩展性:设计规约不仅关注现有功能的实现,还考虑未来的扩展性。通过遵循规约,开发人员在设计时能够预见未来的变化,并构建出灵活的架构,使软件更容易适应未来的需求变化。
- 降低培训成本:对于新加入的团队成员,遵循设计规约可以降低他们的学习成本。他们可以快速了解项目的代码结构和设计风格,更快地融入团队并开始工作。
- 促进最佳实践的推广:设计规约通常基于行业最佳实践和经验总结。通过推广这些规约,可以帮助团队吸收和采纳先进的软件开发理念和方法,从而提高整个团队的技术水平。
设计规约
-
【强制】存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。
说明:有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因历史数据迁移和系统平滑过渡而陡然增加,所以,存储方案和数据结构需要认真地进行设计和评审,生产环境提交执行后,需要进行 double check。
正例:评审内容包括存储介质选型、表结构设计能否满足技术方案、存取性能和存储空间能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等;数据结构变更(如在原有表中新增字段)也需要进行评审通过后上线。 -
【强制】在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 User Case 超过 5 个,使用用例图来表达更加清晰的结构化需求。
-
【强制】如果某个业务对象的状态超过 3 个,使用状态图来表达并且明确状态变化的各个触发条件。
说明:状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。
正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两种状态之间是不可能有直接转换关系的。 -
【强制】如果系统中某个功能的调用链路上的涉及对象超过 3 个,使用时序图来表达并且明确各调用环节的输入与输出。
说明:时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。 -
【强制】如果系统中模型类超过 5 个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系。
说明:类图像建筑领域的施工图,如果搭平房,可能不需要,但如果建造蚂蚁 Z 空间大楼,肯定需要详细的施工图。 -
【强制】如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示。
说明:活动图是流程图的扩展,增加了能够体现协作关系的对象泳道,支持表示并发等。 -
【推荐】系统架构设计时明确以下目标:
- 确定系统边界。确定系统在技术层面上的做与不做。
- 确定系统内模块之间的关系。确定模块之间的依赖关系及模块的宏观输入与输出。
- 确定指导后续设计与演化的原则。使后续的子系统或模块设计在一个既定的框架内和技术方向上继续演化。
- 确定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等。
瑞:
1️⃣确定系统边界:
- 理解:在开始设计一个系统时,首先需要明确这个系统的范围或边界。这涉及到确定哪些功能属于这个系统,哪些功能不属于这个系统。
- 意义:明确系统边界有助于团队成员对系统的整体功能有一个清晰的认识,防止在开发过程中过度设计和遗漏某些功能。
2️⃣确定系统内模块之间的关系:
- 理解:这涉及到识别系统中的各个模块,并定义它们之间的交互关系,如数据流、控制流等。
- 意义:了解模块之间的关系有助于更好地组织代码、提高可维护性和模块的复用性。
3️⃣确定指导后续设计与演化的原则:
- 理解:这意味着在架构设计阶段,需要为后续的子系统或模块设计制定一些标准和指导原则,以确保这些子系统或模块在设计、开发、演化过程中保持一致性。
- 意义:这样可以确保系统的整体连贯性和可扩展性,使得新加入的子系统或模块能够与现有系统更好地集成。
4️⃣确定非功能性需求:
- 理解:非功能性需求通常指的是那些与系统的核心功能无关,但对系统的性能、可靠性、安全性等方面有重要影响的特性。例如,系统的响应时间、可用性、安全性、可扩展性等。
- 意义:明确非功能性需求有助于确保在设计和开发过程中充分考虑这些因素,从而构建出更加健壮和可靠的软件系统。
-
【推荐】需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。
反例:用户在淘宝付款过程中,银行扣款成功,发送给用户扣款成功短信,但是支付宝入款时由于断网演练产生异常,淘宝订单页面依然显示未付款,导致用户投诉。 -
【推荐】类在设计与实现时要符合单一原则。
说明:单一原则最易理解却是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。
瑞:要分清楚 [ 数据对象类 ] 和 [ 业务对象类 ] 的职责区别。数据对象类只负责数据的存储和传递,数据对象类内部不应该存在任何的业务逻辑的处理,通常情况下只有 setter/getter/toString ,如DTO数据传输对象。而业务对象类才是负责处理业务逻辑,不应该去维护非业务的成员属性,如Service层接口的具体实现类
- 【推荐】谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现。
说明:不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方子类一定能够出现,比如,“把钱交出来”,钱的子类美元、欧元、人民币等都可以出现。
瑞:里氏代换原则可以参考《瑞_23种设计模式_概述(含代码)》中的设计模式的6大法则的里氏代换原则(Liskov Substitution Principle)。比如 “BaseController” 或者 “钱有子类:人民币、美元” 等情况才使用继承
- 【推荐】系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
说明:低层次模块依赖于高层次模块的抽象,方便系统间的解耦。
瑞:依赖倒置原则可以参考《瑞_23种设计模式_概述(含代码)》中的设计模式的6大法则的依赖倒转原则(Dependence Inversion Principle)。如Spring框架的
ApplicationContext
类,它是一个接口,定义了各种获取bean的方法,它位于Spring框架的高层次,提供了一种全局的访问机制。低层次的模块(例如控制器、服务、数据访问对象等)通常依赖于高层次的抽象(例如接口或抽象类),而不是直接依赖于具体的实现类。这样,当实现类发生变化时,低层次的模块不需要修改代码,因为它们依赖于抽象而不是具体的实现。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext applicationContext = new AbstractApplicationContext() {
@Override
public Object getBean(String name) throws Exception {
// 自定义实现
return null;
}
@Override
public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
// 自定义实现
return null;
}
}
- 【推荐】系统设计阶段,注意对扩展开放,对修改闭合。
说明:极端情况下,交付的代码是不可修改的,同一业务域内的需求变化,通过模块或类的扩展来实现。
瑞:可以参考《瑞_23种设计模式_概述(含代码)》中的设计模式的6大法则的开闭原则(Open Close Principle)
- 【推荐】系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在系统中不出现重复代码的情况。
说明:随着代码的重复次数不断增加,维护成本指数级上升。
瑞:即使某方法只使用两次,也应该抽出公共部分变为一个方法。如果使用IDEA开发工具,建议开启
duplicated code fragment
即重复代码片段检查选项,如下图所示:
-
【推荐】避免如下误解:敏捷开发 = 讲故事 + 编码 + 发布。
说明:敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。
反例:某团队为了业务快速发展,敏捷成了产品经理催进度的借口,系统中均是勉强能运行但像面条一样的代码,可维护性和可扩展性极差,一年之后,不得不进行大规模重构,得不偿失。 -
【参考】设计文档的作用是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。
说明:避免为了设计而设计,系统设计文档有助于后期的系统维护和重构,所以设计结果需要进行分类归档保存。 -
【参考】可扩展性的本质是找到系统的变化点,并隔离变化点。
说明:世间众多设计模式其实就是一种设计模式即隔离变化点的模式。
正例:极致扩展性的标志,就是需求的新增,不会在原有代码交付物上进行任何形式的修改。 -
【参考】设计的本质就是识别和表达系统难点。
说明:识别和表达完全是两回事,很多人错误地认为识别到系统难点在哪里,表达只是自然而然的事情,但是大家在设计评审中经常出现语焉不详,甚至是词不达意的情况。准确地表达系统难点需要具备如下能力: 表达规则和表达工具的熟练性。抽象思维和总结能力的局限性。基础知识体系的完备性。深入浅出的生动表达力。 -
【参考】代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。
说明:代码的深度调用,模块层面上的依赖关系网,业务场景逻辑,非功能性需求等问题是需要相应的文档来完整地呈现的。 -
【参考】在做无障碍产品设计时,需要考虑到:
- 所有可交互的控件元素必须能被 tab 键聚焦,并且焦点顺序需符合自然操作逻辑。
- 用于登陆校验和请求拦截的验证码均需提供图形验证以外的其它方式。
- 自定义的控件类型需明确交互方式。
正例:用户登陆场景中,输入框的按钮都需要考虑 tab 键聚焦,符合自然逻辑的操作顺序如下,“输入用户名,输入密码,输入验证码,点击登录”,其中验证码实现语音验证方式。如果有自定义标签实现的控件设置控件类型可使用 role 属性。
如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~