前言:
这是一篇面向对象作业总结,作业内容是对多项式进行求导,一共有三个阶段,具体要求不详述,第一阶段只要求’+’连接coeff*x^pow的形式,第二次支持*连接的幂函数及三角函数,第三次则需要支持括号表达式的嵌套。本想不用编译原理所述表达式树,最后发现做成一团乱麻。
第一次作业
类图如下:
说明:第一次作业比较简易,主要是对新版eclipse和checkstyle的安装和熟悉。词法分析器为Token,可以考虑将其单例化,因为只有一个词法分析器。用Poly存储多项式的系数和幂次,在PolyDif里有着主入口,根据加号拆项,以HashMap<>存入<幂次,系数>,按求导法则进行求导,最终遍历Map以输出结果。
代码度量结果如下:
Bug:不能直接去除所有空格,因为+ 10不算数字。
0不输出,由于大整数0输出时前面没有+,导致我强测三处没过,改正方法就是判断系数是否为0,是则跳过。
还有其他,总的来说,bug存在于词法分析程序以及输出程序,因为其分支比较多。
第一次作业略述如上。
第二次作业
类图如下:
三个Minizer可以不用看,因为不支持括号表达式,所以提取公因式无效
第二次作业也没有什么优化,仅仅是系数合并。构造了一个CompTerm的类,表面一个项由系数、幂次、sin幂次、cos幂次所唯一决定,这种断言有局限性,不宜扩展,但是可以有效合并系数。
Bug: 由于多处抛出异常,所以导致最后异常没有处理,就是没有输出wrong format,但是打印函数堆栈。最终把所有异常都交给了主入口main。
度量结果如下:
这里有一个静态属性,但是不是词法分析程序,他是scanner。而词法分析器被分给了diff主控程序,就是语法分析器,这样造成的问题在第三次作业中有所体现。总之,这个类关系勉强可以应对现有需求。
第三次作业
第三次作业的类构造是有问题的,类图如下:
这个类关系图甚至比上一次还要少了,其中Diff本来是对表达式进行语法分析的一个类,它内嵌了一个词法分析器,还有两个字符串,一个是输入的表达式,一个是求导后的表达式。而对因子和项进行分析的函数调用了专门负责求导的函数。作为结果,对项和因子进行分析的两个函数getTerm和getFactor,都是用两个字符串作为返回值的,一个意味着分析到了什么样的串,一个是求导后的结果。然而我直接用Diff类本身作为其中方法getTerm和getFactor的返回值,让Diff类的功能比较模糊。Sin和cos又继承了Diff类,意味着创造它们的实例就可以得到Sin(…)和其求导结果。然而这样做使得词法分析器进行了复制和分裂,就不再是全局的词法分析器了,这样一方面占用额外内存,还会导致各个词法分析器处理到的位置是不同步的,这里有很多bug,而这样仅仅是让Sin可以独立分析其自身表达式。最后在优化时,由于Diff功能太过耦合,所以Optm不能再继承Diff,而是另外完成了一个语法分析器,代码多有类似。
问题的本质就是不想用全局变量,可以说所有类的实例都是临时开的,然后通过字符串进行互相传递,没有抽象出表达式树。可以开两个静态变量,就是词法分析器和表达式树的根节点,而不是完全依赖与语言递归的特性进行字符串处理。
度量结果如下:
可以看出,代码规模并不比第二次大多少,这是因为删了第二次中的许多额外功能。
圈复杂度还是过高,3.171,就是分支数太多,这不oo,按照树的方式重构,然后节点实现统一的接口,也许可以降圈复杂度。方法数有72个,这是因为里面大量的都是get和set方法,不能有protected就只能这样进行同步。静态方法还可以再减少,因为那3个方法只是给main用的。Static方法不是不能用,单例模式就要用,但是也不能专门造一个类把所有的方法都设置成static的了,这是底线。想尽量减少static,就发现所有容器都是函数里的临时变量。Static还是要适中。
Bug: 由于用的是字符串进行传递,所以老是会出现括号存去问题。对于(++sin(x))这样的,去括号后是++sin(x),逐层求导后发现是++cos(x),返回时又要去括号,则会出现…*++cos(x)这样的错误串。解决方案是写了个while去掉前导符号。
还有一些bug诸如词法分析器同步时尾部空格数不一样导致偏移值不同,这种bug太细节了,就不多赘述了。
这些bu*生的规律都是由于字符串没有抽象成语法树所造成的。
设计模式
可以考虑工厂模式,因为每次创造Diff或Sin或Cos或Poly或Optm的时候,都只是为其词法分析器赋值,没有牵涉到求导的情况。又不好把求导直接写进构造器,所以可以用工厂方法,制造一个工厂,产生实例的同时执行其特征操作。工厂应当使用静态方法。
总结
还是不能直接把字符串作为内部表示。内部数据的管理应当抽象出来。