不知不觉又作了三次做业,容我在本文胡言乱语几句2333。javascript
第五次做业是前面的电梯做业的多线程版本,难度也有了一些提高。(点击就送指导书)php
程序的类图结构以下:
css
程序的逻辑时序图结构以下:
html
能够看出,此次的程序依然存在部分类或方法代码较为集中的状况,这样的状况在类Lift
、LiftController
,甚至笔者本身的第三方库DebugLogger
中较为明显。甚至在Lift
和LiftController
类中能够发现其实业务逻辑已经很是的密集。前端
不出意料,我方公测不存在问题。java
此次对方的公测也不存在问题,能够说是赏心悦目了。git
不出意料,我方互测不存在问题(然而出了点别的情况23333,为了保证笔者博客的干净,笔者将在文末讲述,此处不作讨论)github
此次,笔者给对方找出了两个bug。正则表达式
这两个bug分别是:算法
Invalid格式错误 这个,其实就是字面意思,这位同窗的程序Invalid请求输出信息的时候格式不正确(准确的说,是不符合指导书需求)。
ER
这样的请求进入调度器以后将直接转进对应电梯的子消息队列。FR
这样的请求将被暂存在调度器的消息队列,等能够接受这一请求的电梯出现后,将其发送至该电梯的请求队列中。因而,在通常的架构中,在请求队列里,通常都会有这样的一个轮询逻辑:不断轮询各个电梯的状态,等能够合法接受请求的电梯出现后,将其进行分配。然而就是在这个地方,若是,在轮询的一半时,某个电梯的状态忽然改变了,该怎么办。
假设,有这样的三个请求:
某电梯即将到达一层,目前还在运行中。其余电梯均正忙。
因而,假设这个时候开始了一个轮询:
然而,最不巧的是,偏在这个时候,这个电梯已经到达了一层且处于空闲状态,并且请求3还没判断,因而就会出现这样的尴尬局面:
这还不算完,因为该电梯当前在一层,因而:
灾难性的后果发生了,这三个原本多半能够有效负载均衡的请求,由于一个线程同步问题,就这么被一波带走了。
事实上,这个bug在笔者本身进行测试的时候,就已经发现。笔者思考后,以为有两种思路解决这一问题:
此次做业能够说是笔者在多线程上一次工程化的尝试。
笔者以前主要写的是C++
(用于算法竞赛)、C#
(GUI桌面应用编程)、Python
(用于各类脚本)、Ruby
(用于同袍和Questionor后端的维护)、php
(偶尔也会用到)以及前端的html
+css
+javascript
。虽然之前接触过多线程编程,不过也大都用于脚本编程(实际上,多线程的这种特性在网络请求等待的时候能够极大提升脚本效率),并且也大都是简单的并发+阻塞。在此次做业中,笔者真正在强类型OOP语言中进行系统化工程化的多线程编程仍是头一次。
此次笔者认真研究了线程相关的锁(lock)、监视器(monitor)等机制,而且仔细思考了在这样的一个工程中如何经过这些机制来避免由于同步问题致使的错误,且兼顾并发效率。能够说收获不小。
此外,笔者的程序结构依然存在高内聚的问题,再加上这是第一次设计真正的多线程工程代码,太多的时间花在了如何让程序没有bug上,因而代码风格仍是较差。
第六次做业叫作IFTTT,大体意思就是基于IF
... THEN
... 逻辑的文件系统监视器(点击就送指导书)
此次代码的类图结构以下:
此次的UML时序图结构以下:
此次的代码质量分析报告:
能够看见,整体的代码质量有较大的改观,不过仍是存在少数类的行数过大。
不出意料,公测没有出现错误。
不过这里有一个有趣的小插曲,笔者一开始公测被报了一个bug,理由是监视器没有作出响应。笔者打开了这位同窗提供的测试输入,用的是D:\
这样的根目录,因而笔者想起来指导书上貌似规定过不要用规模过大的路径进行测试,并对于这一状况向这位测试人员进行了解释,因而呢,改为了经过。(至于文件系统监视器相关的问题,笔者将在这一节的总结中稍微讲些本身的见解)
对方的程序,在公测环节彷佛出现了不少的bug。并且彷佛彻底不具有对于整个目录树的监视反应能力。(也许是功能就没设计全?)
不出意料,我方在互测环节没有出现任何bug。
此次互测中,对方被笔者查出了两个bug:
此次做业是笔者所写的第二次多线程工程代码,从代码分析数据来看,总体代码风格有了较大的改观,再也不有很明显高度集中的类设计,主要方法的代码密集程度也有所降低。
然而,笔者本身内心清楚,不少地方的代码仍旧不够成熟。同时,笔者已经开始探索着开发一套能够提供各种快速搭建和管理功能的java工程代码框架,而且已经从第七次做业开始使用这样的框架。
以及,此次貌似是指导书被吐槽最严重的一次。此次的issue也能够说是史无前例的庞杂,其中笔者以为一部分缘由是指导书没有去匹配大部分人对文件系统以及IFTTT的认知水平。
说到文件系统监视器,通常来讲有两种主要的解决方案:
显然,此次做业通常采用的策略是前者。这是一种很廉价且在数据规模不大的状况下很靠谱的策略。
可是,各位应该也已经发现这一方法的一些明显弱点:
关于第三点,笔者举例说明下。
例如,在两次快照拍摄之间,a.txt
重命名为a1.txt
,b.txt
目录更改到了dir\a.txt
,这么一来,快照增量检测到的应该是这样的四条信息:
a.txt
b.txt
a1.txt
dir\a.txt
假如咱们的a.txt
和b.txt
文件大小和修改时间再彻底一致的话(本次做业中判断文件等价的惟二依据是文件大小和修改时间,并没采用各种文件指纹算法),问题就来了——a.txt
和b.txt
到底去了哪呢?
a.txt
和b.txt
等价,因此解释为a.txt
--> dir\a.txt
, b.txt
--> a1.txt
,这样是说得通的。a.txt
和b.txt
等价,因此解释为a.txt
--> a1.txt
,b.txt
消失,dir\a.txt
出现,其实也说得通。实际上,快照+增量机制所带来的一个无解的难题就是增量事件变得再也不惟一可断定。
然而,这还不算完。咱们此次采用的是多线程机制检测文件系统变化。因而呢,这样又不得不引入了线程同步问题。由于,按照原来课程组的要求,彷佛还得保证在出现同质文件的状况下事件不能够发生冲突(例如,对于上述的例子,不能够同时检测到a.txt
--> a1.txt
和b.txt
--> a1.txt
)。实际上这样的要求自己彻底合理,这件事在单线程内的断定也很好办,HashMap判重一下便可。然而要是这样的断定分散在多个线程内呢?因而又有了一连串的问题:
因而这个问题又成了一个无底洞(因而,后来课程组决定不对这种极端状况进行测试)。
根据我的了解,在实际应用中,这样的问题经常是基于另一种思路——根据文件系统底层事件来检测文件系统变化。
Java
和C#
中均有现成的FileSystemWatcher
类可供直接使用。此次做业中笔者所检查的程序存在程序不能正常结束的状况。笔者打开了这份程序进行了查看,这份程序在顶层,打开了各个线程后,就再也不对各个线程进行控制。
笔者以为,多线程程序设计的一个基本原则是——任何线程在任什么时候候均不该该处于脱离控制的状态。不管是消息队列,仍是各个业务逻辑线程,甚至是GUI,在任何阶段都应该在上层线程的控制之下。即上层须要结束线程的时候能够随时正常下达指令,且下达指令后须要用join等命令进行阻塞等待,直到各个线程安全关闭,再结束程序。
第七次做业是出租车系统模拟。不得不说,事情终于开始变得有趣了(点击就送指导书)
本次做业的类图结构:
本次做业的UML时序图:
本次做业的代码质量分析报告:
能够看见,排除GUI模块以外(GUI模块并不是出自笔者之手),代码局部复杂度已经获得了必定程度上的控制(三个红色的那个函数点开看了下,是因为代码重复性较高致使的)
不出意料,公测我方未被测出bug。
对方的公测存在一个bug,即没有对于起点终点为同一个节点的状况进行断定。这样的bug添加一处断定便可。
最终,不出意料,我方未被测出bug。
不过中途也仍是出现了一些有趣的小插曲。这位测试者试了下在缺乏map.txt
的状况下运行程序,而后看到笔者的程序输出了红色字,因而认为笔者的程序crash
了。
然而实际上,笔者的程序外部包裹了try catch,只是在catch外面使用了printStackTrace
。而且程序的实际返回值也是0
,也就是说是正常且平稳的结束的。因而笔者摆事实讲道理,进行了申诉以后,对方撤回了这个bug。
然而程序也的确显示了红色字,这又是为啥呢?笔者经过研究java源码,找到了问题所在。
咱们知道,通常高级语言程序通常会带三种自带的Stream:
Java
中的System.in
Java
中的System.out
Java
中的System.err
接下来咱们来看看一切异常类的祖先类——Throwable
类的部分源码:
public void printStackTrace() { printStackTrace(System.err); } /** * Prints this throwable and its backtrace to the specified print stream. * * @param s <code>PrintStream</code> to use for output */ public void printStackTrace(PrintStream s) { synchronized (s) { s.println(this); StackTraceElement[] trace = getOurStackTrace(); for (int i=0; i < trace.length; i++) s.println("\tat " + trace[i]); Throwable ourCause = getCause(); if (ourCause != null) ourCause.printStackTraceAsCause(s, trace); } }
该源码片断截取自Throwable
类,能够看到,默认不带参数的printStackTrace
类,实际上是在调用System.err
进行输出。因此难怪输出的会是红色字,由于的确输出到了异常流内。
说到这里问题就解决了,之后若是须要避免相似的误解,调用printStackTrace(System.out)
而非printStackTrace()
便可。
这位同窗的代码整体而言写的仍是挺不错的,不过在测试的过程当中发现有一个很坑爹的设定。
这份程序只有在按照指定的方式结束程序后,才会有detail.txt
细节信息输出(也就是说用其余的方式,即使平稳结束程序尚未文件输出)。
这样一来,虽然实际上输出了,但也等于彻底不具有实时交互的特性。虽然指导书上并无明令禁止,但实际上已经违背了这个设计的初衷。
因而笔者向多名助教求证过以后,报了一个imcomplete
。
在此次做业中,笔者在开始动工以前,准备了一个简单的程序框架模板。使得程序搭建效率有了略微的提升(关于程序模板,笔者将在下文继续讲述)。
同时,笔者自我感受,从此次开始,笔者的多线程程序设计框架开始变得日趋成熟。
笔者从这三次做业开始,真正接触了系统化工程化的多线程OOP程序设计,开始从零开始一步步思考,如何充分利用多线程的并发机制,协同各个进程,同时充分兼顾多线程并发效率。
从中,笔者仍是看到了自身的一些不足:
笔者将会从接下来进一步的实践当中,进一步改善代码风格,设计更完善更符合规范,人机性能更好的程序。
据笔者观察,貌似不少的同窗至今仍热衷于使用静态数组来进行数据的存储。
起初,笔者十分不解,在Java
这样的OOP语言中,相关的数据结构封装类可谓至关完备,为啥还要使用数组呢?
通过一些观察,不少人仍离不开静态数组的缘由大抵以下:
0
、1
、2
之类的数字表示(然而,静态数组实际上有很致命的缺陷:
grep
命令将对方程序里头的静态数组一口气揪出来,只要能找到,基本上很快就能开开心心的拿下此次的一血。这招一抓一个准,屡试不爽,并且基本是抓住的都是crash,通常人我不告诉他23333静态数组这样的东西只在极少数特定场合下稍微方便些,然而带来的倒是不少性能和工程性上的不可控,可谓得不偿失。
建议使用Java
内置的数据结构,诸如List
、Vector
、ArrayList
类,这些类均进行过有效的代码封装和性能优化,各方面性能均有保证,且不会很容易的出现错误。
从第七次做业开始,引入了一些代码规范相关的考察。我的以为,其实这是一件很好的事情,毕竟真正的工程永远离不开维护,也很难离开teamwork。
然而,据笔者观察,彷佛不少人对这件事颇为反感与不解,诸如如下的论调:
是的,上述的想法能够说很是广泛,笔者在第七次做业正式发布后的客服群里,基本天天都能看到这样的论调。感受至关多的人以为这个要求很不合理。
首先,关于代码规范的重要性,笔者在上次博客做业已经有说过,不想再重复唠叨一遍(或者说,唠叨了估计也没人爱听。。。)。
不过这样估计说服不了任何人,那容笔者来举几个亲自遇到的案例吧。
这个图,来自于第六次做业笔者测试的这位同窗的summary.txt
恩,没错,这就是summary.txt
。为了防止各位看了一脸懵逼,我还能够告诉大家,冒号前的0
、1
、2
、3
表示的是四种不一样的事件。
那么,请你如今告诉我,这个summary.txt
是在表达什么?
是的,没错,看不懂的不止你一个,由于笔者当时看到这个的时候也仍是一脸懵逼(即使猜到了前面的数字表示的是各个事件类型)。
因而,笔者只好开始研究他的源代码。而后很惊喜的发现,这位老哥的全部代码全都写在了一个文件里头,并且右边滚动条上还密密麻麻的都是各类warning。
终于,功夫不负有心人,笔者终于找到了一丝线索:
恩,就是这里
public void addSummary(String trigger) { lock.lock(); summary[trigger.equals("renamed") ? 0 : trigger.equals("Modified") ? 1 : trigger.equals("path-changed") ? 2 : 3]++; PrintWriter output; try { output = new PrintWriter("summary.txt"); for (int i = 0; i < 4; i++) output.println(i + ":" + summary[i]); output.close(); } catch (FileNotFoundException e) { System.out.println("Fail to output to summary.txt!"); } lock.unlock(); }
就是这句
summary[trigger.equals("renamed") ? 0 : trigger.equals("Modified") ? 1 : trigger.equals("path-changed") ? 2 : 3]++;
咱们来研究下这个超长的三元表达式在表达什么(emmm,笔者做为多年的老码农,表示愣是一眼没看懂):
renamed
,返回0
,不然继续Modified
,返回1
,不然继续path-changed
,返回2
,不然继续3
到这里,笔者费尽千辛万苦,终于看明白这个文件了。
想到这里,假如,我不是一个测试人员,而是这位老哥的teammate,想要一块儿开发一个项目。
若是,须要对接的时候要是遇到了这样的状况,得费多大的劲才能搞清楚?
试想一想,若是你每次开发项目,都要花一堆的时间在这种无谓的事情上,你以为值得么?有何效率可言?
若是,输出的不是
0:1 1:0 2:0 3:0
而是
renamed: 1 Modified: 0 path-changed: 0 size-changed: 0
若是,这个程序可以知足咱们所规定的:
这么一来能够节省多少的时间。
在第七次做业中,笔者分配到的测试程序,依然存在和上面相似的状况。
当笔者一次性输入多个请求的时候,等待3秒后,出现了一片的报错:
笔者当时瞬间就懵了,到底哪些指令执行了,哪些指令分配失败了?
而后对detail.txt
仔细研究了好半天后,才终于确认程序运行的是对的。
其实吧,在这种时候顺带输出下错误的详细状况(甚至不用太详细,输出一下哪条指令出错也是好的),对于编程者而言真的就是几秒钟的事情。
然而若是不加,不只给别人会带来很大的困扰,大家本身debug的时候,也会处于彻底摸不着头脑的状态——由于不管哪里错了,输出的都同样。
说了以上这些,其实我只想说明一点:
固然,可能不少人仍是没法理解,这也正常。等有一天大家真正参与项目维护(尤为是多人团队项目)的时候,大家就会明白这些事情的重要性了。
笔者在以前的屡次程序做业中,发现每次都要花上好半天的时间搭建程序框架,并且作得基本都是重复工做。
做为一个聪明的懒人,笔者因而本身写了一个简单的的工程代码模板。Git仓库,欢迎star
这样的一个框架能较好的符合笔者本人每次写代码的工程结构须要。同时对于一些常见需求也都进行了以行为逻辑为主的封装,能够经过类继承等方式快速构建功能模块(尤为是多线程功能模块)。
这个库将会不断地维护和更新,但愿能够帮助到你们。
笔者做为一个写了不少年代码(至今已有10年有余),且实际维护过不止一个工程项目的程序猿,每当想在群里分享一些我的对于程序、对于代码规范、对于工程的理解和见解时,永远会有一些人站出来针锋相对。
他们的主要逻辑以下:
其实,笔者对于这样的想法仍是表示能够理解的。毕竟每一个人站的角度不一样,格局天然也有天壤之别(俗称:屁股决定脑壳)。
不过,笔者仍是但愿各位能好好想一想大家是为什么而学习的。仅仅就是为了赶快毕业拿文凭?
固然了,若是这就是你的所有想法,并且之后不想从事这方面的工做的话,那的确随意,只能说你和我们技术发烧友(或者最起码是打算靠这个吃饭的人)不是一路的人。
若是不是这样,那么你就该站在更高的格局上想问题:
结论很简单,能有所收获的,就是对的;能收获更大的,就是更好的。
接下来我来逐个回应下这三个常见逻辑:
咱们就是想完成做业啊
请想想仅仅就是完成一个做业的话,你能学到的东西有多少。。。个人程序是不能运行了仍是怎么着了
请想一想,程序能运行可是毫无维护性和可合做性,这种代码除了糊弄一下做业有任何价值么。。。你说的再多代码规范啥的同样会被钻牛角尖啊
请相信咱们的助教团,他们会给你公道的。你只管作好本身就是了。另外,可能有些人(甚至包括一些著名大佬)受到知乎上一些所谓的6系人的影响,认为北航的OO课程就是一无可取毫无收获的。对于这样的无脑黑,我只能对您表示深深的同情和怜悯,由于您在用您的将来前途消费,而目的,仅仅只是为了证实一个帖子,和一些前人片面的话语的正确性。
前方高能。接下来的章节可能会引发部分人的不适,请非战斗人员迅速撤离。
时间过得真快,一不当心又过去了三次做业。不过这其中天然也有些不爽的事情发生。
虽然呢,笔者很清楚,这样的东西写进本身的技术博客实在是很是不雅,简直能够说弄脏了笔者的博客(事实上这也是笔者没在上面说这件事的缘由)。可是有些事情嘛。。。仍是不吐不快。
因此接下来讲件事,请你们自行评判。
笔者在第五次做业截止后的一个晚上,忽然被告知,本身被无效了???
而后,到群里问了下助教缘由,缘由是,我在readme.md
的指导书url连接中包含了"我的信息"
连接地址全文以下:
https://files.cnblogs.com/files/HansBug/OO%E7%AC%AC05%E6%AC%A1%E4%BD%9C%E4%B8%9A%E6%8C%87%E5%AF%BC%E4%B9%A62018v1.2.pdf
当时,懵逼了。然而再仔细一想,发现事情并不简单。
越想越以为不对劲。因而笔者过后仔细一琢磨,能够推测出这我的的逻辑流程图:
个人天啊,老哥诶,您老人家为了避免测试别人的程序为了让本身睡大觉,可真是煞费苦心啊。走了这么多步终于找到了我,我佩服你那为了偷懒不怕艰难险阻过五关斩六将的精神,是在下输了。
或者,咱们甚至还能够在这个的基础上进一步扩展下:
也许可能各位还有点疑惑,甚至以为我是在用恶意揣测别人。很明显,有如此耐心历经那么多个环节,只是为了找别人的无效做业痕迹的人,我并不相信他有可能一上来就是想好好测试的。
然而这样的人物,一个对学术毫无敬畏之心只想着偷懒万岁的人,竟然还能和笔者分到了相近的段位,笔者只能哀叹——“知人知面不知心”啊。
综上,我只想对这人说一句:你有种给我站出来,别玩阴的!!!
不过,再一想一想,人家如何如何,关我何事。笔者仍是想认真的对学术和工程负责到底的,以及,没法带来任何实质性改变的怒火是毫无心义的。
正如上文所言,这类人挖地三尺的通常性动机,就是在于一旦把对方无效了,本身就能够不用测试了。
事实上此次事情的后续进展也代表笔者的猜想彻底正确——截至互测时间结束,笔者这份被无效的做业依然一个点都没有进行测试(包括公测)。最后仍是在笔者的一再要求下,课程组的助教们帮笔者完成了测试(在此感谢默默支持的老师和助教们,真的很是感谢)。
至此,这人的动机能够说是很是明显了。若是他只是一个按照规则办事且对学术和工程质量负责的人,那如何解释到最后都一个点都没测试的状况?
说到这里,笔者对于课程有个改进的思路,以遏制这种表面矫枉过正实则恶意满满的行为:
理由其实也很是简单:
以上是个人见解。