软件工程第一次上机实验--代码的坏味道(重构实验)

软件工程第一次上机实验

  1. Data Class(纯稚的数据类)
public class Node {
    public char itself;
    public int minLength;
    public char lastNode;
    public char[] nextNode;
    public int[] nextLength;

坏味道:数据域被设置成了公有的,这不符合封装的特性
修改方法:将类的域声明为私有,添加set和get方法进行设置和获取值,如果有必要,甚至可以将需要调用set和get函数的地方抽象出来形成函数放在这个类里以去掉set和get函数。
修改后:

public class Node {
    private char itself;
    private int minLength;
    private char lastNode;
    private char[] nextNode;
    private int[] nextLength;

    public void setMinLength(int minLength) {
        this.minLength = minLength;
    }

    public void setLastNode(char lastNode) {
        this.lastNode = lastNode;
    }

    public char getItself() {
        return itself;
    }

    public char getLastNode() {
        return lastNode;
    }

    public int getMinLength() {
        return minLength;
    }

    public char[] getNextNode() {
        return nextNode;
    }

    public int[] getNextLength() {
        return nextLength;
    }
}
  1. Sepculative Generality(夸夸其谈未来性)
 public void initArray(int array[][],int len){
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                array[i][j] = 1;
            }
        }
}
public void addOrMinus(int answer[][],boolean mid, int a[][], int a1, int a2,int b[][],int b1, int b2,int len){
        int ahBegin = (a1-1)*len/2;
        int ahEnd = ahBegin+len/2-1;
        int bhBegin = (b1-1)*len/2;
        int bhEnd = bhBegin+len/2-1;
        int avBegin = (a2-1)*len/2;
        int avEnd = avBegin+len/2-1;
        int bvBegin = (b2-1)*len/2;
        int bvEnd = bvBegin+len/2-1;
        for (int i = 0; i < len/2; i++) {
            for (int j = 0; j < len/2; j++) {
                answer[i][j] = mid ? a[ahBegin+i][avBegin+j]+b[bhBegin+i][bvBegin+j] : a[ahBegin+i][avBegin+j]-b[bhBegin+i][bvBegin+j];
            }
        }
    }

坏味道:initArray用于将来对数组进行初始化,avEnd变量也是为将来的操作所设置的值,但是现在用不上,显得代码冗余,可阅读性差,容易引起疑惑,将来也可能因为代码的改变而失去原有的设计作用。
修改方法:initArray函数未被使用,直接删去,addOrMinus函数中的avEnd变量未来可能会使用,但是现在没有被使用,所以删去,当需要时再按要求添加。
修改后:

public void addOrMinus(int answer[][],boolean mid, int a[][], int a1, int a2,int b[][],int b1, int b2,int len){
        int ahBegin = (a1-1)*len/2;
        int bhBegin = (b1-1)*len/2;
        int avBegin = (a2-1)*len/2;
        int bvBegin = (b2-1)*len/2;
        for (int i = 0; i < len/2; i++) {
            for (int j = 0; j < len/2; j++) {
                answer[i][j] = mid ? a[ahBegin+i][avBegin+j]+b[bhBegin+i][bvBegin+j] : a[ahBegin+i][avBegin+j]-b[bhBegin+i][bvBegin+j];
            }
        }
    }
  1. Long Parameter List(过长参数列)
public void addOrMinus(int answer[][],boolean mid, int a[][], int a1, int a2,int b[][],int b1, int b2,int len){ }

坏味道:参数列表过长,影响使用和阅读,而且当需要添加参数的时候就不得不修改所有的函数调用的地方。
修改方法:将参数列表中的参数放入一个新的类,这样在添加新的参数的时候只需要在类里添加实例域就可以了,而不必修改每处的函数调用。
修改后:

 public class ParameterList{
        private int answer[][];
        private boolean mid;
        private int a[][];
        private int a1;
        private int a2;
        private int b[][];
        private int b1;
        private int b2;
        private int len;
    }
public void addOrMinus(ParameterList parameterList){ }
  1. Feature Envy(依恋情结)
 public void printArray(){
        for (int i = 0; i < nodes.length; i++) {
            ArrayList<Character> arrayList = new ArrayList<>();
            char c = nodes[i].getItself();
            while (nodes[0].getItself() != c){
                char x = c;
                arrayList.add(x);
                for (int j = 0; j < nodes.length; j++) {
                    if (c == nodes[j].getItself()){
                        c = nodes[j].getLastNode();
                        break;
                    }
                }
            }
            System.out.print(nodes[i].getItself()+":"+nodes[i].getMinLength());
            System.out.print("路径为:s");
            for (int j = arrayList.size(); j > 0; j--) {
                System.out.print(" -> "+arrayList.get(j-1));
            }
            System.out.println();
        }
}

坏味道:方法在另一个类里,但是方法中需要反复访问Node类的实例域,这使得代码的设计逻辑显得杂糅。
修改方法:将该方法移入Node类中,这样可以降低代码的耦合,使得程序阅读起来更加清晰,代码逻辑更容易理解。
修改后:

public class Node {
    private char itself;
    private int minLength;
    private char lastNode;
    private char[] nextNode;
    private int[] nextLength;

    public Node(char itself, int minLength, char lastNode, char[] nextNode, int[] nextLength) {
        this.itself = itself;
        this.minLength = minLength;
        this.lastNode = lastNode;
        this.nextNode = nextNode;
        this.nextLength = nextLength;
    }
public void printArray(){
	//同上
}
}
  1. Temporary Field(令人迷惑的暂时字段)
		int x = positionChange%9;
        int y = positionChange/9;
        array[y][x] = value;
        this.instanceHolder[y][x].setValue(value);
        TextView textView = this.instanceHolder[y][x].textView;

坏味道:某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处,这种代码不好理解。
修改方法:将这些临时变量放入一个类中,给他们一个共同的家,这样在使用这些变量时,你可以很清楚的知道这是一个临时变量。
修改后:

 		temporary.x= positionChange%9;
        temporary.y = positionChange/9;
        array[temporary.y][ temporary.x] = value;
        this.instanceHolder[temporary.y][ temporary.x].setValue(value);
        TextView textView = this.instanceHolder[temporary.y][ temporary.x].textView;
  1. Primitive Obsession(基本类型偏执)
public class Order {
    private String customName;
    private String customSex;
    private Integer orderId;
    private Integer price;
}

坏味道:customName和customSex总是一起出现,可作为一个整体放入一个类里,这样会使得代码更有味道,作为基本信息的他们理应被放在一起。
修改方法:提炼类,将customName和customSex提炼出来放入一个类中。
修改后:

public class Order {
    private Custom custom;
    private Integer orderId;
    private Integer price;
}
public class Custom {
    private String name;
    private String address;
}
  1. 发散式变化
    假设某个汽车厂商生产三种品牌的汽车:Big、Tiny和Boss,每种品牌又可以选择燃油、纯电和混合动力。如果用传统的继承来表示各个最终车型,一共有3个抽象类加9个最终子类,如果要新增一个品牌,或者加一个新的引擎(比如核动力),那么子类的数量增长更快。
    首先把Car按品牌进行子类化,但是,每个品牌选择什么发动机,不再使用子类扩充,而是通过一个抽象的“修正”类,以组合的形式引入。
    //Car
public abstract class Car {
    // 引用Engine:
    protected Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }
    public abstract void drive();
}
//Engine
public interface Engine {
    void start();
}

在一个“修正”的抽象类RefinedCar中定义一些额外操作:

public abstract class RefinedCar extends Car {
    public RefinedCar(Engine engine) {
        super(engine);
    }
    public void drive() {
        this.engine.start();
        System.out.println("Drive " + getBrand() + " car...");
    }
    public abstract String getBrand();
}

这样一来,最终的不同品牌继承自RefinedCar,例如BossCar

public class BossCar extends RefinedCar {
    public BossCar(Engine engine) {
        super(engine);
    }
    public String getBrand() {
        return "Boss";
    }
}
  1. 重复的代码
	@Override
    public Student getById(String id) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session = sqlSessionFactory.openSession(); 	
        //以上代码要在每个方法中重复出现
        Student s = session.selectOne("test1.getById",id);
        return s;
}

坏味道:代码块重复出现,影响阅读,代码不够简洁明了
修改方法:把重复的代码提取出来放到一个方法中,新建SqlSessionUtil类,对外提供getSession方法

    static {
            String resource = "mybatis-config.xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(resource);
            } catch (IOException e) {
                e.printStackTrace();
            }
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    private static ThreadLocal<SqlSession> t = new ThreadLocal<>();

    public static SqlSession getSession(){
        SqlSession session = t.get();
        if(null == session){
            session = sqlSessionFactory.openSession();
            t.set(session);
        }
        return session;
    }
	@Override
    public Student getById(String id) {
        SqlSession session = SqlSessionUtil.getSession();
        Student s = session.selectOne("test1.getById",id);
        return s;
    }

实验收获:代码的重构很麻烦也很费时间,但是重构后的代码比起重构前确实好了很多,更加简洁,更加具有调理,能把很多编写代码时没考虑到的细节改善完整,也便于以后的维护,虽然可能暂时还没有维护的必要,但是影响是长远的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
     软件工程上机实验要求      利用一种高级语言或数据库程序设计语言,依照所学的软件工程理论设计一个小型软件。要求:   1.大作业在教学17周结束前完成后提交。   2.设计过程原则上要求分组进行,每组一个题目(各组自定)。每组最多不超过3人,组内不同的学生可分工不同,内容不能完全雷同。   3.有详细、完整的文档资料。包括以下内容:      可行性研究报告;     需求规格说明书;     设计说明书(由于编写的是小型软件,故只写概要设计说明书);      用户操作手册;     测试计划;      测试分析报告;      软件开发总结报告。   4.有系统设计代码(其中代码注释不少于代码的30%),系统运行说明。   5.软件工程设计题目示例:        题目一 “教务管理系统之子系统——系内课程安排”   1.系统简介    每学期的期中,学院教务处分别向各个系发出下学期的教学计划,包   括课程名、课时、班级类别(本科、专科、高职)、班号等;系教学主管人员根据教学任务和要求给出各课程的相关限制(如:任课教师职称、和班数、最高周学时数等);任课教师自报本人授课计划,经所在教研室协调确认,将教学计划上交系主管教学计划的主任,批准后上报学院教务处,最终有教务处给出下学期全系教师的教学任务书。   假设上述排课过程全部为人工操作,现要求改造为能利用计算机实现的自动处理过程。   1. 限定条件   (1)每位教师的主讲门数不超过2门/学期:讲师以下职称的教师不能承担系定主课的主讲任务。   (2)系级干部的主讲课时不能超过4学时/周。   (3)本学期出现严重教学事故的教师不能承担下学期的主讲任务。   (4)本系统的输入项至少应包含3个:教务处布置的教学计划、系教师自报的讲课计划和系定的有关讲课限制条件。   (5)本系统的输出项至少应包含2个:教务处最终下达的全系教师教学任务书和系各教学班一学期的课程表(可不包含上课地点)。       题目二、“学校教材订购系统”   1、 系统简介   本系统可细化为两个子系统:销售系统和采购系统   销售系统的工作过程为:首先由教师或学生提交购书单,经教材发行人员审核是有效购书单后,开发票、登记并返给教师或学生领书单,教师或学生即可去书库领书。   采购系统的主要工作过程为:若是脱销教材,则登记缺书,发缺书单给书库采购人员;一旦新书入库后,即发进书通知给教材发行人员   以上的功能要求在计算机上实现。   2、 技术要求和限制条件   (1) 当书库中的各种书籍数量发生变化(包括领书和进书时),都应修改相关的书库记录,如库存表或进/出库表。   (2) 在实现上述销售和采购的工作过程时,需考虑有关单据的合法性验证   (3) 系统的外部项至少包含三个:教师、学生和教材工作人员。   (4) 系统的相关数据存储至少包含6个:购书表、库存表、缺书登记表、待购教材表、进/出库表。       题目三、“机票预订系统”   1、系统简介   航空公司为给旅客乘机提供方便,需开发一机票预定系统。各旅行社把预定机票的旅客信息(姓名、性别、工作单位、身份证号码、旅行时间、旅行目的地等)输入到该系统,系统为旅客安排航班。当旅客交付了预定金后,系统印出取票通知和帐单给旅客,旅客在飞机起飞的前一天凭取票通知和帐单交款取票,系统核对无误即印出机票给旅客。此外航空公司为随时掌握各航向飞机的乘载情况,需定期进行查询统计,以便适当调整。   2、 技术要求及限定条件   (1)在分析系统功能时要考虑有关证件的合法性验证(如身份证、取票通知、交款发票等)。   (2)对于本系统还应补充以下功能:    (1) 旅客延误了取票时间的处理    (2) 班机取消后的处理    (3) 旅客临时更改机票班次的处理   (3) 系统的外部项至少包含三个:旅客、旅行社和航空公司。       题目四:“学校内部工资管理系统”   1、 系统简介   假设学校共有教职工约1000人,10个行政部门和8个系部。每个月20日前各部门(包括系、部)要将出勤情况上报人事处,23日前人事处将出勤工资、奖金及扣款清单送财务处。财务处于每月月底将教职工的工资表做好并将数据送银行。每月初(3日前)将工资条发给各单位。若有员工调入、调出、校内调动、离退休等数据变化,则由人事处通知相关部门和财务处。   2、 技术要求及限定条件   (1) 本系统的数据存储至少包含:工资表、部门汇总表、扣税款表、银行发放表   (2) 除人事处、财务处外,其他只能部门和系部名称可简化,如系1,系2…..等   (3) 工资、奖金、扣款细节可由学生自定       题目五、“实验室设备管理系统”   1

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值