共计 6938 个字符,预计需要花费 18 分钟才能阅读完成。
一、系统分析与设计的逻辑性框架
在日常的工作中,「软件剖析」与「软件设计」这样的词眼常常听到,然而要真正了解「软件剖析」和「软件设计」的实质是比拟难的,它依赖极强的工作教训,又加上软件剖析与设计没有规范的程式化步骤,导致不同的人有本人不同的办法,也就造成了很多人认为软件剖析与设计是十分「空洞」,还不如写具体的代码切实,而大部分的人写的是业务型代码,被嘲弄写 CRUD 的代码没成就感。
软件剖析与设计如其它行业一样,具备很强的逻辑性,没有逻辑性撑持,很难做好事。比方写作,有「谋篇布局」、「起承转合」、「遣词造句」等这些「准则」和「办法」,没有洞悉到这些底层逻辑,一个高手写的文章和一个普通人写的文章,成果是天差地别。因而,首先要讲清楚的是软件剖析与设计的「逻辑性」到底是什么,下图是软件剖析与设计的逻辑全景图。
1.1 办法
1.1.1 分析阶段
软件剖析与设计并没有那么神秘,实质来讲还是为了解决事实的问题,和「医生看病」、「工人修车」、「厨师做菜」一样的,都须要办法作为领导,否则没有任何脉络,只能抓瞎。办法是具备普适性,只是不同的行业有各自的个性,具体落地上有差别。
既然是要解决问题,那么总得晓得问题是什么吧,就好比医生看病,做各种查看、化验,都是为了全面地理解疾病,所以第一步是须要定义好问题,然而当下很多人都疏忽了这一步,间接上来想我要用哪种中间件、哪个框架,连问题都没有定义好,间接想实现无疑是轻重倒置。
问题是现实与实现差距的矛盾,事实是不满足现实的诉求,因而,首先须要理解用户的诉求是什么,想解决怎么的问题,这也即为是需要。需要剖析最大的挑战是什么是真正想要的,就好比一个病人说了一大堆的症状,他所说的症状体现与书本上的形容有时是有出入的,定义出真正的需要至关重要,接下来就要思考通过怎么的办法去梳理分明用户需要。
用例是表白用户应用零碎实现某些指标文本化的情节形容,它强调的是用户指标、观点。咱们应该从用户指标的角度登程思考,也有的人称之为利益相关者的关注点,情理很简略,咱们做进去的软件是为了服务于用户的,不是用户想要的,就算用了多高科技的的技术也是失败的。
用例只是一个概括的形容,因而还须要细化,一个用例包含一个或多个场景,场景是参与者与零碎之间的流动和交互,比方用户下单,有下单胜利,有下单失败两个场景。因而到这里还只是分析阶段,剖析的目标是理解现状和指标,以及零碎因素组成,它不关怀如何实现做、如何实现。剖析不仅限于软件行业,其它的行业也是如此,只是在剖析过程的程式化步骤、办法不一样而已。
1.1.2 设计阶段
当咱们分明地晓得要做什么之后,接下来要思考如何去实现,实现的路径有很多种,如同一百个厨师做同样的菜,做进去的成果不一样。设计阶段有两点须要思考的:一是如何将性能细化实现;另一点是如何更好地实现。第一点不论用什么办法都能够实现,难的是第二点,更好地实现是须要遵循一些章法,也须要评判体系,要不然你怎么晓得好与不好呢,常见的定量评判指标有:老本、性能、可靠性、效率等,还有一类是定性的评判指标有:开放性、体验性等。
度量的指标绝对容易,就像医生看病,看疗效、看老本,软件设计也是一样,归根到底是「多快好省」。然而软件设计的章法就简单得多,它具备很强的艺术性,正所谓「文无第一,武无第二」,比武肯定能够比出个高下,谁打赢了就是胜利一方,然而比文就难了,不能说你写的就肯定比我写的好,只是不同人的爱好不一样而已,当然这里指的旗鼓相当地比照,不同档次的比照一眼还是能看得出来的。
在设计阶段最为要害的是定义出「要害的技术问题」,个别分类两类:一是站在用户视角的设计,它重点考量的是「便捷性」和「易了解」;另一个是站在零碎层面,重点考量的是「复用性」、「扩展性」和「稳定性」。贴近用户的设计,让用户用起码的了解就能应用它,用户毋庸感知底层简单的设计,外围是答复用户最奢侈的原始诉求。零碎层面的设计要灵便,多用组合正交设计晋升零碎的复用性和扩展性,在具体方案设计中要思考到稳定性因素,比方写日志会带来性能问题,这个在方案设计中就要思考到。洞察到关键技术问题,并非久而久之就能练就成,须要在工作中大量地实际,总结经验,放弃技术的敏感度才行,在第二节中通过理论的案例不便大家加深了解。
1.2 工具
有了办法,接下来要有工具来帮咱们更好地做事,与办法对应的,咱们软件设计的工具是 UML,接下来介绍 UML 中罕用的图。
1.2.1 流动图
软件从实质上是在模仿事实业务运行的过程,是由一个个交互流动组成的,因而,在分析阶段须要梳理出业务的流动是怎么的,通过图形的形式记录下来。
流动图要体现出「参与者」、「流动终点」、「流动要害门路」、「流动起点」,比方用户下单,有「浏览」、「加购」、「领取」等流动。通过流动图能够看出业务的生命周期是怎么的,可能抓住业务的要害流程。
1.2.2 用例图
用例图是强调用户的指标和观点,是文本化的情节形容,用例从实质上讲并不是图,它是文本,用图形是简化了表达形式,它外围有三点:「参与者」、「要做什么」、「后果是怎么的」。用例图是对流动图的细化,对其中的一个流动定义出要实现怎么的指标。场景又是对用例图的细化,你会发现,从指标到实现,一步一步地细化下来,细化是对扩充对意识的了解,不在意识范畴内,也就不会去做。
1.2.3 程序图
将场景通过程序图表达出来,它外围强调的是零碎应该提供怎么的能力,留神程序图与时序图的区别,程序图是人与零碎的交互,它想表白的是零碎应该提供怎么的能力满足用户的诉求,而时序图零碎外部实现,强调的是如何实现这种能力。
其实到程序图这一步,基本上零碎要提供的能力就分明了,当然,这里是晓得零碎要做什么,至于要怎么实现它并不关怀。以上我认为它是分析阶段,把用户想实现的内容分明地定义进去,接下来就是思考怎么实现。
1.2.4 时序图
时序图有两种作用:一是表白性能是如何实现的;另一个是看责任调配是否正当。第一点比拟好了解,一个性能实现是由多个不同的对象组合来实现,对象间有交互依赖。第二点是评判对象设计是否正当,如何两个对象频繁交互,是不是能够合并在一起,如何一对象中的操作过多,是不是能够拆解。
1.2.5 类图
类图的作用也有两种:一是表白属性和职责;另一个是层次结构。类中的属性和职责是一个统一体,属性体现的是认知能力,职责体现的是行为能力,领有怎么的意识,就会产生怎么的行为。类不是一个孤零零的个体,它与其它的类之间有依赖、协作关系,因而,类图中体现继承、依赖、泛化、蕴含等关系。
1.3 准则篇
软件设计准则车载斗量,简化下来就三点:「复用」、「变动」、「认知复杂度」,好的设计处处体现设计准则,把这些准则刻画到骨子里,而不是刻意体现,如同「没有规矩不成方圆」一样,重点是要了解为什么要这些准则,从实质上讲是为了软件可能「多快好省」地实现。
利润 = 支出 – 老本,从这个公式中,很显著咱们想要实现利润最大化,怎么办呢,有两个办法:一是支出变多,最好地形式是实现规模化;二是老本升高,不须要或者很少投入老本。从这两点中,引申出「复用」和「变动」两个准则,复用是不投入或者少投入实现性能,相比从头做是不是要节省成本呢,咱们的产品不可能变化无穷的,那么变动是在劫难逃的,如果能撑持灵便地扩大是不是也能节省成本呢。
1.3.1 复用
从下面的剖析看,「复用性」的重要水平显而易见,比起烟囱式开发,复用的老本要低得多,所以产生出了 xx 平台、xx 中台,它们的实质目标还是为了复用,缩小反复开发成本。实现复用的伎俩有很多,复用的水平也不一样,这个就要靠平时的积攒,就像医生积攒「药」和「药方」一样,这些都是咱们解决问题的「工具」。先要有复用的思维,否则只有工具也是无用的,不晓得要怎么用、在哪里用。
不同的场景,复用采纳的设计办法是不一样的,举几个例子不便大家了解。
- 齐全复用
最简略的复用是 100% 的复用,比方加法计算操作,它必定是 100% 复用的,只用传输不同的数字进去,就能够计算出后果。个别齐全复用的是工具型的能力,它与具体的业务语义无关。
- 配置化复用
这一类的复用水平靠近 100%,只用配置一些与业务相干的具体的参数即可,比方对账场景,配置两个不同的数据源表,再配置对账规定即可实现对账。这一类场景实用于业务比拟固定,流程是通用的,并且差别变动是可枚举的。
- 局部复用
然而,在实现世界中,没有太多像齐全复用的事件,如果变动还不能通过配置化来实现,能够应用「模板办法」或「策略模式」,将变动提早到子类中去实现,这种办法在大家日常工作很常见,也有的应用 SPI 扩大点实现。这一类复用场景是有明确的「主流程」,只有大量的变动随业务变动,变动也是可枚举的,那么就能够形象出扩大点。
还有一类变动是很难枚举的,不晓得会有怎么的变动,此时最好的办法是通过「事件」解耦,主流程实现之后,发一个事件音讯进去,谁关注就去实现该性能,实质上讲 Spring 中 Aware 机制也属于事件的解决办法。
- 齐全不能复用
还有一类齐全不能复用,但要形象出规范的接口,比方罕用的数据库操作,Connection、Statement 就是规范的接口,不同的数据库厂商实现具体的数据库操作。不要感觉这种没有意义,它从更高的层面定义了标准,使用者是面向形象应用,能够不关注具体的实现是怎么的。
1.3.2 变动
复用和变动是一起进去的,软件惟一不变的是变动,怎么撑持将来更好地扩大是咱们要思考的,如果一个性能千年不变,怎么简略就怎么实现,而如果有变动的话,那就须要好好地设计,用起码的老本去撑持将来的变动。比拟难的是要洞察出什么在变动,这个还真不是那么好想到的,须要有行业教训积攒,看多了、实际多了,会发现外面的一些门道。
举一个应答变动的例子,税务在计税时,不同的业务计税规定不一样,有的金本位要计税,有的不须要计税,有的非金本位要计税,有的不须要。如果放在一个大的扩大点中实现,那么这就是典型的面向过程的设计思维,咱们形象出了「计税表达式」这个实体来应答变动,「计税项」要不要计税、计税口径是怎么的,新业务接入通过「配置化」来解决。
1.3.3 意识复杂度
意识是分档次的,最高层越简略,最低层越简单,对于使用者来讲,他心愿看到的是简略的内容,如果太简单,很难上手,比方命令行式的操作系统和桌面式的操作系统,显著桌面式的操作系统更受公众的欢送,这也是微软在目前仍然在操作系统市场占用份额上还是大头的起因。软件分层的目标不仅是让关注点拆散,还有另外一个目标是升高意识复杂度,从简略到简单,这和咱们软件剖析一样的,从粗到细。
类的设计也是一样的,举一个例子,税务在开发票时,开票这个模型构造须要调用者感知吗,必定不须要,因为发票中有很多的畛域概念,如开票主体、发票行等,用户的目标就是开一张发票,他只用通知你他晓得的信息,不关怀你外部要怎么实现,基于这个思考,咱们形象出了「开票申请」这个实体进去,它实质是贴近业务场景的实体,所蕴含的信息也是无限的,极大地升高了认知复杂度。
二、系统分析与设计的 2 个案例
2.1 日志框架
2.1.1 日志框架剖析
打印日志在咱们日常工作简直是人人都会接触到,日志的核心作用是记录要害无效的信息,帮忙咱们疾速地排查、定位问题,否则没有日志信息两眼一抓瞎。依据咱们的教训,心愿日志中蕴含工夫、类、办法、代码行、要害日志信息,用了这些信息就能不便咱们排查问题。
依据下面的剖析,咱们很快能够画出日志的概念模型,如下图所示。从实质上讲,咱们是将日志信息存储到指定的中央,如存储文件中,输入到管制台上,另外还有日志存储的格局能够有多种,比方一般的格局,还有 XML、HTML 的格局等。
从概念模型上看,设计一个日志框架并不简单,但在设计阶段中,还须要开掘更多的信息,咱们输出的信息更多,设计时思考的因素也就越全,更能满足用户的诉求。
2.1.1 日志框架设计
2.1.1.1 贴近用户的设计
站在用户的视角,他关注的是信息以怎么的格局存储在哪里,因而,有两个概念用户是要关注的,一个是「存储目的地」,另一个是「存储款式」,这两个是能够依据用户的爱好配置的,将这两个概念形象下,「存储目的地」形象成「Appender」,「存储款式」形象成「LayoutPattern」。除了配置外,用户在应用时须要一个接口类,将其形象成「Logger」门面类,只用简略的调用日志打印接口即可。
2.1.1.1 零碎视角的设计
站在零碎视角上,用户在日志打印时,他关注的是日志信息,如工夫、类名、办法名等信息他不会显示去写,因而还须要形象一个概念进去表白日志信息的概念,形象成「LogRecord」,这样概念类图如下图所示。
依照这个设计,很快能够设计出一个繁难的日志框架,代码构造如下所示。
Appender 类如下所示,它定义的是一个模板办法,先调用 LayoutPattern 获取格式化的日志数据,而后再输入到指标存储上,Appender 和 LayoutPattern 是能够独立变动的,同时将写操作提早到子类中实现。
再细细想一下,日志自身是为了不便排查问题,但额定日志的存储是有性能开销的,这个在设计时就要着重思考了。如何缩小写日志带来的性能开销呢,从三个方面思考:
- 大文件变小文件:关上一个 1G 的文件和关上 1M 的文件必定是不一样的,简单问题的治理也是同样的思路,拆分成小问题解决,因而在写文件时能够依照「工夫」、「容量大小」切分成小的文件。
- 内存映射写文件:传统的 IO 写数据,操作系统须要从用户态切换到内核态,性能开销会很大,能够应用内存映射的形式写文件,晋升 IO 性能。
- 同步变异步写文件:同步写文件是须要等文件写好了后再往下执行业务代码,而异步就不一样,它将日志记录存储在一个队列中,开户另一个线程缓缓写到文件中,不阻塞业务逻辑执行。
通过下面的剖析,一个简略的日志框架随着对它的了解加深,设计方案也在变动,外围是要能看到要害的技术问题有哪些,能提供哪些增量价值或差异化的价值。
2.2 定时工作框架
2.2.1 定时工作框架剖析
定时工作在咱们平时的工作中也常常应用,如每天定时发送邮件、定时做数据查看等,实现定时工作的诉求也比较简单,就是在指定的工夫执行指定的工作,从这个角度看,业务模型并不简单,如下图所示。调度工作和调度工夫是两个独立的变动维度。为了让工作可能调度起来,还有一个「调度器」的概念并没有在业务模型上体现进去,将要执行的工作,以及调度工夫告知给调度器,调度器就能依据要求调度工作。
2.2.2 定时工作框架设计
2.2.2.1 贴近用户的设计
站在用户视角,他关怀的有三点:
- 工作要依照怎么的标准去编写,因而须要一个概念「Job」去表白,它定义了一个 execute() 接口。
- 调度工夫如何去表白,形象出「Trigger」这个概念,示意到时就触发工作执行。
- 工作如何提交,须要一个门面类「Scheduler」去表白,承受用户提交的工作。
2.2.2.2 零碎视角的设计
站在零碎的视角,很快能想到解决办法,用户提交工作后要保留至一个队列中「JobQueue」,「JobQueue」中存储的是「JobDetail」,「JobQueue」蕴含了「Job」和「Trigger」两局部信息,而后有一个调度线程「SchedulerThread」一直扫描「JobQueue」,判断当前任务是否要被执行,如果须要执行就调用「Job」的 execute() 办法,类的概念模型如下图所示。
依照这个设计,很快能够设计出一个繁难的任务调度框架,代码构造如下所示。
定时工作最要害的技术挑战是查找要调度执行的工作,很容易想到对「JobQueue」中的「JobDetail」排序,发现 Job 的 executeTime 快到了就执行,然而排序是耗费 CPU 资源的,不同的排序算法工夫复杂度也不一样。怎么升高排序算法的工夫复杂度呢,最简略的用最小堆排序算法,每次从堆顶获取工作执行,而每次增加工作,又波及到堆的调整,这个过程也是耗费 CPU 资源的。有没有更快的算法呢,有人提出了工夫轮的解决办法,相似钟一样,如一分钟是 60 格,相似将工作的工夫计算好,放到对应的格中,如果有多个雷同的工作,就有一个链表链起来,xxl-job 就用了工夫轮的办法,将将来周期性须要 调度执行的工作放在工夫轮中,工夫轮的数据结构是一个 Map。
这还是一个简略的任务调度框架,还有很多问题没有思考到,比方工作分片、分布式定时工作等,还是回到需要剖析上,咱们要做的性能边界是什么,指标是什么,再去设计对应的解决方案。
三、总结
在日常技术方案设计时,最为要害的是要能定义出要害的技术问题,有两类问题是咱们要着重思考的:一是贴近用户视角的便捷性设计,次要是对业务概念的形象,用户以最小的常识感知零碎;另一个是零碎的视角设计,除了实现性能外,还要定义出要害的技术问题以及度量的办法,如打印日志带来的零碎开销,怎么做到对系统产生最小的影响;定时任务调度的调度算法设计,选用不同的数据结构的成果是不一样的,一般的排序算法没有堆排序算法好,但堆排序同样波及到性能开销,这就让咱们一直想更好地计划去解决。
作者:不拔
点击立刻收费试用云产品 开启云上实际之旅!
原文链接
本文为阿里云原创内容,未经容许不得转载。