首页 > 其他分享 >2023年春面向对象第一单元

2023年春面向对象第一单元

时间:2023-03-18 23:34:43浏览次数:48  
标签:Term 解析 递归 作业 面向对象 2023 年春 方法 public

23年春面向对象第一单元分析与总结

目录

前言
架构
  解析方法
  数据结构
  类图分析
基于度量的程序结构分析
BUG分析
互测相关
总结

前言

OO第一单元的作业主要围绕着表达式的解析,简化与运算展开。笔者在此简要的介绍一下任务背景:

1. HW1: 使用递归下降等方法对于符合一定文法要求的算式进行解析,要求展开括号并进行同类项合并

2. HW2: 新增了三角函数因子,并实现了不多于三个的不可相互调用的函数

3. HW3: 新增了求导因子,并且允许函数间的相互调用

总体上来说,第一单元的任务并不轻松,即使是上个学期修过OOpre的笔者在突然面对第一单元时仍然惴惴不安,手忙脚乱。在第一次作业时写出了bad smell code,并在第二次作业时推倒重来的。但是,在结束时回望,第一单元的任务虽然是完成了,但是实现的细节却不可以说是美观,具体的分析放在下面,各位一看便知(笑

架构

从总的架构上来说,第一单元的任务与其说是面向对象的设计,不如说是一个包装了面向对象外壳,实质是递归练习的面向过程编程。首先,我采用了递归下降的解析方法。由这种解析方法,自然而然形成了多叉树的数据结构,随后由于因子与表达式、三角函数与表达式、函数与表达式等相互嵌套的调用,使得我的程序变成了一个递归练习的集合:递归地解析,递归的展开,递归的化简,递归的输出......

解析方法

本单元作业中,我主要采用了递归下降的解析方法。按照我个人的理解,递归下降解析方法的精髓就在于,为每一个层次建立一个解析方法,当读入表征该层次开始的符号时进入专门解析该层次的方法,并在读入不属于该层次的符号时返回。更加正式的说法是:每一个非终结符(即语法规则中每个位于产生式左侧的符号)编写一个可递归调用的方法,通过方法之间的相互调用来处理嵌套的语法层次关系。 递归下降的解析方式是有固定套路的,其主要由两部分构成:词法解析器与文法解析器

//词法解析器
public class Lexer() {
    private String str;
    private int pos = 0;
    private String curToken;

    public Lexer(String str) {
        this.str = str;
        this.next();        //用于获取下一个有效符号的方法
    }

    public void next() {
        if (pos == str.length) {
            return;
        }
        char c = str.charAt(pos);
        /* if (c 是一个满足文法的符号) {
            pos++;
            curtoken = c;
        }
        else {
            做其他的判断
        }*/
    }

    public String peek() {
        return this.curToken;
    }
}
//文法解析器,以本单元作业为例
public class Parser {
    private Lexer lexer;
    
    public Parser(Lexer lexer) {
        this.lexer = lexer;
    }

    public Expr parseExpr() {
        Expr expr = new Expr();
        expr.addTerm(parseTerm());

        while (lexer.peek().equals("+") || lexer.peek().equals("-")) {
            expr.addTerm(parseTerm());
        }
        return expr;
    }

    public Term parseTerm() {
        Term term = new Term();
        term.addFactor(parseFactor());

        while (lexer.peek().equals("*")) {
            term.addFactor(parseFactor());
        }
        return term;
    }

    public Factor parseFactor() {
        //此处就需要对各种因子进行解析:常数,幂函数,三角函数,自定义函数,求导因子
    }
}

递归下降的解法逻辑清晰,易于扩展,出现bug的可能性也比较小,是比较适合第一单元作业的解析方式。同时,在解析过程中,善用抛出异常,可以检测到不合语法的输入以及程序中可能存在的bug。

数据结构


展开与化简的实现严重依赖于数据的组织方式。在第一次作业中,我是这样定义Term类的:

public class Term {
    private ArrayList<Factor> factors;
}

可以想象,由于所有具体的因子都继承至Factor这个抽象类,在Term中对其进行统一管理的时候,不可避免的就会出现:

if (object instanof Constant) {}
else if (object instanof Power) {}
else if (object instanof TriFunction) {}
......

大量冗杂的判断巨量增加了方法的长度,使得程序运行低效且很容易出现bug。第一次作业中挂掉的强测点大多也都是因为这种复杂判断大量出现,在修改了的时候顾了这头丢了那头导致的错误。在第二次作业时,我推翻了第一次作业的代码,主要在数据的组织方式上面进行的重构:

public class Term {
    private ArrayList<Constant> constants;
    private ArrayList<Power> powers;
    private ArrayList<TriFunction> triFunctions;
    private ArrayList<Expr> exprs;
    ......
}

由于修改了数据存储的方式,使他们分类存放,不必要的判断大量减少,展开括号的方法瘦身到只有20行,第二次和第三次作业强测和互测均未再出现bug。尽管在研讨课中我注意到,使用HashMap容器可能更加便于简化和运算,但是由于改变数据结构是牵一发而动全身的关键要素,再在第二次作业的基础上进行重构,工作量巨大,令人望而生畏。故本单元的重构也就仅限于此。

展开与化简


笔者的展开与化简,在前言部分已经提到过了,是使用递归的方式进行的。具体来说,在Expr层调用Term的化简办法,在Term层调用Factor的化简办法,如果有作为Factor的Expr存在,则递归进入化简。这样做的好处就在于上层接口简单,可以在非常轻松的进行调用。缺点则在于嵌套结构过深,难以寻找bug具体位置。

类图分析


类图
类图如上。笔者项目的主要结构都由该类图显示出来,为了方便理解,我将所有的类拆分为了两个部分,一个部分是controlling parts, 这一部分主要负责输入输出的控制,构建起了递归下降解法的基本框架。而另一个部分,框架当中作为组成部分的类和简化计算方法,我将其归在了data parts部分。在这种结构下,main方法相对来说会很简单,其只需要调用Expr一层的方法即可递归实现所有功能。实际上,我的main方法大概是这样的:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    Parser parser = new Parser(new Lexer(""));
    parser.parseFunctions(scanner);
    String input = scanner.nextLine().replaceAll("\\s","");       
    Lexer lexer = new Lexer(input);
    parser.setLexer(lexer);
    Expr expr = parser.parseExpr();                                             
    expr.expandBracket();
    expr.simplify();
    System.out.print(expr.toString());
    return;
}

大量的细节(1200行代码)隐藏在了宏观接口的背后。我也并不清楚这样的做法是否符合设计理念,但就笔者自己感觉来说,这样的设计还是挺优美的(笑。

基于基于度量的程序结构分析


class metrics
使用metricsReloaded插件生成的对整个项目的分析如上。其中:

  • OCavg : 每个类中所有非抽象方法的平均圈复杂度(继承的方法不计算在内)。代表类的方法的平均循环复杂度。
  • OCmax : 每个类中非抽象方法的最大圈复杂度(继承的方法不计算在内)。代表类的方法的最高循环复杂度。
  • WMC : 每个类中方法的总圈复杂度。代表类的总循环复杂度。

可以看到,由于递归调用的存在,Expr,Term中存在着大量名字相同、功能依次细化的方法。随着三次任务不断迭代,Expr与Term成为了不折不扣的巨类,大量新增的功能往其中添加,使得代码复杂度不断增长。Parser为文法解析器,大量的判断结构倒是无法避免的。递归下降的结构也使得文法解析器的逻辑相当清晰且易于调试bug。因此虽然其复杂度略高,可扩展性和可维护性却几乎是本项目中最好的。Functions主要实现的对自定义函数的替换和解析。在此处笔者原本希望借用java强大的正则表达式功能实现对于函数的替换。但是作业中可能出现的嵌套情况可谓花样百出,防不胜防,最后不得不手动匹配左右括号实现对函数的替换。此处也许有更加巧妙的解决办法,但笔者是在实力有限,难以优化。

当然,高内聚,低耦合是一个优秀面向对象程序的基本要求。我们或许可以在本单元作业中学会一些降低代码复杂度,提高可读性和可维护性的方法
  • 采用HashMap等更加高效的容器
  • 采用更加高效的算法,减少循环嵌套次数
  • 将功能相对独立的方法独立出来,形成可反复调用的模块。比如:匹配括号,替换自定义函数

BUG分析

在第一单元中,笔者出现的BUG主要集中在第一次作业,主要原因是因为在分类判断Factor的类型中时漏判Expr类型的RE错误。在重构之后,错误根源被修复,第二次和第三次作业当中均未再出现bug。当然,这种正确性上的保证是以牺牲性能分作为代价的。如果对三角函数的化简,正负号等做更多的优化,也许还会揭露出现有代码中隐藏的bug。

互测相关

互测是OO课程当中的重要一环,也通常是最令人血压飙升的环节。相信每个经历过OO课程的人都久久不能忘怀。从第一次hack成功的欣喜,到第一次被hack的会心大笑,都是在OO课程中成长的一部分。

对于互测,我也尝试过使用测评机进行自动化“轰炸”,但令人遗憾的是,大多数情况下,测评机即使可以找到有效数据,也不能通过课程组对于互测数据的限制。因此,在第一单元的作业中,我只在第一次作业成功hack三次,在第二次和第三次作业中均未成功hack。据我观察,成功hack的数据大多也是通过手造出来的,这也从另一方面提醒了我比较缺乏对于数据的敏感度,对于逻辑测试,压力测试,边界测试等理解还不够深入。

总结

第一单元的作业有磕绊,但总体上来讲还是顺利的结束了。单纯就面向对象的思想和方法上来讲,OOpre带给笔者的那种醍醐灌顶的通透感远超OO第一单元。但笔者认为,OO这门课的性质本来就更偏向于实践,对于面向对象的理论缺少系统的的讲解并非过错。笔者也理应反省,面向对象的精髓不是说,而是做。与其讨论多做优化会不会导致更多不可预料的bug,倒不如先动手去做。与其积重难返不愿重构,不如学会先架构,后工程。笔者也期待着,在经历过OO的磨练过后,回望这篇博客,还能从中拾遗,学到更多现在没能领悟到的东西。

标签:Term,解析,递归,作业,面向对象,2023,年春,方法,public
From: https://www.cnblogs.com/topady/p/17229725.html

相关文章

  • java学习日记20230317-多态
    多态方法和对象爱过你具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承的基础上;方法的重载体现多态方法的重写体现多态对象的多态一个对象的编译类型和......
  • 2023.3.18软件工程日报
    时间:6小时代码量:500行今日把地铁系统的基本功能实现了,包括三个功能,前两个功能在上课的时候已经构思并基本实现,但是第三个功能确实费了些时间,但是也基本完成,目前仍需要在......
  • 安卓连接MYSQL---2023年3月18软工日报
    今天上午睡到9点,下午用安卓连接MYSQL,最后连接成功,我来帮你踩了坑,老子帮你弄弄。首先就是你要导入5.1.49jar包驱动,别的不行,别问我,我才过坑,试了试,再有就是改权限,mysql要支......
  • SudoEdit(CVE-2023-22809) 提权漏洞分析
    SudoEdit(CVE-2023-22809)提权漏洞分析TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChine......
  • KubeSphere 社区双周报 | OpenFunction v1.0.0 发布 | 2023.03.03-03.16
    KubeSphere社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过commit的贡献者,并对近期重要的PR进行解析,同时还包含了线上/线下活动和布......
  • commitlint 结合 husky 实现提交信息格式校验(2023-03-18)
    为什么要对git提交信息进行格式校验通过对提交信息进行格式校验能统一规范,这在团队协作过程中显得格为重要。符合规范的提交信息能很好反映此次提交的的目的,内容和......
  • Spring Study -lesson12 -AOP(一)-2023-03-18
    AOP方法一:通过SpringAPI接口实现<beanid="userService"class="com.feijian.service.UserServiceImpl"/><beanid="log"class="com.feijian.log.Log"/>......
  • 2023.3.18(软件工程日报)
    Spinner是下拉框控件,它用于从一串列表中选择某项,其功能类似于单选按钮的组合。下拉列表的展示方式有两种,一种是在当前下拉框的正下方弹出列表框,此时要把spinnerMode属性设......
  • 谈谈Java面向对象设计的六大原则
    单一职责原则——SRP开闭原则——OCP里式替换原则——LSP依赖倒置原则——DIP接口隔离原则——ISP迪米特原则——LOD单一职责原则单一职责原则的定义是就一个类......
  • 2023省选16天
    打不了,但是停课。\(DAY\1\(2023/3/18)\)模拟赛日。上午模拟赛,六机房(停课机房)早模过了,所以在七机房。T1是字符串,一眼\(hash\),手玩了个结论,复杂度\(O(n)\)感觉没啥问题......