概述
经历了三次oo作业的洗礼,让我对java语言的强大以及面向对象编程有了初步的理解(当然,我是小白)。本文接下来就将对自己这三次作业的代码进行分析以及分享自己的心路历程。
基础知识点考核
针对前三次作业,我列出了这其中涉及到的主要的知识点:
- java基础语法
- java正则表达式(注意不要爆栈)
- 对java封装、继承、多态、接口的理解
- 程序的构思能力
当然,还有最重要的还是阅读和理解指导书的能力,能够在较短的时间内准确无误的理解指导书的意图也是必需的能力之一。
程序结构分析
作业一:多项式计算
程序类图如下:
这是我第一个认真写的java程序。由于第一次作业比较简单,所以用到的类也只有三个,类图也很简单。尽管并不是纯翻译的c代码,但自从看了很多大佬的代码就愈发开始嫌弃起来。尤其是输入的检测部分,可能是脑抽了吧写了个字符状态机,这样的后果是一个明明非常简洁的程序看上去非常的臃肿,好处是它可以遍历几乎所有的情况。然后是主体部分,类的设计是源自老师的PPT,而且主体方法和其他同学的几乎都差不多,由于第一次作业较为简单,具体细节就不再赘述。但这次作业也让我对java有了较为基础的认识,对比c代码和java代码就可以看出来,很多功能用c实现起来或许很难,但用java却可以在很短的代码内写出较为高效的程序(也可能是我对c的理解不够吧);至于对面向对象的理解,此时还停留在朦胧的阶段,认为面向对象编程就是构造一些对象,然后通过对象来调用它们内部的方法。比如我第一次作业的main函数的内容是这样的:
public static void main(String[] args) { Scanner scn = new Scanner(System.in); if (!scn.hasNextLine()) {System.out.println("ERROR"); System.out.print("#输入不能为空,请重新输入"); scn.close(); return;} String str = scn.nextLine(); if (str.length() != 0) { str = str.replace(" ", ""); new ComputePoly(str); } else { System.out.println("ERROR"); System.out.println("#输入不能为空,请重新输入"); } scn.close(); }
而ComputePoly()类是这样构造的:
1 ComputePoly(String str) { 2 u = v = L = state = 0; 3 poly = new Poly[51]; 4 for (int i = 0; i < 51; i ++) poly[i] = new Poly(); 5 err = 0; cnt = 0; err1 = 0; 6 parseString(str); 7 compute(); 8 }
其实就是一层一层调用其他对象。这是我对面向对象编程的一开始的理解。
作业二:傻瓜调度电梯
程序类图:
第二次作业要难一些,因为我花在指导书上的时间就差不多有接近一天,而且还是在室友以及助教的帮助下才勉强看懂这次作业的需求。我的思路是在主类中新建一个Ctrol对象,然后在Ctrol的构造函数中调用其他对象的方法(如上类图所示)。这次作业和上次作业不一样,因为合法的输入很短,所以可以直接使用正则表达式匹配而不用考虑爆栈问题。在程序的设计中,比较难或者比较模糊的一点是同质指令的判断,我自己写版本是这样的(虽然是对的但很冗余):
1 for (int i = 1; i < req.get_size(); i ++) { 2 r2 = req.get(i); 3 if (r1.issame(r2)) { 4 if (r1.get_kind() == 0) { 5 if (r2.get_tim() - r1.get_tim() <= 1 ||r2.get_tim() <= lift.get_all_tim() ) r2.set_ok(false); 6 } 7 else { 8 if (r1.get_tim() == r2.get_tim() || r2.get_tim() <= lift.get_all_tim()) r2.set_ok(false);// 9 } 10 11 } 12 }
后来在室友的提点下(暴露智商了),第三次作业改成了这样:
1 for (int i = 0; i < req.get_size(); i ++) 2 if (!solve[i] && re.issame(req.get(i)) && req.get(i).get_tim() <= lift.get_all_tim()) 3 {System.out.println("#SAME[" + toReq(req.get(i)) + "]"); solve[i] = true;}
即只需要一个判断条件就足够;
程序的主体框架和第一次作业类似(大概我对面向对象的理解还停留在原地吧),main函数内容如下:
1 public class Main { 2 3 public static void main(String[] args) { 4 new Ctrol(); 5 } 6 }
另外与第一次作业不同的是,此次作业的属性都为private类型(不考虑继承),此外我还对所有属性专门设置了set()方法和get()方法,但是后来又觉得此举不妥:为每个属性都设置了访问方法,这样和将这个属性设置为public有什么区别呢?所以在矛盾的驱使下,我在第三次作业进行了稍微的改动:只留部分属性与外界交换,其他的属性则保持其基本特征不变。
作业三:ALS调度电梯
程序类图:
关于此次作业的设计的话和第二次没什么区别,不再赘述。本次作业的难点应该在于指导书的理解,而好在评论区有很多同学提出的疑问以及助教的回答(在这里感谢一下各位提问题的同学和回答问题的助教筒子们先),所以看完指导书一头雾水的我在这些问题的帮助下顺利的理解的作业要求,于是高兴的认为这次作业应该是没问题了。可到了真正实现的时候却发现没那么简单,后来更换了数据结构(我一开始使用的是queue),改成了ArrayList,这才算勉强解决了这一问题。当然,此次作业也有很大的不足,就是单纯的为了实现功能和完成任务而牺牲了很多代码的可读性和可维护性——很多功能都写在了一个方法里面,不方便日后理清逻辑,此处我正在想办法改进。
程序bug分析
对于前三次作业,幸运的是我的公测都没有出错,互测在第三次作业时被扣了一个点。
作业一:多项式计算
第一次我拿到的是一份面向过程的代码,除了用的是java的语法罢了,代码只有一个类只有一个方法。这份代码公测错了一个点,原因是在匹配输入格式的时候采用了正则表达式但是没有考虑爆栈的问题。我测出了他的两个bug,一是因为该同学没有考虑第一个多项式前面符号为负的情况,而是直接默认为正;还有一个是因为他在处理“-00”这种情况的时候考虑不是很周全,只考虑了一个0。
作业二:傻瓜调度电梯
有了第一次给别人debug(找bug)的经验,这次我拿到代码的时候首先就看了他的readme。但是看了半天却没有什么收获,大概是因为这才第二次所以作业坑点不多吧,然后我就看了他的代码,感觉这是一位代码风格很好但是设计可能不是很周全的同学的作品。虽然我找了他四个错误(报的两个ERROR,两个incomplete),但他的代码格式却给我很清爽的感觉——各个类层次分明,变量名简单易懂,所以很轻松的,我就找到了他的bug(^_^)。尽管找了他的bug,但是我还是感觉那位同学应该是由于设计上的问题才导致了这些bug,他的bug依次为:
- 没有过滤空格(对指导书内容没有完全理解)
- 数组越界(对指导书的理解有误),
- 同质指令的处理(对指导书的理解有误)
- 最多处理99条指令(对助教通知理解有误,助教通知的是不包含RUN指令最多100条)
所以,我测试的这位同学如果当初花在指导书上的时间再多一点的话那这些bug应该是可以避免的。
作业三:ALS电梯
和第二次作业一样,拿到代码我首先看的是他的readme,看完readme我就知道这次应该是找不出代码的——这份代码设计很严密,很多输入输出的限制我连想都没想过(在这里先感谢一下测我代码的那位同学吧),接下来我用了自己的、舍友的、同学的测试数据“轮番轰炸”,但都没有找出bug,索性放弃了。这一次我自己的代码却犯了一个错误——第一条指令匹配的时候忘记加‘+’,所以如果第一条指令的时间前有正号的话就会报错。互测结束后我也反思了一下为什么会犯这种错误,结论还是设计上的问题,因为很多问题如果在设计的时候都没想到的话,写出的代码错误的概率是很大的。
如何测试别人的程序
写到这里,我来总结一下如何测试别人的程序。
首先肯定是看一下对方的readme,看一下对方的实现方式以及看对方在重要的一些点上有没有考虑周全并作记录;
其次呢是将对方的程序在自己用自己的测试数据跑一下看有没有错误的地方;
最后再看一遍对方的readme,看他所提到的功能是不是都实现了以及实现方式是不是有错误,构造专门的测试数据进行测试。
总结
首先感慨一下各位助教姐姐真的都好好吖,感觉我们写oo花了多长时间她们就陪了我们多长时间。
然后是设计真的很重要!!!有了好的设计,真正来完成代码的时候可能只需要一两个小时就搞定了。
最后,好的代码风格也是优质程序的关键,所以,千万不能以节约时间为理由而牺牲代码的可读性,因为你永远不会知道为节约这点时间你会
付出怎样的代价。