OO第四单元博客&学期总结

OO第四单元博客&学期总结

本单元作业的架构设计

从上面的UML类图中可以看出,我的代码按照UML模型的类型分为比较明确的三个部分——ClassModel,StateChart和Collaboration。这其中对原始的UmlElement进行了封装,存储了自己的参数,继承对象,关联等信息构成了MyUmlxxx类。

主要的设计和第一单元表达式求导非常类似,按照自顶向下的顺序的元素建立层次关系,如ClassModel下含有UmlClass类,其中father为其他MyUmlClass,可以实现MyUmlInterface,可以有若干MyUmlOperation和Umlattribute。MyUmlOperation中又可以包含UmlParameter。

输入部分中我选择反复循环遍历数组中的内容,从中一层一层的建立上述关系。如首先确定class和interface,第二次扫描中再确定继承和实现关系等等。

处理请求中我会将每个请求定位到对应的层次,比如这是对应于一个class继承数计算又或是对应于一个方法的参数类型检查,从GenerallInteraction中逐层向下传递处理请求并最终返回。

四个单元中架构设计及OO方法理解的演进

第一单元——前后端分离、自顶向下

第一单元主要架构设计体现了纵向设计的特点,不得不说这一点和大家之前熟悉的面向过程语言还是很贴切的。

前后端分离

前后端分离是一个非常重要的设计思路,在第一单元中我们的任务主要有两个:解析输入的表达式和进行长度优化。这就可以抽象出表达式解析器前端,和优化器后。解析器前端对原始的表达式进行递归下降,提供的是一个约定格式(含有因子的hashmap)的Expression对象,优化器拿到它可以对于这个对象可以进行各种优化尝试,比如合并同类项,拆括号,三角优化等等。

前后端分离是各个领域中的常见策略,web、编译器,甚至物理芯片设计中都以不同的前后端概念践行着这一策略。

  • Web:前端负责网页渲染显示,后端负责数据提供,两者以json交互。
  • 编译器:前端负责语言解析,中台负责进行优化,后端负责生成目标机器的二进制代码。
  • 物理芯片:前端负责进行逻辑设计,后端负责将门级网表在硬件上实现。

前后端分离体现了solid原则中的依赖倒置原则,上层模块不应该依赖底层模块,它们都应该依赖于抽象。前后端交互的信息就是这儿的抽象。这一思路最大的优点是可以将工程的不同部分解耦,极大地方便了工程的任务的分工与合作。前后端团队可以独立完成自己的实现细节部分,比如第三次作业语言前端对语法错误的检查和后端的优化理应没有任何关系。而在合作中两者的交互只需要一份api文档作为中间抽象的解释。

自顶向下

自顶向下又是一个重要的架构设计思路。一个稍有复杂度项目往往在刚上手的时候是一个大的任务——设计一个购物网站、设计一个表达式求导工具。如果先思考底层问题,用什么数据库,里面都存什么值,表达式最小组成单元都有什么,整个项目就陷入了一团乱麻。而应该采用自顶向下的构建方法,设计一级核心子模块,然后是二级子模块。具体就我们的第一单元作业而言,表达式由项构成,而项由因子构成,这就自然形成了一套自顶向下的设计关系。

第二单元——策略模式

如果说第一单元架构主要是纵向拓展,那么第二单元架构设计上最重要的特点就是进行的就是横向拓展了。在第二单元中,我们的电梯会面临不同的输入数据特性(Morning,Night,Random),他们就对应着不同的调度方法。这些调度方法在我的电梯是不同调度类被挑选着来操作电梯,根据输入数据有一些特化的调度逻辑。这和OS中机制和策略分离的思想非常类似,总的来说可以提高程序模块的可重用性、移植性,并且可以让开发更加灵活。在这还有一个例子是第三章中的qnr指令,在我第三单元博客中提到可以设计一种机制——支持查询给定key顺次的接口,那么自然可以开发出不同的策略如直接遍历TreeMap,不同类型的平衡树等来支持这个机制。这样的话这些策略就有了一个统一的视图,具体策略细节对上层透明,而又方便开发者进行横向的拓展和调试。

从垂直架构来说,他们从上层调度器类中接受乘客信息,又操作着与输入无关的电梯基本运行方法,如上下乘客,楼间移动等。

第三四单元——emmmm

这两个单元OO课程组已经规定好了方法的参数和要实现的功能,在此基础上只要写一些小的数据结构或者算法就OK了。在架构设计上给我们的自由度不太高,故我仅仅实现了课程组的代码要求,真正能说上架构的可能几乎没有(。

小的总结

学了一学期OO,总不能连solid原则也不知道,在这也是学期的结束再次温习一下它们和个人在OO实践中的一些理解。

  • 单一职责原则:一个类应该只有一个发生变化的原因。这也就是我们常说的高内聚,低耦合。一个类应该完成单一具体的某一个功能,如电梯应该只管自己的运动与上下乘客,而别的功能如调度等应该和它分离。

  • 开闭原则:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。第三第四单元比较简单,指导书就已经确保了我们几乎就是增量开发,但是我想包括我在内很多同学在第一第二单元都经历了让人抓狂的重构的工作,这就是想要尽力修改已有的实体迎合新需求时的痛苦。我觉得这一点需建立起项目意识,在程设和数据结构中,我们写的基本都是一个半个小时就扔的短小程序,这样的程序自己怎么莽都是无所谓的。但在今后的学习和工作中,有价值的代码都是以项目的形式存在的。在开发一个项目的时候就必须要考虑它的迭代维护性,也就是要有余量设计的意识(区别于过度优化),这样在面对新需求时才能不慌不忙。

  • 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。这一点是说子类和父类应该在视图上保持一致,也就约定我们在重写父类方法时最多只能修改细节,而不能直接破坏父类的抽象。

  • 迪米特法则:一个对象应该对其他对象有最少的了解。维护这一原则需要在便捷性和耦合性之间作出取舍。简单的数学知识告诉我们两边之和大于第三边,不管怎么优化调用链总比不上一次直接的调用来的舒畅。我觉得是否应用这一条原则还应该根据两者之间是否位于设计中的同一级,在同级之间的元素可以相对自由的访问,而如果两者有层次关系,那么最好添加中间转发的设计(如os中的syscall连接了内核态和用户态)。

  • 接口隔离原则:1、客户端不应该依赖它不需要的接口。2、类间的依赖关系应该建立在最小的接口上。这一点是为了减少设计中的臃肿性,比如第一单元中我们的表达式,项,因子等对象都可以进行输出,也可以进行求导,那么这就是最小化的接口,至于别的功能如化简(普通因子不具备)不应该是共有的接口,不应该添加在最顶层的接口中,可以添加为接口的继承关系。

  • 依赖倒置原则:1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。2、抽象不应该依赖于细节,细节应该依赖于抽象。这一点在上面已经说过了。

四个单元中测试理解与实践的演化

首先分析课程中做的最多的黑盒测试和集成测试,也就是大家常说的评测机。直接借用一下研讨课的内容,很难说四个单元有什么“演化,个人感觉更多的还是对症下药,针对性测试。

数据生成

  • 第一单元:强调数据的覆盖性和边界情况。是否能覆盖到所有的可能的合法表达式输入和可能的错误表达式(如在我的互测屋里有一位同学没能考虑sin(x)中x前面可能存在的空格。
  • 第二单元:强调数据的强度和公平性。在同一时间发出大量的请求来制造线程不安全问题,同时为了模拟强测的性能分,数据要包含随机特性和某种的规律特性(然后找人对拍观察自己的性能"bug")。
  • 第三、四单元:强调数据覆盖性和强度。这两单元的强求数量很多,每一个指令应该覆盖到。同时针对各个问题应该构造最坏可能性数据来测试。

总的来说是两个方面:覆盖性测试和压力测试相结合。覆盖性保证各个情况下基础功能完备性,而压力测试对性能热点问题:表达式多层嵌套、多人同时乘坐电梯、完全图、交叉引用等进行排查。

程序运行

这个四个单元是一以贯之的(第二单元除外)。使用java运行jar,重定向输入输出,设置时限为了kill程序。第二单元的独特性在于还要统计cpu时间,以及定时投放输入的要求需要利用管道机制,使用另外的程序对重定向的输入管道输入数据。同时第二单元测试的时候要进行大量进程并发,尽量暴露可能存在的线程不安全问题。

结果评判

主要基于不同的答案的特点

  • 第一单元:sympy检验答案数值正确性,自己的wrong format检查格式正确性。
  • 第二单元:spj检查输出的合法性。
  • 第三第四单元:对拍检验

单元测试/CI

这两项内容只能说在这学期试了一下。基于Junit的单元测试可以将错误尽可能的定位在被测单元中,而CI则可以将代码的问题定位在一次commit中。在我们OO课程规模的代码中可能起不到优势,毕竟方法个数有限而缺乏规格(第三单元虽然提供了规格,但是从规格到测试样例的转化工作量依然很大),commit个数也只有寥寥几次。但在多人合作的更大型软工项目中,这些会是必不可缺的测试技术,希望能在大三的软件工程中对他们有更多的接触。

课程收获

  • 术:从最基础也是最浅层的术的角度来说,本学期是第一次接触java语言和面向对象语言,了解了java的基础语法和面向对象的各种概念。了解java中多线程的控制和简单的多线程编程,如何通过锁的方式来完成线程间的同步和互斥,尝试了单元测试等等。
  • 法:了解了面向对象的基本原则(上文提到的solid),常见的设计模式,测试的方法论等。
  • 道:Think before you code。一直以来,我可能都是一个手走在脑子前面的人,做事情都比较快,这一点在高中的应试教育中一直比较成功,考试最多也不过就2个小时,做的快了还有半个小时检查,之前的程设也是风风火火赶紧AC了了事。对于这些任务来说,简单想一想就动手和边做边想都是常态。但对于一个程序员来说,速度真的就是制胜法宝嘛?在OO中我就多次吃了草草下手的亏,在表达式中疯狂重构,在电梯中疯狂调整线程安全问题。面对一次OO作业,总工作量可能需要5-10小时,在其中抽出半个小时的时间(第一次可能要更多),仔细想一想自己的项目应该怎么架构,测试应该怎么做,我相信会是一个很有回报比的事情。毕竟,在设计阶段出的问题可能在开发中要花10倍时间才能弥补。

改进建议

  • 建议调整几个单元的顺序,将UML部分摆在第一个单元,好处有两个

    • 帮助同学们好好掌握UML语言,知道如何去描述自己的面向对象模型。
    • 降低第一次作业中设计部分的考量,帮助Java语言初学者快速上手该课程。
  • 建议第三第四单元作业给予同学们更大的自主性。虽然我们的课程主张只要完成核心部分的构建,外围代码构建由官方包完成。但最终落实到个人的完成的任务上已经变成了函数填空性质,建议可以换用更有深度的课题,减少要完成的操作数量,而在单一操作上给个人以设计和发展的空间。

  • 公开实验的结果,让同学们知道自己做的对不对。现在的提交只能看到编译不通过个人感觉非常disappointing,至少让同学们知道json解析通不通过,带进源代码里能否编译通过吧。

posted @ 2021-06-26 19:51  goodgame  阅读(44)  评论(0编辑  收藏  举报