OO第5~7次作业总结

作业5——多线程电梯

设计框架——UML协作时序图

(想看大图的话  crtl+滚轮  比较方便)

我为什么不用UML协作图(Communication Diagram),而是用UML时序图(Sequence Diagram)。

一方面,这样横平竖直的图更加直观美观。另一方面,使用Eclipse的plantUML插件能够比较方便地生成时序图。虽然从二者的区别上看:

时序图主要侧重于对象间消息传递在时间上的先后关系,
而协作图表达对象间的交互过程及对象间的关联关系。

协作图似乎能够更好地描述线程之间的协作关系,但是我发现有些软件是可以自动由时序图生成协作图的,这说明二者本质上描述的东西是一样的,只不过传达的角度有所侧重。相信聪明的你一定能够多角度看问题嘿嘿嘿~

【插播一则 eclipse的plantUML插件安装和使用方法】—— 摘自 http://www.importnew.com/24706.html

1. 安装:

PlantUML for Eclipse 插件主要用于在 Eclipse 中使用 PlantUML。在 Eclipse 的插件市场中安装,点击 Help --> Install new software --> add --> 起个帅气的name,location中输入网址:http://plantuml.sourceforge.net/updatesitejuno/  --> 其它安装操作

2. 点击“Window/Show View/Other..”,可以将 PlantUML 预览窗口面板显示出来。

然后我自己摸索了下就这样做了:在eclipse中右键工程--> New --> Untitled Text File (就是在工程文件夹下建了一个txt)。

在txt中按照一定的语法编写代码就能够在PlantUML 预览窗口中实时看到对应的时序图。像这样:

3. 所以编写代码的语法是什么呢?超简单的!

官方教程文档(中文):

http://translate.plantuml.com/zh/PlantUML_Language_Reference_Guide_ZH.pdf

【插播完毕,回归正题】

我的多线程电梯UML时序图:

线程总数:主线程Main + 输入处理InputHandler + 主调度器MainScheduler + 从调度器SubScheduler * 3 = 6个线程

(注:一共4个请求队列,一个大的,3个小的分别是归属于3部电梯的)

  • 主线程:启动其它线程。
  • 输入处理器:不断接受控制台输入,并进行分析,如果有效,则将请求放入请求队列。
  • 主调度器:不断扫描请求队列中的请求。判断是否是同质请求,是则删除,不是则:判断是否能被某部电梯捎带,是则选择一部可捎带电梯并将这个请求丢给它,不是则:找出运动量最小的电梯并将这个烫手请求丢给它。
  • 从调度器:不断检查自己所管辖的电梯的小队列的情况以及电梯的情况,来决定并通知电梯接下来的动作等。

设计类图

有了设计框架,就进一步细化成 设计类图:(我手动进行了删减和移位)

程序度量分析——Metrics

圈复杂度大在哪?

嵌套块深度大在哪?

不出所料...当初写这个作业的时候没有想那么多设计上的权衡之类的问题,只是一股脑地想做出来...所以该包装的、该划分功能的地方我都有所忽略。

然后打开代码,就发现以前怎么这么丑陋啊啊啊

  • 主调度器的轮廓是这样的:

  • 从调度器和输入处理器比它好一些。电梯中也有些小方法是 > 形状的...

线程安全的考虑

  • 虽然3个小请求队列 是3个电梯独有的,只会被从调度器使用,但是 大请求队列是 输入处理器线程  和 主调度器线程 共享的对象,因此我将请求队列类的所有  修改或者读取队列内容的方法  都设置成同步方法。(这么一想,是不是电梯的队列  应该另做一个不需要同步的队列类 来提高一丝丝效率呢?)
  • 3个电梯是  主调度器 和 从调度器 共享的对象,因此我将电梯类的所有  修改或者读取电梯信息 的方法  都设置成同步方法。
  • 当时比较年轻的我还是不大放心,又在4个调度器的run方法里面的某些代码块进行了同步化。

自评优缺点和反思设计原则

  • 优点:不是无效。总体框架还算清晰有逻辑。
  • 缺点:(1)细节的实现没有考虑简化,只是想做出来而已...所以搞得有些复杂,导致心情浮躁课下漏洞较多。(2)主调度器做的事情有点多,应该把判断同质请求放在到 输入处理器 或者 请求队列 中。(3)没有把功能进行 划分、精细化,导致几个很大块的方法的存在,可读性、可维护性不强。
  • 反思设计原则:(1)最大的问题:方法的职责不够单一、不够精细,也就是违反了SRP原则、责任均衡分配原则。(2)没有使用枚举类、全局静态变量等,不符合显式表达原则。(3)高层次的模块直接依赖于低层次的模块而非抽象,即没有考虑到DIP原则。

Bug分析

  • 课下调试:(1)没有使用枚举类 而且在判断字符串是否相同的时候 直接使用了==,我真的是=.=,而且是多处如此,而且其中有一个地方忘了改过来导致没有过公测的那个权重为5的测试点真是心痛心痛心痛。(2)在主请求 从无到有、从有到无、由一个变到另外一个即升级未完成的捎带请求为主请求  这几种变化情况上,急匆匆地就搞得比较复杂,出现了一些bug,然后我静下心来重新捋一遍就好了。
  • 公测:没有过公测的那个权重为5的测试点真是好心痛。
  • 互测:我方安全,对方完美。
  • 我栽大了。
  • 顺带感谢一波laj小朋友对我的信任!

作业6——IFTTT监控程序

设计框架——UML时序图

  • 为什么没有输入处理器:首先,你要知道,我的程序的输入顺序是酱的:
  1. 控制台接受IFTTT指令,直到输入RUN,表明指令输入结束。
  2. 根据输入的指令,启动所有的相应的触发器线程,让主线程睡眠一小段时间,以便于初始化所有触发器线程和快照。
  3. 创建和启动测试线程,进行一系列文件操作,来进行测试。
  4. 在启动测试线程之后,主程序将等待您在控制台输入END来结束所有还未结束的触发器线程。
  • 可见,输入处理器并不是一个单独的线程,所以我就懒了一把,直接把输入处理的方法都写在Main的类方法中了。
  • 上图中,为了简化,就用父类Monitor 来代替 4种监控器线程,因为每类监控器做的事情类似:不断获取新的快照,并与旧快照相比,来判断是否满足触发条件并执行相应动作,以及是否满足监控器的结束条件。每类监控器的属性以及做的事情中的相同的部分,我都提取到了父类Monitor中。
  • 一共有4类监控器线程,每个监控器线程以  被监控对象(某个目录或者某个文件)和 触发条件即监控器类型(4选1)作为  这个监控器线程区别于其他监控器线程的标志,也就是说 本程序的监控器线程最多有10*4个,而非10*4*3个。

触发条件和监控条件经过我的readme如下:(正如我上一篇博客所提到的策略,我进行了所有情况的遍历,以避免我遗漏了什么条件)

表1

表2

设计类图

再细化一下,就有了设计类图:(我手动进行了删减和移位)

程序度量分析——Metrics

圈复杂度又是哪里大?

嵌套块深度又是哪里大?

  • 嗯,还是老问题,没有把功能进行细分包装。
  • 而且我的四个类别的监控器的run方法比较相似:我是先写完一个最难的RenamedMonitor,然后把它的bug找得差不多了,才复制到其它3类监控器中,进行部分代码的修改,这样能够尽量避免  在发现一个bug之后,不会手忙脚乱地去改4个或者2个子监控器类的代码。
  • 【分享我这次的码代码经验】
  • 首先搭了一个差不多的类的框架,然后写了Main方法以及输入处理,并进行相应的debug
  • 然后写完一个最难的RenamedMonitor
  • 然后在RenamedMonitor中开一个主方法,在这个主方法中通过参数构造一个RenamedMonitor对象,然后调用他的  run( )方法  。
  • 开始单步调试,然后直接操作文件系统:进行创建、移动、重命名等操作,文件操作单步调试 交叉进行,同时你还可以查看你实时输出的detail或者summary内容。
  • RenamedMonitor监控器的bug找得差不多了。同理可以单独调试另外3个监控器类。
  • 最后单线程调得差不多了,就开启多个线程,这时,就不能像上述那样 在多线程的边缘试探 了....老老实实打印出来debug吧,这个时候出现的问题,基本就是线程安全和协作的问题了。前面的步骤已经为你de掉了许多小bug还是可取的。

线程安全的考虑

  • 拍快照的时候,会读取文件信息,要求此时文件的信息不能发生改变,因此将 线程安全的文件类SafeFile 的方法设置成了同步方法。
  • 另外,如果仅仅按照上一条所述进行同步,仍然可能出现这种情况:遍历某个文件夹下的文件进行拍照时,有可能拍照拍到一半的时候,CPU切换给了测试线程,测试线程对文件A进行了某些操作,当CPU切换回来继续拍照时,照相机可能已经对文件A拍过照了,这时就有会把 A文件被更改这个消息 延迟到 下一次拍快照的时候 发现。
  • 为了解决这个问题,我想加把大大的锁,进行文件操作或者拍快照之前,都必须获得这把大锁,这把全局锁是这样的:Object lock = new Object( )。
  • (但是现在想想,好像这个延迟的问题并不大,反正马上就要进行下一次拍照了QAQ....)

自评优缺点和反思设计原则

  • 优点:总体框架比较清晰有逻辑;自己想出来了个在多线程边缘试探的debug方法;把 四个类别的监控器 的共同方法 和属性 抽出来放到了父类Monitor里面,代码重用性还算好。
  • 缺点:(1)有些设计没有必要...(2)没有把功能进行 划分、精细化,可读性、可维护性不强。
  • 反思设计原则:(1)方法的职责不够单一、不够精细,违反了SRP原则、责任均衡分配原则。(2)没有考虑到DIP原则。

Bug分析

  • 课下调试:对象复制时,什么时候是要 对对象的引用进行复制,什么时候是要 clone一个新的对象出来,这个问题在我调试时反复折磨我,于是我赶紧搞清了这二者的区别,然后又从头缕了一遍。
  • 公测:安全。
  • 互测——对方:对方的设计思路是最多开120个监控器线程的那种,由于处理不当出现了 “若监控一个文件,且被触发后要执行的任务多于1个,若该文件满足了触发条件,那么有时候这些任务都会被执行,有时候这些任务只会被执行一部分”,这是设计框架的时候容易忽略的一个问题。(我当初没有想过这个问题,当初选择 最多开40个监控器线程的思路 的时候,纯粹是怕120个线程太多了....)
  • 互测——我方:我方抛出一个NoPointerException异常,似乎是因为我后来为了能实现 “若监控目录,且满足触发条件后的任务包括recover,那么每当监测到一个文件满足触发条件后,就把快照回退到这个文件改变前的状态” 这一目标,而导致我解决了一个bug后另一个bug又浮出水面....
  • 我服。

作业7——出租车的乘客呼叫与应答系统

设计框架——UML时序图

线程总数:主线程TaxiSys + 输入处理InputHandler + 调度器Scheduler + 图形化界面GUI + 出租车Car * 100 = 104个线程

  • 主线程:启动其它线程。
  • 输入处理器:不断接受控制台输入,并进行分析,如果有效且非同质,则将请求放入请求队列。
  • 调度器:不断扫描请求队列中的请求。若请求窗口时间未到,则在所有出租车中寻找具备抢单条件的车。若请求窗口时间到了,则从队列中删除该请求、从所有抢单的车中筛选出最终进行派单的车,然后跟这辆车说,你去响应这个请求吧。
  • 出租车线程:不断检查自己的状态等信息,根据自身信息来决定自己接下来该做什么。
  • GUI:没什么好说的。

设计类图

有了设计框架,就进一步细化成 设计类图:(我手动进行了删减,为了提取重点和优美)

再细化一些,我的程序包括这么些类:

  • 打黑色圆圈的是上述104个线程。
  • 出租车的属性比较多,而且有些是不需要被外人知道的,为了传递出租车的信息时更加简便,我就做了个出租车信息的类。(我为什么没有做RequestInfo,因为请求的信息比较简单...纯属个人想法,如果您觉得这样做不大对,欢迎留言讨论)
  • 为了抽象,我抽取出汽车的运动方法成CarMove接口。(或许以后滴滴还能有truck、motorbike...)
  • ErrorHandler你懂的。
  • FileLogger是输出日志的类,考虑到DIP原则和扩展性,我又把它抽取成Logger接口。
  • gui是课程组提供的,为了读入地图文件信息并转化为数组,我又做了MapInfo。
  • 做MyPoint类是为了扩展Point类的功能。
  • 请求和请求队列你也懂的。
  • 还有一些表示 状态类型、错误类型的 枚举类,就没有列出了。

程序度量分析——Metrics

与前两次相比有了较大进步。

圈复杂度哪里大?

嵌套块深度哪里大?

  • gui类我没办法了肝不动了....
  • InputHandler和MapInfo都是输入处理,我就没考虑拆分。我的输入处理比较精确,写了35行代码,看来要拆分一下。
  • Scheduler我拆分包装得挺好了呀,咋回事?找到Scheduler的run方法,我发现了这个东西 :)

线程安全的考虑

  • 请求队列是 输入处理器线程  和 调度器线程 共享的对象,所以我将请求队列的所有  修改或者读取队列内容的方法  都设置成同步方法。
  • 每个出租车对象是  这个出租车线程  和  调度器线程  共享的对象, 所以我将出租车的 修改或者读取 出租车状态等信息 的方法 都设置成同步方法。

自评优缺点和反思设计原则

  • 优点:结构和逻辑比较清晰,因此debug比较方便。
  • 缺点:感觉不是很工程化;在给某一个请求寻找能够抢单的出租车时,采用的是遍历100辆出租车的方法,这样似乎有点浪费资源消耗时间。或许可以如老师所说,【给出租车做几个队列,不同状态的车进不同的队列】,这样查找起来比较方便,又或者,【给出租车做几个队列,不同区域的车进不同的队列】?
  • 反思设计原则:这次作业前老师讲了设计原则,因此我这些设计原则实现得还算不错,不过都是自我感觉良好....还是要去看看真正的工程化代码[fighting]

Bug分析

  • 课下自我调试:(1)曾经在使用  wait(200) 还是 Thread.sleep(200)  这件事上犹豫了,最后我是这样考虑的:我的出租车类的方法基本都是同步方法,这辆出租车自己 和 调度器是 唯二的 可能拿到这辆出租车的锁 的人,出租车在运动过程中花费200ms的时候,应该把锁让出去给调度器,所以用wait(200)。当然如果你的线程同步设计和我不同,可能结论也不同。(2)为了能够实时输出日志信息,且为了避免所有请求实时输出到一个文件时发生的交错 不优美 的问题,我给不同的请求都分配了日志,但是课下debug时,居然出现了  请求1的部分日志信息写到了 请求2的日志文件中去  的魑魅魍魉现象,我百思不得其解,后来不知道为什么,又好了....(有遇到同样问题且知道幕后黑手的同学欢迎留言...)
  • 公测:通过
  • 互测:我方安全,对方的输出不符合指导书和issue的要求,而且请求多于3个就抛异常,而且还有一辆车同时匹配2个请求的情况。
  • 终于平安耶。

其它总结

设计策略的变化

  1. 心态上的变化:一开始接触多线程的时候,如上述所讲,我比较急匆匆、晕乎乎,后来掌握了一些套路,就能够冷静下来分析,特别是线程安全相关的问题。当然,冷静的同时还要有紧迫感,反正我隔三差五跟自己说:你要无效了无效了笑了...
  2. 由全局到细节地考虑。我一般是这样的,先考虑和讨论大框架:应该有多少线程、分别都做什么事情、有共享什么对象吗,这样的设计能够满足那些硬性的大的需求吗。然后再去考虑如何调整小部件去实现那些小的需求。然后感觉差不多了,我就把大概想一想 列一列  有什么类、大概有什么属性和方法、有共同点可以提取吗、有什么线程安全问题要解决。然后开始在纸上把  尽量考虑到各个设计和实现细节的伪代码  写出来。然后开始码代码,在码代码的过程中可能会发现自己伪代码的一些错误或者遗漏,这个时候就能补救;另外,每写完一个 可以测试的 适度规模的 模块的时候,我就先对这个模块进行debug。最后再对整个多线程程序以打印的方式进行debug。
  3. 尽量不要推倒重构(反正我是没有这个能力和勇气)。这就是我为什么采取上述2策略的原因。
  4. 码代码的时候就尽量考虑到功能的单一化精细化,不要相信 你有时间 能够在de完bug之后 把某些代码段提取出来成方法 进行功能的归一化。

发现bug的策略

我的上一篇博客的所述策略——

  1. 解剖指导书。我遍历了几遍指导书,而且自己用笔把一条条规定都简单列了出来,用红笔标记不同条列之间的关系以及易忽略的点。然后进行一些深入思考,比如指导书说到出租车可能的几个状态的时候,我就画了个状态机出来。深入了解指导书是前提。
  2. 给自己程序打过的补丁,也可能是别人容易漏掉的。
  3. 不同的设计方式会有不同的易错点,如果你抽到的代码的设计方式和你不同,可以找找其他使用这个设计方法的同学,问问他们课下de出来了什么bug。比如IFTTT作业中的“最多40个线程设计”和“最多120个线程设计”,这就属于两种不同的设计方式。
  4. 讨论区和微信群里大家的讨论也都一条条列出来。
  5. 与你亲爱的同学交换测试样例。(多线程的话,这条基本不管用了....)
  6. 以更加宏观、复杂、遍历的视角去看待问题,就更容易发现问题所在。比如我在IFTTT作业中,将不同文件操作是否满足4种触发器的触发条件或者结束条件 列了个表格分析。

的基础上,又总结出了一些策略:

  1. 由简单到复杂地进行自测。自我测试的时候好理解,从小补丁开始打起。
  2. 互测的时候,你就可以灵活些了。你可以一上来先炸它100个输入(比如100个出租车请求),看他是不是大佬,如果结果和预期不同,你就可以轻轻一笑(如果结果和预期相同,你还可以祈祷它有小bug),然后用 分离的 功能点的 测试样例 去测他的代码,然后一步步增加测试样例的复杂度。(好吧,我承认一上来炸它一个大数据主要是为了把握大局....)
  3. 灵活地构造测试样例。比如出租车作业中,我构造了这么一个好玩的样例:在 全世界的每个小区域里 发出乘车请求,目标北航,然后看着GUI上出租车大部分都到了北航的时候,乘客们发现北航人都开飞机去了,于是乘客们在北航附近 发出了许多乘车请求想要离开,去哪里就随意吧。这个时候,会出现许多辆出租车抢单的情况,然后你就可以根据  日志输出的抢单的车在请求窗口结束时的信息  来判断应该是哪一个辆车最终被分配到请求。(好吧,这个样例只是好玩而已,可能并不是很有效嘻嘻....)
  4. 我一般不会仔细地去看人家的代码,除非那人代码特别棒没有bug我就会开始欣赏。但我会大概结合被测程序的代码设计结构,来设计测试用例。因为不同的设计结构会有不同的容易遗漏的问题。

        再次感谢各位帮助过我的大佬么么哒~

也继续预祝大家 习得OO,策马归来~          

原文地址:https://www.cnblogs.com/jeffan/p/bingoit2.html

时间: 2024-11-11 05:12:04

OO第5~7次作业总结的相关文章

OO第二次博客作业(2018春)

写在前面 多线程 搞死人 第五次作业 类图: 这次作业的调度器(als_3_controller)继承了上一次的调度器(smart_controller),并且分解了之前的schduel方法,内容分布在run()和新建的类ELE_thread的isCarry()和run()里.在程序运行启动了类ELE_thread 的三个线程,分别代表三部电梯的控制系统,分别操作三部电梯. 度量分析: 问题都出在ELE_thread 的run()里,包括圈复杂度和嵌套深度.主要是因为把上一次的大部分代码都集中放

OO第二次博客作业

一.第五次作业--多线程电梯 (1)分析: 因为时间比较紧迫,所以采用了伪多线程的方式,即计算还是单线程,但是输出是三个多线程.不过最后被判无效了,GG. 现在分析一下觉得还是挺清晰的,电梯开三个线程,在分派任务的时候wait,notify一下就行了. 算法分析: 1.每个电梯有一个list队列,在新任务来的时候决定加入哪个list 2.有变动的list更新(用上一次的代码,从头算到尾,得到应该输出的真实时间) 3.三个线程死循环,遍历对应的list,如果有请求的应输出时间小于当前时间,输出,标

面向对象第二单元训练总结

一.前言 第二单元的三次作业是很有特点的三次作业.多线程电梯的设计思路和前两次电梯作业迥然不同,导致我花费了大量的时间去重构之前的代码,使其适应多线程电梯的作业要求:文件监视器是一个独立的作业,不像电梯和出租车那样是一个系列,因此写起来没什么包袱,感觉并不困难:出租车调度和多线程电梯写起来感觉比较相似,但出租车几乎没有算法上的难度,因此主要的工作都花费在了如何构建一个好的设计上面.这三次作业之间看起来没有什么关联,但却环环相扣,一步一步加深着我对多线程编程的理解. 我对这三次作业的总体难度评价为

oo第四次作业

很不幸由于一些原因,前两次的作业我都没有能够提交,虽然都尽力完成了,但没有经过公测和互测的洗礼,也不知道程序写的究竟怎么样,这里我只能简单说一下程序设计的思想了 第一次作业:多项式加减 这次作业中一共写了2个类,第一个类是poly类,用来记录每一个输入的多项式.其中有两个属性coe和index,前者记录系数,后者记录指数. 第二个类是polys,用于把输入拆分为单个的多项式,构造多项式的队列. 第二次作业:傻瓜电梯 按照要求设计了5个类: elevator:电梯类,用于构造一个电梯,并且描述和记

OO前三次作业总结

很庆幸我还活着-- 千言万语尽在一言中-- 好了话不多说直接进入正题,在此对前三次OO作业做一个简单的总结: 第一次作业:第一次接触面向对象-作为一个没有java编程基础的小白来说,面对这个本来比较简单的作业还是比较头疼的,首先不懂java语法,其次不理解面向对象的含义:一脸懵逼-- 好在经过两天的煎熬之后也算是勉强入门了,磕磕碰碰写完了第一次作业,由于初次第一次对于面向对象这个概念没有多少理解并且作业难度也不大,所以整个程序只有一个类,代码量110行:主要难点为输入是否合法的判断以及多项式算法

面向对象OO第5-7次作业总结

面向对象OO第5-7次作业总结 学习OO七周了,深切的感受到了这门课程的不友好.前三次作业能够算是勉强地通过了,但是从第五次作业开始就完全GG了.这三次作业,从多线程电梯开始,然后文件监控,然后到出租车,都没有写出来有效的作业.所以就不按照给定的基于度量来分析自己的程序结构了. 第五次作业 多线程电梯 这次作业是根据第三次作业改写而来的,自认为前两个电梯写的还不错,只是电梯捎带功能上少了几个判断的条件,以为这次能够加入多线程完美运行.但是实际写起来却并不如意,各个线程本身写出来了,但是在各个线程

OO作业总结第二弹

这个四月,多线程电梯,IFFF文件管理,出租车系列作业之--version1.(鬼知道我经历了什么......) 不用说,三个作业都不是什么省油的灯(白眼).那先从电梯系列作业终极版--多线程电梯说起吧. 因为之前的电梯程序我写的并不好,在之前的两个版本中我也没对代码做大篇幅的改动,所以各种设计缺陷累积起来,旧的代码怕是扛不住多线程的折腾了,所以我干脆重新架构写了一份全新的多电梯调度作业.大概的设计就是把请求类用继承的方法衍生出了电梯请求和楼层请求这两个子类,似乎比较方便判断和管理:电梯类里面是

2018-北航-面向对象567次OO作业分析与小结

设计策略及其变化 第五次作业-多线程电梯 在这次作业一开始的大部分时间,我一直想着怎样设计最为完美,完全使用BlockingQueue,导致交作业前发现设计并不能满足指导书的要求.最后仓皇之中加了一个新的类,既臃肿,又是轮询实现,导致出现了bug. 第六次作业-IFTTT 这次作业采用了不同照snapshot的方式实现,由于对指导书理解有些问题,导致出现了一个未考虑的情况.这次作业中线程安全的文件类的设计加深了我对线程安全以及Java文件操作的理解. 第七次作业-出租车调度 这次主要使用了锁来实

OO博客作业2:第5-7周作业总结

(1)从多线程的协同和同步控制方面,分析和总结自己三次作业来的设计策略及其变化. 第5次作业:多线程电梯 基本照搬了课件上"生产者-消费者"模型的设计策略,将InputHandler设计为生产者线程,将Scheduler设计为消费者线程,将RequestQueue设计为托盘.生产者与消费者的工作并发,提高效率.同时,每部电梯设计为一个线程,因为每部电梯的运行彼此不干扰.InputHandler, Scheduler由主线程创建,三部电梯由Scheduler负责创建,这样使得调度器可以获