北航2022面向对象第四单元:UML模型
北航2022面向对象第四单元:UML模型
内容概括
- 本单元的架构设计
- 架构设计和面向对象方法总结
- 测试方法总结
- 课程总结
- 课程建议
1. 本单元架构设计
本单元主要是解析UML元素之间的关系。从字符串到UML对象的部分已经写好了,我们的任务主要是从UML对象开始,建立一个组织结构。
1.1 第一、二次作业
这两次作业主要是实现查询功能,工作是写一个接口实现类,实现接口的方法。刚看到这个要求,就想起来三层架构模型,类似Controller-Service-Dao的设计可以搬到这次作业来。另外,创建一个model包,包括各种元素的模型,在UmlElement基础上拓展,比如访问一个类模型,可以知道它继承谁,实现哪些接口,有哪些属性和方法,相比于UmlElement类访问更加方便。这里建立模型的过程也就是实现组织架构的过程。
接口实现类就类似于Controller,处于最顶层。我的写法是在这一层只进行异常处理。通过异常判断之后,直接调用这个类当中的Service对象,交给下一层处理。
Service对象是最主要的逻辑部分。它提供两个功能,一个是承接上层Controller的异常处理任务,判断是否有异常。另一个是业务逻辑处理。此时可以认为不会有异常情况,就直接进行业务处理。Service接触到的都是自己创建的UML元素模型,而不再是朴素的UmlElement元素,访问元素关系更加方便。元素模型就来自于下一级的Dao对象。
Dao对象是最底层的,类似一个数据库的功能。这里通过传入的UmlElement[]
作为最基础的数据,利用model包,创建自己的元素抽象。在构造方法时就直接构建模型,比如先创建MyClass
,然后创建MyOperation
,把MyOperation
填到MyClass
等等。构造方法结束以后,Dao对象已经建立了组织结构,给Service提供数据模型访问的服务。
总体而言,请求进来时,先通过顶层Controller判断异常,然后交给下级Service处理逻辑,Service访问下级Dao获得数据模型,进行逻辑操作,再把结果传给上级Controller。
1.2 第三次作业
第三次作业增加了判断模型是否有逻辑矛盾的结构。但是前两次作业的Dao是在构造方法建模的,而且直接默认没有逻辑矛盾。这里为了方便,在Service层增加了一个Service类CheckService
,专门用来判断逻辑是否有矛盾。如果有问题直接退出,没有问题了,再创建Service对象和Dao对象进行建模。好处是不用破坏以前建立的模型结构,坏处是没有用上之前的代码。CheckService
类直接从UmlElement[]
原始数组开始判断,导致代码量很大。
1.3 架构图示
第三次作业临时增加的部分是CheckService
,相较于第一、二次作业的内容比较独立。
2. 架构设计和面向对象方法总结
2.1 架构设计
这四个单元作业下来,基本的架构设计都是一样的思想:分工。
把一个很复杂的工作,逐渐分解成内容相近,作用相同或者递进的部分。这样每个部分都只需要专注处理自己的工作,然后调用其他部分来完成连接。作为整体的编程者或者控制类也可以抽象出来,专门处理各个部分的连接和调用关系,而不用在意底层的细节。
-
第一单元作业中,就把复杂表达式拆成了简单表达式、项、因子等。每一层只需要考虑自己的逻辑,把下一层的逻辑给下一层处理即可。
-
第二单元作业中,把乘客的输入输出分门别类。把外界输入包装成类,只关心往“数据库”里放乘客。电梯线程专门做成类,从“数据库”取乘客,只关心移动,开关门,上下客。至于客人进来和出去,则调用“数据库”方法即可。“数据库”则只负责数据处理和分析,判断电梯运行方向,乘客往哪里放,乘客是否全部送达等,至于放好以后则让电梯自己取即可。
-
第三单元作业,也是通过规格实现明显的分工。通过强制的要求,分清楚每个部分的权利和责任。每个部分都可以认为输入是正确的,或者只有有限种异常情况。如果输入错误,则不用保证程序的正确性。
-
第四单元作业,也是把整个流程拆成几级,有的只处理异常,有的只处理业务逻辑,有的只处理数据和建模。通过纵向请求,每一级的逻辑只处理上级请求,只调用下级方法而不管具体实现逻辑。
我认为实现一个复杂任务最重要的部分就是分工和权责明确。首先了解整个任务要完成哪些功能,这些功能是否能从更简单的功能拼接而成。这样就可以把整个任务拆成平行或者垂直的各个子任务,每一个子任务的工作量适中,内容紧凑。实现分工过程也是一种分工,对于编程者而言,把整个任务分成了逻辑层和实现层,有利于理清思路和纠错。查看各部分连接时,不用关心实现细节。查看实现细节时,不用关心和其他的关联是否正确,直接调用即可。合理的分工可以显著提高程序的逻辑性,更容易保证正确性。
2.2 面向对象方法
面向对象的三大特性,封装、继承和多态,在这几个单元都有应用。
这学期的代码检查,就要求所有的属性要是私有属性,对外提供方法来访问。这样的完全封死在继承方面略显麻烦,但是对于新手来说,可以培养作用域和权限的意识。其实通过方法访问也能完成所有的功能了。
继承和多态则是提供一种高层次的抽象。对于子类,可以像父类一样调用;对于接口实现类,可以像调用接口函数一样调用。作为使用者而言,只需要这个对象继承了我这个类,或者实现了我这个接口,就可以直接调用方法,而不用考虑具体实现是什么样的。甚至具体使用哪个方法都有可能不确定,可能根据用户使用进行调整。这样调用者和被调用者通过接口/抽象类/继承可以达成一种连接。即调用者只管使用方法,而被调用者只管维护方法的形式。如果后期有修改的需求,只需要修改内部逻辑,对接口和接口以上的所有类都不用修改。这样高层次的抽象实现了一种去耦合,减少了代码的大量修改。
通过使用面向对象方法,增加了代码量,换来的效益是程序逻辑清晰,修改和纠错方便。在代码量很大,很复杂的程序当中,面向对象比面向过程的优势更加明显。
3. 测试方法总结
3.1 白盒测试
在拿到源代码之后,如果能了解编程者的处理逻辑,对于测试和纠错就有了很大的方便。因为代码的全部逻辑都是已知的,如果哪里有逻辑错误,就可以进行定向爆破,很快能发现问题。但是通过代码理解逻辑并不简单,尤其是代码量很大的时候,可能逻辑非常复杂,编程者没有考虑到的问题可能测试人员也考虑不到。因此白盒测试虽然有效,但是实际上效率是比较低的。对于代码安全要求很高的地方,比如军工、航天,需要查看代码,分析逻辑来debug。
3.2 黑盒测试
黑盒测试不需要理解代码的逻辑。认为代码正确就是在给定的输入下应该有给定的输出。如果已经知道了输入和输出,那么测试是非常方便的。只需要给程序大量的测试样例,看看输出是否是我希望的。在很大量的数据输入下如果没有问题,可以认为程序出错的概率非常小,可以忽略不计。
3.3 我个人的问题
一般白盒测试需要看代码,而且能理解别人的逻辑,还要找到漏洞是很痛苦的事情,所以采用黑盒测试更多。
但是黑盒测试有个问题,就是标准的输出需要已知。我感觉这就说明黑盒的测试更加适用于输出情况较少或者容易判断的情况。一般黑盒测试是搭建评测机,让程序和标准程序对拍。
但是大部分的情况下,对任务的描述都是自然语言,描述存在模糊性,很难覆盖到所有情况,而错误往往以意想不到的情况出现。如果编程者难以写出没有错误的程序,那测试者怎么能写出完全正确的评测机呢?这实际上是不可能的,我们看到课程评测机有时也能出现问题,把错误的程序判对。
所以我们的作业正确与否也只是相对正确。评测机能查出一些常见的错误,但是不能保证评测机就一定是完全正确的,也许有的同学有其他的错误,但是评测机没测试,就认为该同学正确了,但是在互测环节还是能测出很多bug,说明同学们的作业也不是完美无缺的。当然,在分数上可能就显得参差不齐了,两个错得不同的程序,只因为是否被评测到,可能最终分数有较大区别。上评测机也只能是一种折中和妥协。
我只在很少的地方用了评测机测试,因为我认为如果我写作业程序不一定能写对,那写评测机又如何能保证没有错误呢?要么是在把作业的逻辑又从头到尾写一遍,要么是用别的算法来相互印证。但是由于思维惯性,如果评测机和作业错得一样,那永远也不能发现错误。
因此我在想,对于用自然语言描述的程序,有什么好办法可以debug呢?我现在还没有这个问题的答案。
4. 课程总结
在OO课之前,我自己接触了一些Java代码。但是也没有到能应对整个课程要求的情况。
本学期OO课从成绩而言估计不太好看,但是换个角度说,能发现自己的很多问题,也能说明进步是很大的。
我认为收获最大的是第一、二单元的内容。解析字符串的方法让我隐约感觉到了编译的味道,从更接近自然语言的程序语言或者表达式中抽取逻辑,然后实现相应的功能(计算,化简,部分化简等)。而多线程程序则是我之前几乎没有接触,也没有写过大量代码的区域。刚学习多线程时,甚至都不明白是哪个线程调用wait
和notify
,也不知道锁的对象应该是什么。经过几个星期的学习,逐渐熟悉了多线程编程中比较基础的用法。实际上在其他地方,比如操作系统,Qt程序等也用到了多线程和线程安全控制。
统计损失比较惨重的几次作业,很多都是大的逻辑没有问题,但是在莫名其妙的地方犯错。以至于把那个方法单独提出来,根本不可能出错,但是放到一大堆代码里就写不对了。这主要是分工不明确,没有搞清楚哪个部分应该做什么事。特别是在第二单元,数据和逻辑混在一起,导致代码往往要兼顾几个部分的内容,加上情况很多,写的时候思路不连贯,就会遗漏或者搞错条件。
电梯单元是容易搭评测机,而不容易做全面检查的。但是恰好那几个星期时间很紧,没有写评测机,结果就撞在枪口上了。但是抛开这种情况,测试的问题依然存在,就是如何写出正确的程序。既然写作业有问题,那写评测机就一定没问题吗?
我认为比较好的方法还是修改分工。每个部分内部的逻辑紧凑,代码复杂度适中,写细节的时候思路能更加连贯。而连接部分最好能画图表示,更加清晰。在写代码量大的程序的时候,逻辑清晰尤其重要。
5. 课程建议
- 代码测试可以单独开一个章节,让同学们学到更多测试的知识。目前课下中测的强度很弱,强测又不能多次提交。如果想提高正确率就只能构建大量评测数据和搭建评测机,基本上是把作业又写了一遍,花费很多时间,而且自己测试的效果还不一定好。
- 互测可以进行一些调整。我听说有不少同学不愿意或者没有时间做互测,因为hack别人的代码和hack自己的代码一样,需要搭建评测机。如果以正确性为要求,我认为评测机样例和同学样例应该有相同的效果,不能因为评测样例错了就认为错的多,错得严重,而同学例子错了就是不严重的。也许评测是每个错误点扣分,而互测可能一个点就错了很多。但是许多错误点都不是孤立的,错了一个可能其他点也过不了。这部分我还没有特别好的解决方案,只是提出自己的看法。
- 讨论区和答疑的形式可以优化。比如讨论区的回答不能接着问题,导致要翻很久才能找到回答,提问处不能知道有没有回复。而且关于指导书的内容问题可以专门整理一个区域做勘误处理,否则没有及时看讨论区,就可能错误的理解,导致写的程序错误。