单元小结

Posted buaawang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单元小结相关的知识,希望对你有一定的参考价值。

经历过OO作业的考验与摧残,在此分享自己的心得与感悟。

这三次作业分别为:多项式加减运算、傻瓜电梯调度、ALS电梯调度。

一、多项式加减运算

这是熟悉面向对象编程思想的第一次实战作业,也是我第一次接触JAVA编程,虽然在编程中学习的过程是痛苦的,但那种所学即所得的欣喜也是不言而喻的。在实际编程过程中,相信大家均深有体会:对于多项式加减的计算并不是太过复杂,该作业的难点在于如何保证程序的鲁棒性。一款程序的设计需要考虑到方方面面的因素,站在用户群体的角度思考:如果我是用户,我会有怎样的行为。用户的输入多种多样,五花八门,即使你在说明书中明确规定程序应该接受怎样的正确输入,可也难以保证用户的误输入。因此,程序的鲁棒性至关重要。

对于结构化的字符串输入,其格式检查有两种常见的做法,其一是有限状态机,其二是正则表达式。由于有限状态机需要设计状态转移图,较为复杂,而正则表达式只需要了解输入的结构,因此,在本次作业中,我采用了正则表达式来匹配文本。这是我第一次的正则表达式:

1 private String charPattern="[[0-9]\\\\+\\\\-\\\\(\\\\)\\\\{\\\\}\\\\,]+";
2 //private String numPattern="([0-9]+)";
3 private static final String numPattern="\\\\([+-]?\\\\d{1,6}\\\\,([+-]?[0]{1,6}|[+]?\\\\d{1,6})\\\\)";
4 private static final String parPattern=String.format("(%s(\\\\,%s){0,49})", numPattern,numPattern);
5 private static final String curPattern=String.format("([+-]{1}\\\\{%s\\\\}){1,20}?", parPattern);
6     

其思路是先检查是否有非法字符,接着检查格式,程序针对不同的输入给出相应的响应。其中,格式检查这一步刚开始遇到了不小的问题。第一次匹配时,我采用的是直接整体匹配

Pattern.matches(curPattern,str)

这种方法的好处是可以直接否定掉不符合标准格式的非法输入,但是缺点也显而易见,程序无法给出确定的详细信息,即用户可能会一脸懵逼的无法得知输入错在哪里,显然是不友好的。并且这种匹配方法有一个重大缺陷:当待匹配字符串很大时,比如这次作业的压力测试,程序会CRASH,这应该是与JAVA正则匹配机制的影响。所以这种直接整体匹配的方式是无法胜任输入检查这一工作的。经过重新思考之后,我采用了以下方式:

对多项式进行一个一个的匹配,每匹配到一个多项式,便把它串接到临时字符串的末尾,等到匹配检查结束后,临时字符串中保留了输入字符串中所有正确的多项式输入。最后将临时字符串与输入字符串进行比较,如果两者相等,显然输入是合法的,否则,输入就是不合法的。

这样就完美解决了JAVA不能一次匹配较大文本的缺陷。事情到这里就完美解决了吗?显然没有,之前就说过,这种匹配虽然可以排除所有非法的格式输入,但是无法给出出错的具体信息,原因便是正则表达式太过具体,这就相当于一扇紧闭的门,一开始就把所有非法的请求拒之门外,门内的“人”是不可能得知具体的信息的。因此,经过重写之后的正则如下

1 //小括号模式
2 private String par_pattern="(\\\\([+-]?\\\\d{1,6},(\\\\+?\\\\d{1,6}|[+-]?0{1,6})\\\\))";
3 //大括号模式
4 private String cur_pattern="(\\\\{.+?\\\\})";

很简单对不对,尤其是大括号模式,对于大括号模式而言,只检测成对的大括号,对于大括号里面的内容是什么并不关注,这一工作交由小括号模式去做,所有在第一阶段被漏掉的非法格式在第二阶段均被检测出来。这种二级匹配是可以做到具体到错误信息的,但相应的代码逻辑部分会大大增加,为了实现检测错误信息的功能,程序用到了分组的概念,以及匹配的起始位置,根据这些信息来对输入字符串里里外外的进行详细的检查,一旦遇到非法信息,程序打印出错误信息并退出。这种方式是我在互测阶段阅读一位大佬的代码时发现的,当时的我是无比赞叹的,所以自己也实现了一下,那种感觉很美妙,下面是部分代码。

 1 while(cur_m.find() && cur_cnt<=20)
 2         {
 3             if(cur_m.start()!=cur_end)
 4                 expHander(cur_m.group(1).substring(cur_end, cur_m.start()-1));
 5             if(cur_m.end()<str.length() && ! ( str.charAt( cur_m.end() )==‘+‘ ||  str.charAt( cur_m.end() )==‘-‘ ) )
 6                 expHander(str.substring(cur_m.end(),cur_m.end()+1));
 7             cur_cnt++;
 8             cur_end=cur_m.end()+1;
 9             par_end=1;
10             
11             Matcher par_m=par.matcher(cur_m.group(1));
12             while(par_m.find() && par_cnt<=50)
13             {
14                 if(par_m.start()!=par_end)
15                     expHander(cur_m.group(1).substring(par_end, par_m.start()-1));
16                 if(par_m.end()+1<cur_m.group(1).length() && !(cur_m.group(1).charAt(par_m.end()) ==‘,‘))
17                     expHander(cur_m.group(1).substring(par_m.end(),par_m.end()+1));
18                 par_cnt++;
19                 par_end=par_m.end()+1;
20                 
21             }
22             if(par_end != cur_m.group(1).length() || par_cnt>50)
23                 expHander(cur_m.group(1).substring(par_end-1));
24         }
25         if((str.length()+1)!=cur_end || cur_cnt>20)
26             expHander(str.substring(cur_end-1));

 

第一次作业出现的唯一BUG就是没有考虑到空输入时程序的相应,从而导致程序CRASH,其实,只要增加一行catch便可以完美解决,不过总体来讲第一次编程体验还是挺美妙的,既熟悉了面向对象编程,同时也了解了JAVA语言,这种学习与实践的交互过程还是挺令人享受的。

下面附上程序类图以及代码统计

第一次作业的功能较为简单,类功能相对较为完整,在这里,主类PolyComputer的功能时实例化对象并执行相应的方法,InputHander类把输入及检查独立出来进行处理,PolyManager类则用来管理多项式类同时进行多项式的加减运算,Poly类则用来构造多项式对象并交由PolyManager类来管理,其中,InputHander类和PolyManager类参考了课件的推荐设计。

 

技术分享图片

第一次作业的代码统计如下,可以看出,程序的“圈复杂度”较高,这与正则匹配是密不可分的,程序考虑的输入情况越复杂,相应的圈复杂度一般也越高。同时,对于Method Line of Code这一指标,PolyManager类较为复杂,可以将其功能限制一下,比如将多项式加减分配到一个多项式运算类中,这样,程序的功能就会被均衡的分配到各个类中,各个类协同完成多项式运算。

技术分享图片

 

二、傻瓜电梯

如果说第一次作业是一次令人愉悦的编程体验,那么第二次作业便令人苦恼了。相比较第一次作业,第二次作业侧重于如何合理安排各个类的功能,避免出现两种极端情况:“上帝”类和“傻子”类。在开始编程时,由于对时间线的概念不清晰,虽然最后成功实现了作业要求,但是程序代码逻辑混乱,冗余代码过多,类功能分配不均,类功能不明确。这样的做法甚至导致我本人阅读代码时都需要停下来稍微思考下,理一下思路,然后才能接着阅读,是不是很荒唐,更不要说阅读代码的其他人了。在这里就不展示初次编程时的代码了。。。

本次作业的难点在于如何处理同质请求,我采用以下方法来解决。

楼梯类与电梯类维护“点灯”与“灭灯”,当拿到一条请求时,首先判断该请求是不是同质请求,如果此时对应楼层的“灯”正在亮,则表示是同质请求,否则执行该请求并“点灯”

一部分代码如下。

 1 //电梯方法
 2 public void geton(int n,double sys_time)
 3     {
 4         Ele[n]=sys_time;
 5     }
 6     
 7     public double getoff(int n)
 8     {
 9         return Ele[n];
10     }
11 //楼层方法
12 public void geton(int n,String dire,double sys_time)
13     {
14         if(dire.compareTo("UP")==0)
15             FloUp[n]=sys_time;
16         else
17             FloDown[n]=sys_time;
18     }
19     
20     public double getoff(int n,String dire)
21     {
22         if(dire.compareTo("UP")==0)
23             return FloUp[n];
24         else
25             return FloDown[n];
26     }

 

参考课件的推荐设计,加上自己的思考,重写之后的程序新增一条时间线来模拟时间的流逝,楼梯类与电梯类重写了“点灯”和“灭灯”方法,同时对调度器类进行了简化,代码逻辑更加清晰,类功能分配较为均匀,多个类协作完成电梯的上行、下行,总体是比较合理的,关键是代码的可读性更高了。。。

附上类图与统计

主类ElevatorSys和InputHander类参考课件的设计,功能同前所述。Request类作为Floor和Elevator类的主类,用于维护输入请求的基本属性与方法,Floor和Elevator类在Request类的基础上,增加了独有的方法和属性,用于完善输入请求并和其他类进行交互。RequestList类则维护一个请求队列,具备队列的基本方法:入队和出队,同时支持获取指定元素。Scheduler类则用于调度电梯,处理请求队列,要么相应请求,要么判断同质请求,这里同质请求的判断综合了Floor类、Elevator类里的“灯”属性。

技术分享图片

通过代码统计图可以看出,本次作业的类功能分配较为合理,基本能保证类之间有很好的交互性,因为每个类只维护自己的属性和方法,因此,它们需要为实现统一功能共同协作。

技术分享图片

三、ALS电梯

本次作业是在第二次作业的基础上,增加了捎带功能,即在电梯前进的方向上,尽可能的捎带请求,从而缩短电梯的响应时间。初次尝试,完全是重写了之前的Scheduler类,换句话说,就是仅仅为了满足指导书要求为了继承而继承,功能基本由一个类单独完成,有一些类被闲置在旁。修改后的代码重载父类的方法,用于寻找可以捎带的请求,接着,由父类来寻找下一个未完成的请求。子类与父类协同进行请求的处理。

本次作业的难点在于如何寻找下一个捎带请求,我的做法如下:

模拟真实的电梯运行,即从当前楼层出发,一层一层向目标楼层前进,每上行或下行一层,遍历请求队列,如果待响应请求的请求时间小于当前的系统时间,则将该请求作为捎带请求处理,如此反复

在这个过程中,同时寻找下一个主请求,并返回主请求在队列中的位置,用于下一轮的执行。

附上类图与统计

相比较第二次作业,本次作业新增了一个继承类ALS_Scheduler类,父类为SCheduler类。ALS_Scheduler类用于处理捎带请求,同时与父类共同寻找下一个主请求。其他类的功能与第二次作业基本相同。

技术分享图片

ALS_Scheduler类的功能较为复杂,其实也可以理解,毕竟要从请求队列中动态的寻找捎带请求。其他的基本与第二次作业相同。

技术分享图片

 

四、总结

在重写三次作业的代码时,我发现一个共性的问题是初次尝试基本能完成作业要求,但是逻辑混乱,代码冗余,类功能不明确,没有很好地自包性,而经过重新构思之后的代码显然是基本避免了之前的问题。因此,这也给了我关于如何写出质量更高的代码的启示:

在着手写代码之前构思清楚,或者在实现程序功能的基础上重写第二遍

当然,第一种方法是高效的,但对我而言,大多数时候思路是在码代码的过程中逐渐被唤醒的,因此在现阶段我可能会更加倾向于第二种方法,不过会逐渐锻炼自己的逻辑能力,逐渐向第一种方法过渡。

以上是关于单元小结的主要内容,如果未能解决你的问题,请参考以下文章

第3章 寄存器(内存访问)小结

华为实习小结

AngularJS模块——理解小结

单元小结

单元测试 NPE,当我添加片段自定义转换时

数据结构 第6章 图 单元小结