OO_Unit4 + Summary

第四单元总结博客

终于一路熬到了第四单元,第四单元总体来说应该是四个单元最简单的,我耗时间最少的,但也是最纠结,最想打烂评测机的(原因是这个单元乱七八糟的概念太多了,而且需要关注的点还一堆,样例给的还十分不充分,我第一次交上去是WA了后三个点,然后经历了无数次“**,这个点没考虑到”,改过后信心满满的交上去,还是WA后三个,有那么一瞬间想放弃的心理(哭)。更可恶的是,这回RE会报WA,这就再次增大了盲盒debug的难度。导致我全部AC之后是这样的:

我也就差不多沾沾自喜了一天,第二天心慈手软的助教们就把最后一个点公开了)

总结本单元作业的架构设计

还是先上UML图(用UML图分析分析UML图的代码)

(先自我批评一下)感觉这次自己的架构挺垃圾的,第一次为了不超过文件行数,封装了一个方法,第二三次直接摆烂,最后一个完成后MyImplement类写了整整1443行,代码风格当然也就50分起了。
另外,有了第三次的经验,这次我为了性能,开始无脑使用Hashmap。首先是各个Id和uml元素对应起来,会java的人怎么可能挨个遍历(骄傲,另外按照UML元素的父子关系,我建了很多很多父亲和孩子list的Hashmap,其实就是这样:

private final HashMap<UmlClass, ArrayList<UmlOperation>> classOperations = new HashMap<>();

简而言之,就是一个class包含很多的operation,这个map可以用class直接找到它所有的operation,反查也很容易,直接用parentId找相对应的元素就可以了,相当方便有没有。不过这也导致了我代码前200行基本都是这样的:

另外,这个架构就导致初始化的过程非常恶心。首先初始化每个元素,需要加到相应集合中,再将它的Id(可能还有Name)与其本身建立映射。对于那些下面还有从属关系的元素,还需要新建一个子元素List,再将其与本身映射......总之就是前期工作特别多,写函数最烦的不是函数本身多恶心,而是写着写着发现需要的映射关系还没建立,就得到前面去加好多好多东西,才能建立起一个所需要的映射...但所谓“磨刀不误砍柴工”,这些前期准备让我后面实现起来非常方便快捷,而不是套一堆糟心的for循环。同时,也让我的程序性能非常好(暗喜

总结自己在四个单元中架构设计思维及OO方法理解的演进

先谈架构设计。我应该是oo课程中被架构设计坑得最惨的人之一了。第一单元架构完全没有设计,一是大一养成的坏习惯上来就开写,也不是很会设计架构,二是时间实在是不够了。这就导致第一单元架构相当混乱,除了提取了几个方法之外,相当于还是面向过程的,出了问题,总得从头点一点点找,效率相当低下。第二个单元依旧是那个老毛病,上来就写,一点设计没有。虽然最后克服了这个问题,架构看着也不错,代价就是花了整整一个月。三四单元我学乖了,(但也没什么需要我去设计的了),先考虑自己需要什么,把那些与主逻辑无关的代码封装成单独的类,或是作为工具类使用,保证他们正确的前提下,出了问题一定是主逻辑的锅。也就是我们在实际编程的过程中,是把这些封装类看作“黑盒”的,我们只能保证我们的输入,它能给出正确的输出,具体内部逻辑实现,我们是不关心的。当做好了架构设计,无论是写代码还是debug的过程都轻松很多。

对于架构一词,我一直没有一个很好的理解,经历了一个学期,我也只是对此有模模糊糊的感觉,百科上给的解释是“架构,又名软件架构,是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计”。我的理解是,如何把一个相当庞大的任务,分工下去,让其下面的小部件各司其职,如何对任务进行划分,以及各个模块之间如何达到耦合度最低,是架构设计的艺术。拿上学期的计组实验举例,完成一个流水线CPU,要经历取指令等过程,那么这里就存在一个问题:程序计数器(PC)是无脑接受NPC(计算下一条指令的地址)发给他的地址,还是让NPC给它一个地址和一堆信息,让它自己判断这个地址的正确与否,以及如果地址错误还要自己计算一个正确的地址?答案显然是前一个。因为假如我们的地址是错的,我们只需要检查NPC计算的地址是否是正确的,而PC只需要拿到地址从内存取指令就可以了,地址的正确与否和它无关。但假如我们改成了后一个,当地址出现错误,我们就无从得知是NPC计算的地址是错的,还是PC拿到错误地址后再计算的地址仍然是错的(或者PC的判断完全是错的,NPC明明给的正确地址,但PC以为是错的),这样我们就要检查两个部件,这就是一个糟糕的架构。好的架构一定要满足“高内聚低耦合”。

再谈oo方法的理解。面向对象设计语言(不仅仅是java,还有c++,python等)的三个基本特征是封装,继承以及多态。封装我在前面已经提过,继承本身我用的不是很熟练,再加上代码风格本身不是很支持使用protected,即便继承了,也只能继承方法无法继承属性。但类似封装特点,“继承”的根本目的绝不是为了仅仅少那么几行代码,更多的是将父类的很多细节封装起来,只对子类保留他想要保留的接口,而同样,在使用子类时,可以认为父类所有提供的接口都是正确的(或者根据所需对父类方法进行重写)。一个良好的代码,一定不要让类似的代码反复重复出现,这样维护起来极其困难,而且需要修改时,需要改很多地方。oo语言的继承特性,让父类为所有继承他的子类封装了一个模板。
(而多态我就更加陌生了,在此就不误导读者了。定义如下:“多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。多态成立的另一个条件是在创建子类时候必须使用父类new子类的方式。”

总结自己在四个单元中测试理解与实践的演进

(我在oo课程中最大的遗憾就是到最后也没学会如何自动生成数据)测试无非就要做到:全面性和压力性。压力性就是搞一些边缘的数据,比如第四单元数据限制200条指令,那就扔进去250条(多扔一些是为了保险),看看程序会不会超时或者卡爆掉,而不是简简单单跑对样例就可以了,因为样例实在是弱得很。
全面性就比较难搞了。全面性就是常说的“恶心的数据”,但怎么让数据“足够恶心”,是计算机决定的,而不是人决定的。首先是各个特殊情况是否需要判断,比如第三单元有一个限制“群聊人数不能超过1111”,那测试中就一定要包含往一个群聊塞1112人,看第1112人能不能加进去这种。这种还相对简单,最难的是程序会不会在各种奇奇怪怪的情况中出现奇奇怪怪的bug,比如第二单元我那个横向电梯的“活塞运动”,20个强测点都没有测出来,这时候就需要“鸟枪数据”,无差别打击,看看是否哪个数据就会hack到。本人就是吃了不会自动生成的亏,每次用于测试的数据都少的可怜,然后在强测和互测中疯狂出bug(下次一定)
不过这里还是给像我这样不会自动生成鸟枪数据的提一些自己的看法:第一,要考虑时间复杂度,像出现二重循环,甚至三重循环的地方一定要多堆数据,(比如第三次的qgvs),即便出现了解决方案,也要重点关注解决方案会不会有漏洞(比如很多人为了解决qgvs,在group类定了一个属性,存放valueSum,这本没有错,但他们的解决方案是每次出现诸如addRelation,addToGroup这种就重新计算一遍valueSum,那如果我atg和qgvs交叉扔呢?)第二,假如你写的时候不是百分之百有把握,就一定要反复测试反复检查。还拿我第二单元的活塞举例,虽然写完后觉得自己想法很nb,但心里也考虑过会不会出现“活塞”的情况。只不过当时犯懒,没对其精准打击罢了,现在属实后悔莫及。第三,要善于利用IDEA的警告,比如IDEA报可能出现空指针,这时不要assert其不等于null,而是对此反复测试,看看是否真的会有空指针。(但最根本的方法,还是自己生成数据覆盖测试,有条件的还可以找自己的朋友/男女朋友对拍)
另外,像三四单元之间各个方法耦合度较低的,还可以考虑“单元测试”,思想便是前面提到的“封装”思想,保证每个模块能完成自己的任务,出了问题就可以放心“甩锅”。
最后,鲁迅曾说过,测试只能发现问题,而不能证明程序没有问题。无论多么经验丰富的程序员,也难保证自己的程序完美无缺。但最根本的,还是要及时摒弃“样例过了就算过”的错误思想。

总结自己的课程收获

又到了万众瞩目的总结(致谢)环节。首先,这门课让我的编程能力有了极大的提升,大二上一学期的HDL让我几乎失去了软件编程的能力,这学期几千行的java代码又把我打醒回来了。而且,这门课设计的很多环节,在当时看起来很不合理,但回过来想,都是在模拟未来真正实战我们要面临的种种环境(少量的测试数据,更多的是我们未知的,甚至是需要自己构造出来的数据,并且我们的程序在压力测试下能否正常运行以及运行能否得到正确结果,是没有评测机实时反馈的,更多的要靠用户去一点点发现bug),以及模块化编程(第三四单元,如何理解一个已经几乎完成的代码结构,并利用现成的东西完成所需要填写的类或方法)。这门课也让我第一次有了一点点入门的感觉,包括代码风格工具(这一习惯甚至让我带到了os中,即便是智能性像个弱智的跳板机,我也能尽量维护我的C代码风格),还有git的使用(虽然我学oo课前从未使用过git,开始使用时因为不懂基本原理经常出问题而倍感恼火,但当自己的代码真正出现版本错乱,或者想恢复到原来的版本时,才发现git有多好用),以及很多好用的网站(包括Github,StackOverflow等),而不是出了问题就去csdn和百度(csdn东西用着虽方便,但上面的文章质量很参差,答主的水平也良莠不齐,所以出问题在csdn上几乎解决不了,百度就更不必说了,懂得都懂)虽然现在我们都不能称作是一个合格的程序员,但有了这些工具的使用,至少是有了入门的资格。
最最重要的,便是面向对象设计的能力。oo其实是随着软件的发展,更加迎合用户需求的编程思想,这其中很多的规律,使其比面向过程的语言(C等)更贴合人类。现在很多游戏都更倾向于用C++而不是C,除了C++有更灵活的语法外,更多的是C++的oo特性,让其设计起来更容易理解。譬如,一款十分经典的游戏“植物大战僵尸”,如果用oo来写,可以为每个植物都建一个类,属性可以包括“需要多少阳光,冷却时间,生命(需要僵尸啃多少下)等”,方法可以有“生产阳光,攻击,防御”等,而僵尸可以包括“速度,生命,工具,啃植物,死亡等”。这也符合玩家的基本思维,即见到一个新的植物先关注其需要多少阳光,是干什么的。而一些不需要暴露的细节(比如攻击植物的攻击速度),就直接隐藏起来了。倘若用面向过程的语言实现,就感到无从下手了。这门课的收获,总结一句话便是“万物皆可封装成类”,有需要的暴露接口(外部属性),不能暴露的在内部实现(内部属性)。老师第二节就讲过的话,我直到现在才理解。

立足于自己的体会给课程提三个具体的改进建议

泪目,居然有吐槽的机会那就随心所欲了(bushi

  1. 指导书,指导书,还是指导书。指导书就是我们的命根子,所以希望能写的更详细一些。虽然我知道能来6系的大多都是大佬,甚至很多人以前就接触过java,但这种模式对我这种没学过java理解力还极差的小白实在是不友好,特别是最开始git的使用那个地方,刚开始用属实是一脸懵x,后来是看了os的指导书,才对git有一点初步的理解。除此以外还有一些很困难的部分,比如多线程部分,还是希望指导书上能多多给指导,让我们这些小白能学的不那么沧桑。
  2. 有亿点点理论和实际编程有差距的感jio。虽然上课所讲的内容不仅仅是为了让我们写出某一次作业,而是让我们从俯视的视角来看oo思想,从根本上理解,但对于初学者来说,这些理论有些太过高深,就像我刚刚学会了加减乘除,要给我讲微积分一样,跨度很大。我不反对从高瞻远瞩的视角学习oo,但我认为这是一个循序渐进的过程,要先从理解oo的目的,oo与面向过程的区别等基础讲起,再逐步提升格局,否则理解起来会很困难,甚至出现不知所云的状况。
  3. 这点也是参考os)建议增加每周的线下答疑,线下交流的效果毕竟比在微信上好很多(就这样)
    总而言之,还是感谢老师和助教大大一学期的辛勤付出,为我们带来了这样一门体验感俱佳的课程。最后,祝我们的oo课程越办越好!!!
posted @ 2022-06-26 16:03  YiWforever  阅读(20)  评论(0编辑  收藏  举报