软件工程第一次上机实验
- 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;
}
}
- 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];
}
}
}
- 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){ }
- 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(){
//同上
}
}
- 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;
- 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;
}
- 发散式变化
假设某个汽车厂商生产三种品牌的汽车: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";
}
}
- 重复的代码
@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;
}
实验收获:代码的重构很麻烦也很费时间,但是重构后的代码比起重构前确实好了很多,更加简洁,更加具有调理,能把很多编写代码时没考虑到的细节改善完整,也便于以后的维护,虽然可能暂时还没有维护的必要,但是影响是长远的。