window.cnblogsConfig = { banner: { home: { background: [ "https://ss2.meipian.me/users/1208999/9592effa062cfa4997cdfe0b236df685.jpg", "https://img35.51tietu.net/pic/2016-121403/20161214035544cb0jgjjorpc54532.jpg", ], }, }, } window.cnblogsConfig = { banner: { home: { title: [ '每一个不曾起舞的日子,都是对生命的辜负。', '抛弃幻想,准备斗争。', '上辈子天打雷劈,这辈子学计算机。', ], }, }, }

BUAA OO Unit4 —— UML Interpreter

BUAA OO Unit4 —— UML Interpreter

by Monument_Valley

0.写在正文前

本篇博客是笔者在北航2022年春季《面向对象设计与构造》课程第四单元的三次作业的总结。

本单元的主要任务是设计一个UML的解析器,解析给定类图、状态图、顺序图的信息,并基于UML的检验规则对给定的图进行检查。

1. 架构设计

1.1 Homework 13

本次作业的任务是解析类图。

在写代码前,笔者预测到未来作业中不只有类图解析的任务,因此将所有类图处理的函数放在MyClassDiagram.java下,便于集中处理类图元素,并为后续图的类型扩展,书写新功能函数留下了开发空间。

对于UML的元素,我们将其分出几个等次,在遍历所有元素的时候,只有在遍历完上一类的元素后,下一类的元素才能找到有意义的父类元素以搭建逻辑关系。顺序如下:

  1. Class / Interface
  2. Asociation End / Operation / Interface Realization / Generalization / Attribute
  3. Association / Parameter

此外,由于每个UML模型内的元素都有唯一的id号,故仿照第三单元作业的思路,建立一个HashMap,用来存储所有模型中元素id->元素的映射,并利用Java不可变元素的特点将这个哈希表共享到相对应的处理程序中。

对于诸如Class, Operation等具有多个内部元素的元素,对其新建一个文件,设置相关容器用来存储这些元素。

对于检查重名的问题,我采取一套HashMap<String, ArrayList<MyElement>>的机制,将重名的某种元素集中储存到一个列表容器中(在本作业中,主要检查重名类),这样既可以保存元素,又可以快速判断出一个类名下是否映射多个类的情况。

剩下的算法也没什么说的,都是些基础的图算法,有的是查询,有的是遍历,有的是检索,用数据结构离散数学的知识写写就好。

结构如下:

1.2 Homework 14

这次作业增加了对状态图与顺序图的解析。

由于上一次作业中预留了扩展空间,因此只需要增添状态图类与顺序图类用来处理相关图,在MyImplication类中实例化相关方法,分配到对应业务程序中处理即可。基本结构没什么变化。

架构如下:

1.3 Homework 15

这回作业添加了对UML类图的检查逻辑。

由于UML图的相关程序结构已经建立完整,因此这回只需要在相关业务程序中添加相关检查函数即可。对于某一类元素(如name为空的元素等),在解析图的时候就对其进行判断,并用布尔值标注好,减少之后重新遍历的时间。对于使用递归函数进行检查的方法(如R004检查重复继承)尽量采用队、栈等方式进行非递归实现,避免特殊图导致爆栈。

好在由于程序树形结构比较好,将相关函数实现尽可能分解功能到最基础的元素上,因此没有出现重构、超行等问题。

结构与Homework14一致,故不展示程序相关UML图了。

2. 架构设计思维及OO方法理解的演进

程序设计的架构思维在这学期的课程中得到了显著提升。

2.1 Unit 1

这单元作业以表达式为例,在理解表达式运算的过程中快速理解抽象实例,建构对象。在这个单元开始之初,我尽量模仿pre中对对象的抽象过程,尝试通过正则表达式与贪婪模式提取元素,建立变量、幂次、项、式子等元素,尽管抽象过程非常混乱,但由于单元任务比较简单,因此也就蒙混过关了。

在第二次作业中,三角函数与多重括号的加入使得正则表达式快速退出了我的设计方案。在经历近一周的冥想时间后,终于对递归解析有了具体认识,将式子中的多项式项识别出来并进行进一步解析。在此过程中,我对单变量、幂、三角函数等算子的认识进一步加深,并经由算子与抽象数学的相关认识对表达式中的元素进一步建模,并设计相关工厂模式来解决问题。

在第三次作业中,由于第二次作业中已经对表达式进行递归解析,因此实际上任务早已完成,但在重新阅读代码的过程中,进一步从运算律的角度认识这些表达式对象的特征,并将其进行正确排序。

这个单元的总体收获就是,面向对象的过程就是要把相应实例进行高度抽象化,提取特征建立对象,并将对于这个对象的操作尽量集中在这个对象对应的类之中,使程序逻辑更为清晰,更易拓展。

2.2 Unit 2

这单元作业以并发状态下运行的电梯系统为例,引导我去认识多线程程序的设计。

尽管在单元开始前后阅读了大量有关多线程的材料,但在程序编写的过程中还是出现了许多问题。如线程安全隐患,对调度器功能的模糊不清,调度算法实现方法等,其中最恶心的还是长期无法排查出来的轮询问题。由于对共享对象关系的模糊,导致有的共享对象来回上锁,甚至上多道锁,造成效率低下,有的是没有及时上锁导致程序反复运行一段代码,致使占用大量无效资源。直到最后一次作业,我才算真正认清共享对象的处理方式,在必须共享的时候,才会搭建起线程锁对其保护,其余时间,各个线程各干自己的事就好(上了OS以后,借着信号量的概念,我才真正搞明白锁的实现方法与并发运行)。

2.3 Unit 3

这单元作业以一个社交网络软件为例,带领我学习JML语言,准确理解规格,保障程序可靠性。

这单元的架构基本是限定死了,不过基于在前两个单元的学习经验,我在需要Kruskal处理最小生成树与Dijkstra处理最短路径时,建立了相关算法函数,建立了一个算子库,在需要某个算法时,直接调用算子对象就可进行运算。这可能也算一种架构?

2.4 Unit 4

这单元作业是解析UML图。

UML中的元素的对象特征可谓是相当明显了。只需要按照对应元素建立扩展类处理该类对应对象的一些任务即可,此时对这种级别的面向对象设计可谓是轻车熟路了。

3. 测试理解与实践的演进

测试这个工作,主要还是考验你的程序在一般情况、边界情况、高强度情况下的程序正确性与效率。

我记得我有个很好的同学曾经跟我说过:为什么大二以后的编程课会相比大一那些大块大块的数理基础课上得累?因为在真实的教学过程中,微积分可以在你跳步甚至轻微伪证的情况下因你得到正确答案而给分。程序就截然不同,如果一个地方出现了错误,哪怕再细微,也将会导致你整个程序无法正常运行。因此,你需要花大量的时间来去搜寻你那个细微的错误。也就是说,编程是一个写一堆零在后面,但要保证最高位一定是1的工作。而如何让你的程序的鲁棒性足够高,足以经过风吹雨打,如石头上刻着的圣经一样永垂不朽呢?那就得靠上面所说的测试。

对于第一单元,我采用自动与手动方式构造表达式,使用sympy做正确性范例进行对拍。由于自动化数据生成总会违反一些规则,在一定概率下出现不能用的式子,因此还需要一些手动筛选。

对于第二单元,我在与大量同学进行对拍后,保障基本正确率,然后自动生成时间局部请求密集,楼层跨越高等高并发电梯使用请求,对程序进行边界测试与性能测试/

对于第三单元,我在基于对代码的形式化验证后,首先自动生成大量数据,构建复杂图(比强测要求高10倍以上的强度),对程序进行测试,然后手动构造特殊的社交网络图(如一次长蛇阵,检验我编写的Dijkstra算法代码的运行效率),对程序进行边界测试。

第四单元由于进入考期,所以除了基本的功能性测试与一些形式化验证,没干什么事情。(不过写了一学期代码了,代码正确率确实有了明显上升,基本写出来的不用太经过调试就可以运行。而且,UML图的测试要求相比Unit3来说低了太多了,理论上也不会出现超时的实现。。。至少我想不到)

不过说到底,测试的最有效方法还是自己耐心阅读代码寻找错误。在一行行阅读时,利用运行中的断电,寻找错误变量的出现位置,并对相关函数进行修改。

4. 课程收获

这门课作为北航计算机学院的编程能力培养的相关课程,推背感十足,课程内容也非常充实,除了进一步强化对象思维外,还强调了多线程程序的架构思维、JML语言与规格,以及UML与抽象。说到底,编程是一个将人的思维过程映射出来的方法。它解构了你的思维模式,并将其清晰化。如果说你现在让我去思考我生活中身边的一些事情,如点外卖,我就会去思考在这个过程中,除了顾客与商家外,还有骑手与外卖平台。一份外卖有什么特征?一个店又有什么特征?外卖员抢单,到底怎么才能让最快抢单的棋手真正抢到那个单子?外卖中间出了事故该如何反应给顾客情况。。。这样的思维展开可以有许多。

更抽象地说,面向思维其实就是一个方法论问题。你该如何去认识你周围的世界?这个世界里都有什么物质?物质之间是如何形成关联的?其实,这些都可以用面向对象的思维进行一些回答。

老实说,这门课的强度是够大的。上这门课之前,我做梦也不会想到我可以在一天写出1000多行代码。如果算上别的课程,那北航计算机学院的大二工程训练强度确实能达到一周数千行的规模,曾经以为的吹牛转眼就变成了现实。。。

不管怎么样,这学期的课程也算是熬下来了,好在我也没有无效作业,虽说浑身是伤,但也活下来了。下学期,继续来熬吧。。。

5. 课程建议

大半夜的,实在是写不动什么东西了。。。把我在OO2023助教第一轮面试的回答贴在下面吧,供诸君交流。

Q1: 对于第一单元的三次作业安排,你观察到了什么问题?对此又有什么建议?

问题一:作业递进速度太快。

就2022年OO课程安排来看,第一单元第一次作业的难度是刚刚好的。同学们知道对一个多项式的形式化表述后,基于在Pre3中学到的正则表达式对表达式进行解析,然后以“浅薄的对象思想”对解析的模块进行对象建构,然后完成计算。然而在第二单元,括号嵌套的加入直接将第一单元解析式表达的任务提升到了解析表达式树的层次。而其实,有能力使用递归解析完成作业的同学,第三次作业的意义并不大(到了第三次作业,无非就是把表达式树中的某个节点替换成从这个节点出发的某颗子树罢了)。在第一次作业中,懂递归解析与不懂递归解析在完成作业时直接上升到两个极端,两极分化速度太快,打击同学初入OO这门课程的自信。

解决方案:

  1. 第二次作业更侧重于工厂模式等开发模式以及设计过程的学习,强化对同学们在设计模式上的要求,将原第二次作业的内容移动到第三次作业中,将第三次作业的内容另设加分作业放在平台上,鼓励同学们进行尝试。换句话说,递归解析是得讲,但考虑到很多人学习速度没那么快,因此将简单的递归解析放在第三次作业,更难的留作加分。

问题二:作业完成中存在许多只用字符串替换等方式完成作业。我们本单元作业是希望同学们借着计算这一问题出发,思考计算的本质过程,将计算过程用面向对象型语言进行建构。

解决方法:

  1. 提示同学们复习抽象代数。
  2. 增强人工审查代码力度,对强行使用字符串替换完成作业的同学给予一定扣分惩罚。

Q2: 2022年是OO研讨课第一次举行小组讨论。在研讨中你发现了什么问题?对此又有什么建议?

问题一:主动交流意愿薄弱,讨论节奏较低。

尽管能感受到OO课程组在通过跨单元打乱分组的方式鼓励同学们与更多的人进行讨论,但不可否认的是,与其它同学甚至陌生人主动进行交流的能力是当前许多大学生所缺失的。如果一个组内没有愿意表达自己想法与观点的人坐场主持,那没人愿意说话。如果组内有数个组员非常愿意表达自己(但不是全部组员,一组6人都是那种愿意表达的,说明分组情况还不太均匀),那就容易变成这数个人自己发挥,其余人不想也无法插话的现象。

解决方案:

  1. 增加每个老师的随班助教数量,助教在小组讨论时多多走动,必要时分享自己在研讨主题下的技术经验/研讨经验,引导同学们发言
  2. 优化研讨小组分组情况,在第一单元两次研讨课结束后,及时发现、筛选潜在的爱发言的同学,鼓励并分配其担任小组主持。
  3. 优化小组分组算法,尽量使每个同学在学期内站到讲台上发言一次。

问题二:研讨讨论内容重质性过高。

尽管课程组布置了诸如架构、技术、测试等多种讨论主题。但现实的执行情况是,我们的同学往往会选择架构而不去选择其它,究其原因,还是由于大家在开发代码的过程中,更注重于完成课程内容这一方面,对其它方面缺少主动探索的欲望。这个问题在课程上课、实验、作业、研讨等多方面中都体现了出来,只是在研讨课上的问题展现就是咱们的同学为了让自己能说一些东西,最后都集中去选架构这一相对好讨论的主题。

解决方案:

  1. 限制各个研讨主题的选择数量,对一些相对较少讨论的主题进行加分鼓励。
  2. 研讨课题可以更加贴合同学们的开发实际,如在第一单元与第二单元这两大基础开发单元最后一次讨论课中增加“吐槽环节”(如研讨课时间不够,也可放到线上讨论区,开设“吐槽帖”,帖子相比于微信的好处是说,信息流动性较低,所以内容的留存时间相对更长,一段发言可能被更多人看到),鼓励大家通过各种形式分享自己开发时出现的问题、不足。
posted @ 2022-06-29 03:44  Monument_Valley  阅读(20)  评论(1编辑  收藏  举报