比较细微的收获
写在前面的前面 本博客的作图使用了:百度脑图 , ProcessOn (部分图例已经公开模板)。
之后将写一篇博文介绍UML统一建模语言的使用流程。
写在前面
这次的写在前面想写一些虚虚实实的东西——需求分析。可能有同学和我一样是在写完代码之后才开始撰写需求分析文档的,我是因为“生活所迫”,因为对工作量的估计不够精确,不知道自己能在多少时内完成代码的构建工作,所以选择优先写代码。但如果经验足够,时间充沛,先进行需求分析工作可能是更合理的选择。这里以 第7次作业 为例尝试分析先进行需求分析将带来什么好处。
从需求开始设计,而少让设计去适应需求。
系统运行的性能要求
时间精度
构建在一定精度内自洽并且准确的时间系统是实时系统设计的关键。
涉及到时间的系统在分析性能要求时需要考虑时间精度问题。既不能完全实时(一般的实时系统指的是处理频率在一定值以上的系统,完美的实时目前无法做到),也不能完全不顾时间问题(系统的正确性可能建立在时间序关系上)。另一方面,需求系统处理频率的快慢直接关系到需求系统性能的高低。
所以在开发前需要确定系统在什么时间精度范围内是保证正确的。
抽象而言,本系统主要是沟通两类对象的桥梁:一是需车用户,二是出租车。本系统时间精度的要求主要基于这两类对象状态更新的最小时间,这里对此进行分析(用请求代表用户)。
状态更新项 | 更新最短时间 |
---|---|
车辆 | 200ms |
请求 | 100ms |
车辆从一个顶点移动到四周与其相连的另一个顶点所需时间为200ms,而车辆状态保持的时间长度有:20s,1s等皆为200ms的倍数。询问 后得知,车辆在移动的200ms内的详细状态不在需求范围内。这里考虑以200ms作为车辆更新的最小时间单位。
请求的时间精度明确是100ms,进一步询问需求得知,这里100ms主要用于判定是否为同质指令。如果请求关闭时刻和车辆状态更新时间冲突时,即请求关闭时刻所有等待出租车都在移动的过程中时请求窗口关闭,则由于车辆在200ms内的移动详细信息无法确定,可以选择在所有车辆走完路程之后再进行派单,或者选择依照车辆从上一个节点出发前的状态信息进行判断。
综上所述,将选择200ms作为系统的时间精度要求,即在每一次触发状态更新后,有关当前状态的所有操作都应在200ms内完成。
任务规模
系统需要在同一个时间片内支持多大规模的任务也是系统性能需求的重要组成部分。
同样从两类对象出发对任务规模进行分析。
在一个时间片(200ms)里,系统应该支持如下任务:
任务名 |
---|
更新出租车状态 |
请求抢单列表更新 |
分配出租车 |
最短路规划 |
其中更新出租车状态的任务规模是固定的,任务复杂度取决于采用什么样的方式转移出租车的状态,请求抢单列表更新的规模弹性很大:从0
到6400×30
。分配出租车的规模同样具有较大弹性,一个时间片内为了完成分配任务,需要遍历出租车的次数的一个上界为6400×100
。同一个时间片内可能将产生100
个最短路径规划需求,在200ms内,没有进行必要的初始化,以Java的运行效率难以完成这些任务。故需要系统进行必要的初始化以提升运行时性能,或者使用运算缓存线程使系统性能缓慢爬升。
交互分析
作业的要求基本上是引导我们用UML建模的标准套路。
本系统是模拟系统,交互分析可以分为两个层次,一是模拟意义上的分析,二是实际使用时的交互分析。这里主要深入进行模拟意义上的交互分析。
交互关系
这里将使用UML统一建模语言对项目需求进行分析。
外部的交互关系分析将基于用例图展开。
这里将乘客和出租车作为出租车分配系统的外部交互对象,其交互关系如上用例图所示。其中的椭圆形状图形代表了用例,即使角色发生关系的事件。本分配系统主要需要解决的就是这三个事件以及用例所示的关系。
实际上出租车也是本系统的一部分,不属于外部角色,但为了更好地展示角色(乘客)与本系统的关系,这里将出租车也作为外部角色进行分析。
交互动作
这里的交互动作分析将出租车分配系统和出租车视为统一的系统(下表“系统”指代统一系统)。
编号 | 发起者 | 接收者 | 交互动作名 | 描述 |
---|---|---|---|---|
01 | 乘客 | 系统 | 发出乘车请求 | 包含出发位置,目的位置等信息 |
02 | 系统 | 乘客 | 前往出发地点 | 系统分配出租车前往触发位置接乘客 |
03 | 系统 | 乘客 | 前往目的地点 | 出租车将乘客运往目的地 |
-
数据
乘客发出请求,即有请求数据传入系统,从数据角度而言,这一阶段的交互动作是显式的,对于系统而言传入的数据是不可控的,事先不可知的。出租车到达出发地点接乘客和将乘客送到目的地后产生的数据是系统内部的(出租车向分配系统发出数据),与外部乘客的数据交互是隐式的,但是可以通过记录日志的方式,通过侧面的状态信息展示这一阶段的交互动作。 -
时间
乘客产生请求的时间是系统内部不可预测的,而之后前往出发地点和前往目的地点的交互动作发生的时间都是系统可以预测的。
系统内部对象识别与构造
- 传入数据类
FileInputHandler主要负责读取并初步解析保存在文件中的地图信息。ConsoleInputHandler主要负责处理来自控制台的数据,其中有对外方法run用以新建线程相应输入。
- 请求类
Request类对输入本系统的请求信息进行抽象,更进一步可以说是对客户的具体需求信息进行了抽象。CRRequest继承自Request主要负责对CR类请求的解析与有关CR请求特性的相关操作。
RequestList管理目前已经输入系统的请求。在ConsoleInputHandler和Scheduler中引用了同一个requestList对象,实现多线程之间的消息传递。
- 调度器类
Scheduler负责为每个Request更新抢单列表(goThroughRequest),并在每个Request关闭抢单窗口后根据一定规则构建出租车的选择顺序,并按顺序尝试让出租车抢单(trySendRequest)。
- 出租车类
Taxi类中实现了出租车的导航、行驶、接单、状态转移等底层功能。接口TaxiOperation对出租车的各向功能进行了抽象屏蔽,使得TaxiHandler出租车集群管理类可以基于抽象操作出租车。TaxiStatusPackage对出租车信息进行了抽象管理,实现了类似建立快照的功能。
并发分析
- 出租车
一般单机程序(非分布式程序)使用的线程不宜太多:实际的计算资源有限,硬件上不支持太多线程同时进行,线程的切换反而带来了额外的开销。
分析程序实现细节的算法复杂度后,我认为每个出租车都开一个线程不太合适,可以使用一个线程对所有出租车进行统一的管理,如此时间上来得及,也不会带来额外的开销。
2. GUI
GUI往往占用较多运算资源,应单开线程实现对GUI的更新运算。
3. 输入
对输入响应的速度是影响用户体验的关键因素,故也新开线程完成对输入的相应工作。
4. 调度器
调度器运行所需的信息来自出租车管理线程的更新操作,时序上应跟在出租车更新操作之后。但是出租车管理线程在更新状态后还要对GUI进行设置,这部分可以于调度器并行,所以这里再对调度器单开一个线程进行管理。
多线程架构设计
电梯
在多线程电梯作业中,我采用的架构是每部电梯一个线程,调度器一个线程,输入一个线程。线程架构设计主要依照于各个类的功能设计:调度器主要负责请求的分配操作,任务分配给电梯后,电梯主要负责按各自的时间进度执行计算工和打印工作,电梯间没有时间同步问题,也不需要信息交流,所以考虑为每一部电梯安排单独的线程运行。
输入单独一个线程,使输入时,不容易出现明显的卡顿现象。
IFTTT
为相同监控对象的同一类触发器安排了一个线程运行,触发器可以携带多个任务执行。这样的设计模式可能会导致不同监控对象集合有交集时会出现某一触发器漏爆的情况,经询问,这样的情况是被需求允许的
,在权衡后还是考虑使用这样的设计,以求在符合需求和精简设计之间找寻平衡点。
出租车
出租车的多线程架构设计如上章节末所述。
程序架构设计
电梯
这里需要指出,RequestList是请求队列类,其中以LinkedList为容器管理Request对象(因为使用了内置容器,故在生成类图时没有给出相应关系。)。
设计中主要需要解决的是时间系统问题。需求中明确给出了一定的时间精度,并要求在这一精度下,程序运行的结果能够自洽,并且没有要求程序使用线程状态作为判定电梯运行的依据。需特别指出,假如使用线程状态作为判定电梯运行状态的依据将与在一定时间精度下程序运行结果自洽的要求产生矛盾。
这里仅举一例:
(ER,#1,1);(ER,#1,2);(ER,#1,3);(ER,#2,7);(ER,#3,7),(FR,11,UP)
最后一条指令应该给#1电梯捎带。但是假如使用使用线程状态作为判定电梯运行状态的依据,没有经过特殊处理将概率性地给#2或#3电梯捎带。
IFTTT
程序主体框架是按照指导书中的推荐框架构建的。
出租车
为了适应之后的开发,将程序分为:taxies, scheduler, mapinfo, output, input这几块进行构建。需要特别指出的是为了方便后续开发,在这次的代码构建中采用了Interface和抽象类等方式,屏蔽了下层实现细节的同时只关注本层需要实现的业务逻辑。比如TaxiHandler主要负责周期性更新若干量出租车的位置和服务状态等信息,并不关心下层出租车的具体是如何更新的;另一方面TaxiHandler也需要向上层提供查询Taxi信息,为Taxi派单等接口,所有Taxi对象对其它线程而言是不可见的,需要通过TaxiHandler进行统一管理。这么做是在考虑了效率的同时兼顾时间驱动的精确性(保证两次状态更新开始时间的间隔是200ms)
度量分析
这三次作业在设计框架的过程中都考虑了metrics评测的主要标准,故量化评分方面没有出现太大问题。
Debug
有实时性的软件系统可以考虑从代码本身入手:阅读代码来查找有关需求上的逻辑错误,再构建相应的样例佐证判断;性能方面是否符合要求依然可以使用手动输入数据的方式进行测试,查看是否出现明显误差。
自己的bug
- 三次作业中出现的bug1
没有仔细阅读指导书的要求,排序出租车优先派单列表时,以出租车到出发地的距离信息作为第一关键字而非使用出租车信用为第一关键字。
他人的bug
在找到的bug中有一些问题我在代码构建的过程中也有遇到,这里暂时仅举两例。
- 架构问题
上文中叙述了有关使用线程状态作为判定电梯运行状态的依据可能出现的各种问题,这里不再赘述。 equals
是否应该重载?
调用equals
方法后在什么情况下应该返回true
,equals
的概念和“同质”是否应该设计成等价?在没有确定时着急构建代码可能比较容易出问题。
待续未完