OO第二阶段纪实


$ 0 写在前面

往往是那些令人格外痛苦的经历,会带给人以最快的成长。转眼间,半个学期的时间过去了,时间匆匆,不管之前对这几次充满了怎样的畏惧,在身边朋友们的帮助和努力下,我也渐渐度过了一个个难关。回首走过的路,满满的收获和成就感。


$ 0-0 多线程 or 多进程

在这一学期的另一门核心专业课OS中,我们接触到了进程与线程的概念,我们在操作系统层面上对二者的运行机制有了初步的感性认识。在近几次的作业中,我们真正将多线程的编程思想应用到了所编写的工程项目开发中去,也以此为契机,亲身体验到了多线程编程的种种特性,也触发了诸多思考、积累了不少经验。

$ 0-1 多线程的不确定性

多线程的本质是并发,多道线程轮流占用CPU计算资源,故形式上连续的代码块中的每条语句之间均有可能被打断。这种打断并切换到执行其他线程语句的行为,就引入了多线程程序运行结果的不确定性,而我们往往期望对于同一组输入而言可以获得确定的运行结果,这种不确定性是我们所不愿看到的。

$ 0-2 线程的安全性

由以上段落所引发的讨论我们可以看出,多线程程序的不确定性是我们在编程过程中需要尽量依靠代码逻辑去避免的。若仅仅是在访问数据阶段被打断,则受到影响的仅是数据调用的过程;如是写数据阶段或进行逻辑判断的阶段被打断,则可能产生十分严重的后果,包括运行结果出错、进入死循环乃至程序crash等等。因此有必要采取一切可能的同步机制和锁控制,来确保线程的安全性。

$ 0-3 设计的重要性

设计是工程人员永恒的主题。在最近一次的课程上,老师用简明的语言介绍了有关工程开发过程中所需要遵循的设计原则。设计过程是一个工程所必不可少的核心步骤,合理的设计能够大大优化编码、降低测试调试难度同时也提高了代码的可读性。相反,若设计工作不够充分、“手先于大脑行动”急于编码,则可能事倍功半,更为严重的后果是最终需要整体推到重来。因而在后文的叙述中,我将记录在设计层面的心得体会。


$ 1 多线程电梯


$ 1-0 初探“多线程”

多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。 在一个程序中,这些独立运行的程序片段叫作“线程”,利用它编程的概念就叫作“多线程”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

$ 1-1 多线程经典模型的应用

在多线程电梯的实现过程中,我采用了经典的多线程“生产者-消费者”模型,输入端InputHandler为生产者,调度器Dispatcher为消费者,公共对象请求队列为模型中的“托盘”。生产者和消费者各自独立为一个线程,轮流获得公共对象锁的使用权。采用同步控制机制synchronized关键字保证同一时间只有一个线程能够访问共有对象。

$ 1-2 防御与攻击

在这一轮的互测阶段,由于我对生产者消费者模型的理解不够深入,导致在二者轮流访问共有对象的时候出现了不合理的等待现象。这一bug出现的主要原因在于设计层面未做到深思熟虑,调度器的调配方法出现了严重的不合理性。由于每次调度器从请求队列中扫描请求并进行分配的行为发生在输入完毕之后,因而请求队列中等待的请求可能无法得到及时的响应,具体的响应时机依赖于输入结束的时间。

对于该bug的修复策略我采用了取消等待的方式,使输入端和调度端并发地扫描请求队列。由此便避免了获取请求队列中的请求对于输入结束标志的依赖。

$ 1-3 代码度量分析 && 类图

                                                     

从上述分析结果来看,造成圈复杂度和嵌套块深度过大的主要原因出现在电梯类的运动状态检查方法上。为了更好的优化这一部分的代码质量,可以进一步提高代码的抽象层次,合并不同检查方法中相似的部分。

【类图】


$ 2 IFTTT


 

$ 2-0 线程安全实例

在这一次的作业中,我对线程安全有了更进一步的理解。

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。

由于本次作业突出了对文件的操作,因此java.io.File类得到了频繁的使用。但File类是线程不安全的。因此,基于线程安全性的设计考虑,我建立了SafeFile类,为File类中提供的各个方法加锁,保证每个方法在执行过程中的原子性,避免运行过程中的不确定性情况发生。

$ 2-1 化简复杂问题

由于指导书内容繁杂,对于设计角度理解要求较高,因此需要将具体的设计需求进行合理抽象,并将需求总结归类为以下几点。这一过程,就是在设计工作中,对复杂问题进行简化的步骤。

1.对于目标路径为目录的情况

(1)根据指导书的要求,即使目标路径为目录实际监控的也只是目录下的文件,目录不在监控范围内

(2)目录下文件如果发生变化处理方式与目标路径为文件的处理方式相同,可以把目标路径为目录的情况理解为目标为一组文件

2.对于目标文件消失和出现的情况

(1)根据指导书的要求,四个触发器都是在目标文件存在的情况下才会触发,所以一个文件的出现不会触发任何一个触发器

(2)对于文件消失(也找不到重命名或移动后的文件),并不在指导书所述触发器职责范围内,也不用触发

3.对于一个目标文件上有多个触发器的情况,假设一个文件上拥有全部四个触发器,

(1)各个触发器互相独立(对应指导书每一个触发器一个线程的说法)

(2)对文件内容的修改(写入新信息,不是删除),会触发size-changed和modified

(3)如果文件被重命名,会触发renamed,其他三个触发器都将失效(path-changed要求文件名不能修改)

(4)如果文件被移动,会触发path-changed,其他三个都会失效,(renamed要求文件名有变化并且文件还在原目录下)

(5)以上所说的失效是指不继续跟踪

(援引自gitlab Homework6 issue #32)

$ 2-2 防御与攻击

在这一轮的互测工作中,我的程序被检查出path-changed的目录监控失效,导致该分支的测试点全部未能通过。究其原因,在于path-changed触发器线程在编写的过程中,对于目录的监控需要在每一个时钟周期内,遍历扫描目录下的所有文件,而由于我的扫描方法命名不当,导致在递归扫描的过程中使用了错误的方法名,进而使监控失效。由此引发的思考在于设计的显式表示原则,倘若连自己都有可能混淆的命名,想来一定在很大程度上降低了代码的可读性。

$ 2-3 代码度量分析 && 类图

 

    

 

在本次的工程度量分析结果中看到,一项新的指标超出了标准值--参数个数。这一指标超标的原因在于对某一特定的方法或构造方法,我向其中传递了过多的参数(多达8个)。参数过多的弊端在于进行参数传递时,各个参数的相对位置极易出现错误,导致方法失效。

解决方案可以采用以下策略,根据所传递参数的特性进行合理归类,建立一个指定的类去管理待传递的数据。在设计上提升抽象层次。

【类图】


$ 3 出租车调度系统


 

$ 3-0 系统的架构与设计

由于出租车和电梯一样属于系列作业,因此总体的设计就应成为被重点考虑的要素。这一次的作业中也特意考量了12条重要的设计原则,但的确由于本人能力有限,加之是首次带着诸多设计原则要求进行设计,我只能是尽力体会其中的思想,并不能保证完全符合要求地实现了12条设计原则要求。

要求中所提及的设计原则如下:

1) Single Responsibility Principle

2) Open Close Principle

3) Liskov Substitution Principle

4) Interface Segregation Principle

5) Dependency Inversion Principle

6) 层次化抽象原则,按照问题域逻辑关系来识别类;

7) 责任均衡分配原则,避免出现God类和Idiot类;

8) 局部化原则,类之间不要冗余存储相同的数据,方法之间不能够出现控制耦合;

9) 重用原则(共性抽取原则),把不同类之间具有的共性数据或处理抽象成继承关系,避免冗余;

10) 显式表达原则,显式表达所有想要表达的数据或逻辑,不使用数组存储位置或者常量来隐含表示某个特定状态或数据;

11) 信任原则,一个方法被调用时,调用者需要检查和确保方法的基本要求能够被满足,获得调用结果后需要按照约定的多种情况分别进行处理;

12) 懂我原则,所有类、对象、变量、方法等的命名做到“顾名思义”。

(节选自【第7次作业指导书】)

此外,由于注意到系列作业的特点,我必须为后续扩展的需求预留出相关接口,也应在程序的可扩展性方面,花更多心思设计。

$ 3-1 算法优化

在本次工程任务中,由于对最短路径有所考量,因此不可避免的要对单源最短路径,图的BFS搜索算法有所设计。在作业文件提供的GUI中提供了部分的BFS方法供设计时调用。但是由于该算法的局限性和复杂度过高的特性,使得这一算法无法在项目中直接应用。因此,我对提供的BFS算法进行了优化,对连通图矩阵的存储进行统一的初始化,从而大大提升了程序的性能。

$ 3-2 防御与攻击

在这一轮的互测阶段,我共被发现了1个bug,此次的bug是由于我对于指导书和issue的理解偏差所导致的。这提醒我,在阅读到某条确定的设计需求时,切忌想当然,一定要和身边的同学进行交流、考证,确保理解正确的前提下,在着眼于设计,动手编码。

此外,由于我在代码中出现了少量使用数字记录状态信息的编码行为,因而被报告了1处设计缺陷,对此我已进行修复,进一步提高了代码的可读性。

$ 3-3 代码度量分析 && 类图

      

从度量分析结果来看,第一次的出租车项目代码实现较为理想,指标超标的范围大多为提供的GUI以及Main类中的方法自身的问题。

而我自身的编码问题主要体现在出租车运动最短路径算法的实现上,出租车接、送乘客的两个过程采用的是完全相同的算法,只是始终点有所差异,故对于我构造了两个几乎完全相同的方法的做法是有所欠妥的,故我对这一部分代码进行了修改,构造了抽象程度更高的方法用于实现相关功能。另外,输入类的check方法实现了过多的功能,将正则匹配,分离和提取信息等功能聚合到了一起,引入了过高的复杂度,这一点在后续的工程中我将会注意避免。

【类图】

 


$ 4 写在后面


 

多线程编程的这一段经历是令我难忘的,相关的知识也是日后工作中所不可或缺的。由于是初次接触多线程的相关概念,时间又十分有限和紧迫,故而其中仍有些许疑问困扰着我,相信我能在以后的几次代码作业中有更为深入的体会和理解。同时由于其知识内容高度抽象,难度很大,完全依靠个人努力是完全不够的。这门课给了我一个良好的启示,那就是合作与交流。每当遇到疑问,经过自己的一番探索后,仍有一些细节不够清晰,这时同身边的同学交流讨论一番,总会有意想不到的收获。在设计的过程中,经过一段时间的独立思考,我还喜欢同室友们一同进行“头脑风暴”,大家畅所欲言,分享自己的想法,互相借鉴,我总能从中借鉴到优化自己设计的创意。

我想几次作业所带给我的不仅仅是工程设计能力的提高,更多了一段与同学交流技术的经历,锻炼了自己的沟通能力,这些都会为我日后的工作提供大量帮助。

猜你喜欢

转载自www.cnblogs.com/chrischen98/p/8977065.html
今日推荐