目录
2.4 自定一个新的迭代情景,并说明你的最终设计在这个新迭代情景上的可扩展性
3.2.1 对比分析出现了bug的方法和未出现bug的方法在代码行和圈复杂度上的差异
1 基于度量来分析自己的程序结构
1.1度量分析
由于三次架构没有做出过大的调整与重构,本次分析主要基于比较全面的第三次作业代码。
1.1.1 类的属性个数、方法个数、类总代码规模
类 | 属性个数 | 方法个数 | 类 | 属性个数 | 方法个数 |
---|---|---|---|---|---|
MainClass | 0 | 1 | DeFactor | 1 | 3 |
Lexer | 4 | 7 | ExponentialFactor | 2 | 3 |
Parser | 2 | 4 | FuncFactor | 2 | 3 |
PreProcess | 0 | 1 | FuncDefine | 2 | 2 |
NumberFactor | 1 | 4 | Mono | 3 | 11 |
VariableFactor | 1 | 4 | Poly | 2 | 10 |
ExprFactor | 2 | 3 | Token | 2 | 3 |
Term | 2 | 6 | Expr | 1 | 4 |
从分析结果可知,代码规模最多的类有Mono类(219行),Poly类(168行),Lexer类(128行),Parser类(115行)。其中Mono(单项式类)和Poly(多项式类)作为表达式最基本形式,其中包含大量的计算方法、求导方法以及转化为字符串方法,因此行数较多。
其次就是Lexer类以及Parser类,二者作为递归下降的表达式解析的功能类,其中包含表达式词法分析、语法分析解析方法,面向过程性较强,故代码规模较大。
还有一个值得注意的就是自定义函数的处理类(FuncDefine),其中包含两个功能性方法,一是自定义函数的定义,即将读出的自定义函数转化为字符串按照函数名分别储存起来;二是自定义函数的展开,即在解析表达式阶段,将自定义函数因子带入具体的参数转化为纯粹意义上的因子,存储为FuncFactor。这两个方法面向过程行比较明显,
其他类作为数据结构,例如表达式类、项类、因子类等,都是作为面向对象的类,包含的方法不太涉及功能性,故代码规模较小。
总体来说,各个类的代码规模与其本身的面向对象/面向过程特性有很强的关联。
1.1.2 方法规模、控制分支数目与复杂度分析
参数解释(来自网络)
CogC: Cognitive complexity,认知复杂度,用来衡量代码被阅读和理解时的复杂程度。每种控制结构的使用都会增加认知复杂性,而且嵌套的控制结构越多,认知复杂性就越高。
ev(G): Essential complexity,基本复杂度,用来衡量程序非结构化程度,ev(G)高意味着非结构化程度高,难以模块化和维护。
iv(G): Design complexity,设计复杂度,与方法控制流与对其他方法的调用之间的相互联系有关,也代表了将方法与其调用的方法进行集成所需的最少测试次数。
v(G): Cyclomatic Complexity,圈复杂度,是通过每个方法的不同执行路径数量的度量,可以看作是完全执行方法控制流所需的最少测试次数。圈复杂度 = 1+ if、while、for、do、switch cases、catch、条件表达式、&&'和||的数目。
很明显看出来我Mono类中的toString方法复杂度显著大于其他方法。这个方法用于将单项式即
转化为字符串。
为了实现最简化,我傻瓜式地分类讨论,系数是否为0?指数是否为1?为0?exp的底数是否为0?面向过程化过于严重,这是程序需要优化的一个点。
同理,在Mono类的求导toDePoly方法的复杂度也很高,我同样是傻瓜式分类讨论。其他标红的方法有语法分析类的ParseFactor()方法,Mono中实现exp()字符串转化的方法等。 我们同样能得到上一节的结论,即面向过程性强的方法通常复杂度较高。
1.2 分析类的内聚和相互间的耦合情况
参数解释(来源网络)
OCavg: Average operation complexity,计算每个类中所有非抽象方法的平均圈复杂度。继承的方法不计算在内。
OCmax: Maximum operation Complexity,计算每个类中非抽象方法的最大圈复杂度。继承的方法不计算在内。
WMC: Weighted method complexity,计算每个类中方法的圈复杂度总和。
1.2.1 内聚性分析
可以看出各个类的复杂度与我们之前的分析基本一致,FuncDefine类、Mono类、Poly类、Parser类以及Lexer类的复杂度较高,说明这些类内的内聚性较高。
其中FuncDefine类、Parser类以及Lexer类都是面向过程性的类,其中的方法多为功能性方法,且通过分类讨论解析表达式,故复杂度较高,类内的过程内聚性高。
Mono类和Poly类中包含计算、字符串转化、求导方法,后两者在上述的说明可知,我都是用傻瓜思维对其分类讨论,复杂度必然是高的,同样是说明类的内聚性高。
1.2.2 耦合度分析
由我的UML类图(见下图),可以明显看到,解析表达式过程中的Lexer、Parser以及Token类之间紧密相连,层层相关,耦合度高。
数据结构部分,Factor作为接口,与所有的Factor因子类相关联,其次,Term由Factor构成,Expr由Term构成,再者,因子部分很多类又包含表达式Expr,故可看出这些数据结构也是错综复杂,关联性强,故耦合度高。
计算与输出部分的Mono类与Poly类中,Mono中包含一个Poly属性,Poly又有Mono构成,二者紧密联系,耦合度高。
由此可以分析,不同模块里的类耦合度较高,在不同模块之间也是相互联系的。
1.3 架构
1.3.1 第一次作业
在第一次作业中,我的代码架构基本形成与定型,其中包括预处理阶段、递归下降阶段、表达式解析阶段、数据结构、计算阶段、字符串转换等。
-
PreProcess类:预处理阶段
将读进的字符到PreProcess类进行预处理,首先处理空白字符,使接下来的预处理更为简便,接着去前导零,最后去双符号,因为有了之前空白字符的除了,我们可以直接replaceAll,例如:
input = input.replaceAll("-\\+","-"); input = input.replaceAll("\\+-","-");
同时要注意会出现“--+”等情况,所以要replaceAll两次。
-
Token类,Lexer类与Parser类:递归下降与表达式解析
-
基本是按照oolens公众号以及实验代码改写的。在Token类中,枚举了8种token的类型。
NUM,ADD,SUB,MUL,LEFT,RIGHT,POW,VAR
其中,
POW
部分,我将^index
(即指数符号与指数)看作一个整体处理。 -
在Lexer词法分析类将表达式分为一个个token。
-
在Parser语法分析类,采用递归下降的方式解析表达式。
-
-
Factor类,Term类和Expr类:数据结构
-
Factor接口:因子,其中包含了NumFactor,ExprFactor以及VariableFactor。
-
Term类:项,将项看作各个因子的乘积,因此,在此类中用ArrayList保存项的因子,其中sign表示此项的符号(正负一),符号的判断在语法分析阶段。
-
Expr类:表达式。将表达式看作各个项的和,在此类中用ArrayList保存表达式的项。
-
Mono类:表达式最终化简结果的单项式,根据题意,最终Mono的表达形式为
其中a为系数,i为x的指数。
-
Poly类:表达式最终化简结果的多项式表达,Poly为多个Mono之和,因此在Poly类中,用ArrayList储存Mono。
-
-
Mono类和Poly类:计算
计算在Poly类中进行,其中:
-
AddOrSubMono方法:将一个Mono加入这个Poly的
ArrayList<Mono>
,其中我们要判断ArrayList中是否含有同类项,由于表达式较简单,只需判断指数是否相等。 -
AddOrSubPoly方法:在AddOrSubMono方法的基础上,进行Poly与Poly之间的加减计算。
-
MonoMultiplyMono方法:进行Mono与Mono之间的乘法,系数相乘,指数相加。
-
PolyMultiplyPoly方法:在MonoMultiplyMono方法的基础上,进行Poly之间的乘法计算。
-
PolyPower方法:在PolyMultiplyPoly方法的基础上进行Poly的乘方计算。
-
-
Mono类和Poly类:字符串的转换
这一步我们分为两个过程:
-
首先在Mono类中对单项式进行转化,根据系数是否为零,指数是否为0,1分类讨论输出。
-
然后在Poly类中对Mono.toString进行连接,最后与预处理一样,把双符号化简,同时把开头为
0+
去掉。 -
即可得到最后结果,输出。
-
1.3.2 第二次作业
在第二次作业中,作业要求新增了exp以及自定义函数部分,对应的,我在整体架构上增加了函数定义部分,函数因子,指数因子,以及对应的函数解析,指数运算等部分。
-
FuncDefine类:函数定义与参数代入
包含了两个功能方法:Define与Extend。
-
Define:将读到的函数表达式储存起来,因为函数名一定不同,所以我将函数名与函数表达式、形参联系起来,即利用
HashMap<String,String>
存储函数表达式,HashMap<String,ArrayList<String>>
存储形参列表。 -
Extend:已知函数名以及实参,就可以从HashMap获得对应的函数表达式,再将实参代替形参,这样就可以获得一个表达式的字符串。
-
-
FuncFactor类:函数因子类
其实函数因子的本质可以看作表达式因子,其中包含两个属性:func : String和expr : Expr
-
func表示表达式中的自定义函数字符串,利用FuncDefine类中的Extend方法进行扩展得到一个表达式的字符串。
-
对以上的字符串进行词法与语法分析,即可得到表达式(Expr),接着就可以利用expr的toPoly转化为多项式,与其他因子进行融合。
-
-
Exponential类:指数因子类
包含expr(Expr)和index属性,其中expr为解析后的表达式因子,index是指数。在构造器中,将exp()中的表达式字符串进行词法分析、语法分析,得到Expr类型的expr底数,这样利于之后的计算。
1.3.3 第三次作业
本次作业在上次作业的基础上增添了求导因子。在整体架构上,我在每一个数据结构类中增加了求导方法,新增了求导因子类。
-
求导因子类
-
包含一个expr(Expr)属性,代表求导因子中的表达式因子。
-
在Token中新增DE类,修改Lexer和Parser中的方法,实现对求导因子的识别。
-
1.1.4 自我评价
-
优点
-
功能模块逻辑清晰,预处理、解析表达式、计算等模块功能较为独立,利于模块测试。
-
不同模块的内聚性较强,模块内的类之间耦合性较高。
-
-
缺点
-
部分类内的方法分支过多,不利于测试,可以适当化简。
-
分支重复代码过多,需要适当提取,简化分支。
-
没有优化结果,导致性能分的缺失。
-
没有认真思考细节问题,例如,指数需要用bigInteger存储,前导零处理不当等。
-
2 架构设计体验
在第一次作业中,我代码的主体架构已经成型,在后两次作业中以添加、修改计算方法等为主,没有大规模的修改已有架构。以下便是我在每次作业的架构迭代中的一些修改与想法。
2.1 第一次作业
第一次作业中,我的主体架构已经包含了预处理部分、递归下降部分、解析表达式部分、单项式多项式的计算与转化为字符串部分。
-
预处理阶段
将读进的字符到PreProcess类进行预处理,首先处理空白字符,接着去前导零,最后去双符号。具体实现已写在上文。
-
递归下降与表达式解析
-
基本是按照oolens公众号以及实验代码改写的。在Token类中,枚举了8种token的类型。
NUM,ADD,SUB,MUL,LEFT,RIGHT,POW,VAR
其中,
POW
部分,我将^index
(即指数符号与指数)看作一个整体处理。 -
在Lexer词法分析类将表达式分为一个个token。
-
在Parser语法分析类,采用递归下降的方式解析表达式。
-
在后续的迭代中,若有新增的因子,仅需新增Token的枚举,以及在词法语法分析是多加考虑新增因子的识别与解析。
-
-
计算
计算在Poly类(多项式类)中进行,其中:
-
AddOrSubMono方法:将一个Mono加入这个Poly的
ArrayList<Mono>
,其中我们要判断ArrayList中是否含有同类项,由于表达式较简单,只需判断指数是否相等。 -
AddOrSubPoly方法:在AddOrSubMono方法的基础上,进行Poly与Poly之间的加减计算。
-
MonoMultiplyMono方法:进行Mono与Mono之间的乘法,系数相乘,指数相加。
-
PolyMultiplyPoly方法:在MonoMultiplyMono方法的基础上,进行Poly之间的乘法计算。
-
PolyPower方法:在PolyMultiplyPoly方法的基础上进行Poly的乘方计算。
-
-
字符串的转换
这一步在Mono类和Poly类中分为两步进行:
-
首先在Mono类中对单项式进行转化,根据系数是否为零,指数是否为0,1分类讨论输出。
-
然后在Poly类中对Mono.toString进行连接,最后与预处理一样,把双符号化简,同时把开头为
0+
去掉。 -
即可得到最后结果,输出。
-
2.2 第二次作业
在第二次作业中,作业要求新增了指数函数因子以及自定义函数部分,对应的,我在整体架构上增加了函数定义部分,函数因子,指数因子,以及对应的函数解析,指数运算等部分。
-
Mono类与Poly类的修改
由于引入了新的因子,需要把多项式Poly修改为为形如
单项式Mono类增添多项式Poly项以记录exp的指数表达式,即Mono类中包括了系数coe,幂函数指数index和exp指数函数中的多项式,其转化为String时为以下形式。
由于基本形式的改变,计算方法、toString等方法均要做出调整。其中遇到最大困难是单项式的合并问题、深克隆问题,在以下部分细说。
-
单项式合并问题
-
首先说明,我的Mono类中包含Poly属性,而Poly类中包含
Arraylist<Mono>
属性,二者相互嵌套。 -
最开始,采用最基本的重写Mono和Poly的equal方法,但是最后会出现情况:
exp(x)
与exp((3*x))
会被合并。出现这个问题的原因是:Mono判断可以合并的条件是x的指数相等和exp的底数相等,其中exp的底数我用Poly保存,即判断Mono中的Poly是否相等。接着就到了Poly的判断,即
x
与(3*x)
是否可以合并,前者的ArrayList中包含一个Mono因子:x,后者包含一个Mono因子:3*x,由于这两者x的指数和exp底数均相等,按照Mono的相等判断方法,二者是相等的,所以程序认为Poly是相等的,从而exp(x)
与exp((3*x))
是可以合并的。 -
针对以上的问题,我发现
x
与(3*x)
的系数不同,所以我在Poly类中增加了一个HashMap<Mono,BigInteger> monoCoe
来储存Poly中各个Mono的系数,在判断Poly是否相等时会同时比较系数,在以上出错的那一步,就不会判断Poly相等,从而不会误判exp(x)
与exp((3*x))
是可以合并的。public class Poly { private ArrayList<Mono> monos = new ArrayList<>(); private HashMap<Mono,BigInteger> monoCoe = new HashMap<>(); ... }
-
-
ToString方法修改
由于exp()的特殊性,即底数必须为因子,所以在部分非因子的情况下需要添加括号,这里我新写了一个方法判断底数是否为因子,其中使用正则表达式对特殊情况进行判断。
-
首先是变量因子,例如x,x^2等,我们用一下正则表达式判断。
String isVar = "(x)(\\^\\d)?"
-
其次是常数因子,我们用一下正则表达式判断。
String isNum = "(\\+|-)?\\d+";
-
最后是指数因子,最初我使用以下正则表达式判断:
String isExp = "(exp\\()(.)+(\\))";
即判断是否为exp(...)形式,但是,在最后的测评中出现问题了,它会将
exp(x)+exp(1)
也识别为正确的(因为这也是exp(...)形式),但是由于我对正则表达式的掌握程度不高,不知道如何构造正确的正则表达式,所以我对方法进行修改:我摒弃了
String isExp = "(exp\\()(.)+(\\))";
的判断,将表达式内第一个出现的exp()因子变为x,在exp(x)+exp(1)
例子中,即将字符串转化为x+exp(1)
,这样显然不会判断为因子。若其确实是exp()因子,则整个因子都会变成x,依然会被判断为因子。
-
-
函数嵌套处理
-
我选择在Parser语法分析阶段处理,采用的是从外到内层层递归的方法。
-
首先在词法分析中,根据括号的个数,我将最外层函数表达式的','转化为'@',例如,将
f(f(x,y),x)
转化为f(f(x,y)@x)
。 -
在Parser部分,就可以用'@'进行分割,得到外层函数的实参,这样层层嵌套递归后就可以解决函数嵌套的问题。
-
2.3 第三次作业
第三次作业在第二次作业的基础上新增了求导函数,对应的,我在整体架构增加了求导因子,以及各个因子、项、表达式、单项式以及多项式的求导方法。
-
因子、项、表达式求导
-
最基础的是因子的求导,根据数据特性,进行求导,引入
toDePoly():Poly
方法,求导后得到Poly类型的数据。 -
项求导,利用乘法法则,其基础也是因子的求导。
-
表达式求导即为各个项的求导之和,进行
PolyAddPoly
计算即可。
-
-
本次作业的最大难点在于嵌套求导如何实现?
本来未在Mono和Poly类中实现求导功能,但是对于求导因子自身的求导(即嵌套)无法实现。
因为求导因子内部的表达式求导完为Poly形式,为了实现嵌套,必须对这个Poly继续求导,所以在思考下,又在Mono和Poly类中增添了求导方法。
则在求导因子内的toDePoly方法即可写为:
public Poly toPoly() throws CloneNotSupportedException {
return expr.toDePoly();
}
public Poly toDePoly() throws CloneNotSupportedException {
return expr.toDePoly().toDePoly();
}
2.4 自定一个新的迭代情景,并说明你的最终设计在这个新迭代情景上的可扩展性
-
若有新增的因子,以去年作业中的sin,cos为例,其实处理起来和指数因子类似。首先要在Token中新增SIN,COS类型,再对Lexer和Parser做出相应的修改。新增SinFactor和CosFactor,其中的属性也为表达式,与指数因子类似,接着就是修改Mono和Poly的属性、计算方法、字符串转化方法等。
-
若有多个变量,也是对Token、Lexer、Parser作出对应的修改,同时在VariableFactor类中新增name属性,来判断多变量的名字,同时要修改Mono和Poly的属性、计算方法以及字符串的转化方法等。
-
可以发现,我的架构在新的迭代中也具有可扩展性。
3 分析自己程序的bug
3.1三次作业的Bug分析
3.1.1 第一次作业
-
前导零问题
判断失误,很简单的判断如果当前位为0,后一位为数字,则去零,直接导致了100变成10,103841变为13841,这是非常严重的bug,导致在第一次强测和互测都被hack了。
后来发现,去前导零简直多此一举,所以我索性将其删除,因为在读数字阶段,字符串转化为数字,前导零会自动被消除。
3.1.2 第二次作业
-
深克隆问题
在本地测评时,发现了深浅克隆问题,Mono在运算过程中没有深克隆,导致其系数、底数的改变,所以在Mono类和Poly类中增加了深克隆方法。
-
toString正则表达式
对于指数因子,最初我使用以下正则表达式判断:
String isExp = "(exp\\()(.)+(\\))";
即判断是否为exp(...)形式,但是,在最后的测评中出现问题了,它会将
exp(x)+exp(1)
也识别为正确的,所以我对方法进行修改:我摒弃了
String isExp = "(exp\\()(.)+(\\))";
的判断,将表达式内第一个出现的exp()因子变为x,在exp(x)+exp(1)
例子中,即将字符串转化为x+exp(1)
,这样显然不会判断为因子。若其确实是exp()因子,则整个因子都会变成x,依然会被判断为因子。 -
函数代入扩展中的exp
本地测评时,exp()中也包含x,在函数实参替换形参的过程中,要避免exp的x被替换。所以首先将exp都转化为eap,在进行替换,最后再将eap还原为exp。
-
Mono和Poly的equal方法
最开始,采用最基本的重写Mono和Poly的equal方法,本地测评是出现:
exp(x)
与exp((3*x))
会被合并。(具体原因见2.2部分)所以我在Poly类中增加了一个
HashMap<Mono,coe>
来储存Poly中各个Mono的系数,在判断Poly是否相等时会同时比较系数,在以上出错的那一步,就不会判断Poly相等,从而不会误判exp(x)
与exp((3*x))
是可以合并的。 -
指数问题
强测中的
(((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8
由于我的指数用int存储而爆了。😢 所以我将所有的index都改为BigInteger,在Bug修复阶段又出现了以下问题。 -
未重新修改equal方法
将所有的index都改为BigInteger后,Mono中的index同样也被修改为BigInteger,但是由于没有重写equal方法,又出现了问题,所以又重新生成equal方法。
3.1.3 第三次作业
-
求导求错了
是令我最痛心的!这个错误导致我强测只有50+,没有进互测屋。
在Mono求导时,系数写错了,例如
dx(exp(x^2*exp(x)))
会得到2*x*exp((x+x^2*exp(x)))+2*x^2*exp((x+x^2*exp(x)))
,对比正确答案:2*x*exp((x+x^2*exp(x)))+x^2*exp((x+x^2*exp(x)))
,发现后一个项的系数是错误的。 -
但是但是但是!!可怕的是,跑测评机没有跑出来,至少提交过十次,而且事后我又测评了十几次,均没有发现问题。所以不能太依赖测评机,要自己多构造数据,回到代码本身,多多检查。😩
3.2 分析
3.2.1 对比分析出现了bug的方法和未出现bug的方法在代码行和圈复杂度上的差异
以上Bug大多发生在这几个方法:
-
Mono类的toString方法
-
Mono类的toDePoly方法
-
FuncDefine类的extendFunc方法
图片数据来源于 1.1.2 方法规模、控制分支数目与复杂度分析
我们可以发现,这些方法的复杂度都爆红了,而且代码量大、分支多,难以进行测试。
在这些方法中,我都是傻瓜式进行分类讨论,分类的情况很细很琐碎,导致复杂度高。
3.2.2 思考可以通过怎样的方式来降低方法的复杂度
应该进一步优化分类讨论,将可以合并的情况和在一起写,尽量控制if
嵌套的情况出现。
同时,在每个分支中部分的实现代码基本一致,但是我没有提取。为了降低复杂度,我应该提取相同的方法,提高代码可读性、可维护性。
3.2.3 思考与总结
在强测与互测出现的低级错误归根结底还是自己对题目的思考不够,测试的覆盖面不够,过于依赖评测机测试。
对于一些细节,例如前导零的处理、指数的类型等,容易想当然,不进一步思考可能出现的问题,自认为不会出现某些情况,没有实际构造数据去验证。对于求导错误,还是因为写完没有再次检查,在测评机上跑了几趟就以为没有任何问题了,特别是第三次作业相比前两次作业较容易,写完就放那里不进行更细心的检查,导致疏忽。
在接下来的单元测试中,我应该更加细心,深刻理解题意,不只是停于表面。
4 分析自己发现别人程序bug所采用的策略
主要是利用同学的测评机发现Bug,对测评数据进行化简,找到出错的点。按照这个方法,成功对互测屋成员进行了几次hack。
值得反思的是,我在几次的互测中,并没有仔细阅读被测程序的代码设计结构,也是过于依赖评测机,所以在接下来的几次作业中,要认真对待互测。
5 分析自己进行的优化
权衡了优化的代价与正确性,这三次作业我选择不对结果进行优化。
6 心得体会
记得开学第一天就布置了第一次作业,有一种和寒假突然割裂的痛感。那天晚上包括第二天,都对思路、架构毫无想法,在网上找往届的博客,和同学讨论、看公众号……但是在这一个过程中,我依然是毫无头绪,最后终于在周三的下午,我在纸上整理了自己的思路,生成了最初的架构。在忙忙碌碌却毫无头绪中,我非常挣扎,一度觉得自己第一次作业就交不上去,但最终生成初版的代码,能够浅浅运行,感觉整个人都松了一口气。
第二次作业最基本的新增代码很快写出来,但是接下来就是漫长的debug过程,在本次作业,对于深克隆还有改写equal方法出现的问题,我经过很久的测评才发现问题。这一周的工作量很大,在这里非常感谢舍友们的帮助!!
第三次作业相比于前两次作业看起来是比较简单的,但是,我粗心的毛病又犯了,在Mono类的求导求错了,罪魁祸首是一个分支内的一个系数写错了,直接导致本次强测50+,甚至没有进互测屋。反思过来,就是太依赖测评机了,自以为跑了十几二十次测评机没有出现问题就是完全正确了,没有对求导步骤进行更加精细的检查。同样也说明了分支过多导致测评不完全,所以在接下来的作业中,我要简化分支,尽量不写if嵌套,写完一定要自行进行检查,不过度依赖测评机。
总的来说经过三周oo作业的洗礼,我认识到设计的重要性,全面的设计让我们编写代码的过程中思路更加清晰;认识到架构的重要性,良好的架构有利于多次的迭代;认识到测试的重要性,尽量要覆盖代码的每一个分支。同时,最重要的是,我要从之前面向过程的思维转变为面向对象的思维,关注类之间的内聚与耦合。
在接下来的作业中,我要注重测试,确保不发生这三次作业同样的低级错误,深刻理解指导书,思考特殊、极端的情况。
7 未来方向
对我个人而言,我感觉第一次作业布置后的压力非常大,特别是毫无头绪的那些天,我觉得可以在oo课上或者指导书上多讲一些相关知识或者提供一些方向,让我们梯度过渡、适应、完成作业。