定义

过度的考虑程序的通用性

影响

  • 过度的设计导致代码不易理解和维护

改进目标

  • 删除过度设计的代码

方法

  • 折叠继承体系
  • 内联类/函数
  • 改变函数声明
  • 移除死代码

案例1:问题代码

代码背景1

  • 这是一个计算人的体脂比以及判断是否肥胖的需求的实现
  • Person类用来记录人的各种数据
  • HealthService类根据人的数据计算体脂比以及判断是否肥胖
  • 考虑到未来可能不止计算健康信息,专门抽取了一个人的基础信息类Person,健康相关类再有一个单独的类PersonForHealth来承担
  • 又考虑到以后可能不止计算人的,抽取一个Animal接口作为 泛型参数传进去
  • 考虑到不止有体重相关的这些指标,又做了一个futureMayUseInfo接口,便于未来扩展

症状/问题1

代码中出现了很多过度设计:

  • HealthService、Animal、FutureMayUseInfo都是不必要的接口
  • 没有必要抽取Person抽象类
  • 没有必要使用泛型
/**
 * 健康指标计算服务
 */
public class HealthServiceImp implements HealthService<PersonForHealth> {
    @Override
    public double getBodyFatPercentage(PersonForHealth person) {
        double bodyFatPercentage =
            1.2 * getBodyMassIndex(person) + 0.23 * person.getAge() - 5.4 - 10.8 * person.getGender().getCode();
        return BigDecimal.valueOf(bodyFatPercentage).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }

    @Override
    public boolean isObese(PersonForHealth person, double waistHipRatio) {
        double bodyFatPercentage = getBodyFatPercentage(person);
        if (person.getGender().equals(Gender.FEMALE) && bodyFatPercentage >= 32) {
            return true;
        }
        return person.getGender().equals(Gender.MALE) && bodyFatPercentage >= 25;
    }

    private double getBodyMassIndex(PersonForHealth person) {
        return person.getBodyMass().getBodyMassIndex();
    }

    @Override
    public double getBasalMetabolism(PersonForHealth person) {
        double basalMetabolism;
        if (person.getGender().equals(Gender.FEMALE)) {
            basalMetabolism = 665.1 + 9.6 * person.getBodyMass().getWeight() + 180 * person.getBodyMass().getHeight()
                - 4.7 * person.getAge();
        } else {
            basalMetabolism = 66.5 + 13.8 * person.getBodyMass().getWeight() + 500 * person.getBodyMass().getHeight()
                - 6.8 * person.getAge();
        }
        return BigDecimal.valueOf(basalMetabolism).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }
}
/**
 * 健康指标计算服务接口
 */
public interface HealthService<T extends Animal> {
    /**
     * 根据BMI指数、年龄和性别估算体脂比
     * 
     * @return 体脂比,单位%
     */
    double getBodyFatPercentage(T animal);

    /**
     * 根据体脂比和性别判断是否肥胖
     * 
     * @return 是否肥胖
     */
    boolean isObese(T animal, double waistHipRatio);

    /**
     * 根据性别、年龄、身高和体重计算的基础代谢量
     * 
     * @param animal person
     * @return 基础代谢量,单位kcal
     */
    double getBasalMetabolism(T animal);
}
/**
 * 人员健康信息
 */
public class PersonForHealth extends Person {
    private final BodyMassInfo bodyMassInfo;

    private final FutureMayUseInfo futureMayUseInfo;

    public PersonForHealth(String name, int age, Gender gender, BodyMassInfo bodyMassInfo,
        FutureMayUseInfo futureMayUseInfo) {
        super(name, age, gender);
        this.bodyMassInfo = bodyMassInfo;
        this.futureMayUseInfo = futureMayUseInfo;
    }

    /**
     * 获取身高体重相关信息
     * 
     * @return 身高体重相关信息
     */
    public BodyMassInfo getBodyMass() {
        return bodyMassInfo;
    }

    public FutureMayUseInfo getFutureMayUseInfo() {
        return futureMayUseInfo;
    }
}
/**
 * 人员信息
 */
public abstract class Person implements Animal {
    private final String name;

    private final int age;

    private final Gender gender;

    public Person(String name, int age, Gender gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Gender getGender() {
        return gender;
    }
}

/**
 * 性别
 */
public enum Gender {
    FEMALE(0),
    MALE(1);

    private final int code;

    Gender(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}
/**
 * 动物接口
 */
public interface Animal {
}
/**
 * 未来可能会用到的扩展信息
 */
public interface FutureMayUseInfo {
}

改进手法1:折叠继承体系,删除不必要的接口和抽象类

代码背景2

  • PersonForHealth类有个BodyMassInfo属性,记录身高、体重,可以获取人的BMI指标

症状/问题2

代码中出现了很多过度设计:

  • BodyMassInfo类的只是获取BMI指标,属性也比较固定没有必要单独作为一个类
  • getBodyMassIndex不用单独作为函数
public class PersonForHealth extends Person {
    private final BodyMassInfo bodyMassInfo;

    private final FutureMayUseInfo futureMayUseInfo;

    public PersonForHealth(String name, int age, Gender gender, BodyMassInfo bodyMassInfo,
        FutureMayUseInfo futureMayUseInfo) {
        super(name, age, gender);
        this.bodyMassInfo = bodyMassInfo;
        this.futureMayUseInfo = futureMayUseInfo;
    }

    /**
     * 获取身高体重相关信息
     * 
     * @return 身高体重相关信息
     */
    public BodyMassInfo getBodyMass() {
        return bodyMassInfo;
    }

    public FutureMayUseInfo getFutureMayUseInfo() {
        return futureMayUseInfo;
    }
}
/**
 * 身高体重相关信息处理
 */
public class BodyMassInfo {
    // 身高,单位m
    private final double height;

    // 体重,单位kg
    private final double weight;

    public BodyMassInfo(double height, double weight) {
        this.height = height;
        this.weight = weight;
    }

    /**
     * 计算体脂比
     * 
     * @return 体脂比
     */
    public double getBodyMassIndex() {
        return weight / (height * height);
    }

    public double getHeight() {
        return height;
    }

    public double getWeight() {
        return weight;
    }
}

改进手法2: 内联类,将BodyMassInfo的相关属性直接移入Person类中 内联函数,消除getBodyMassIndex

代码背景3

  • 考虑到健康指标除了BMI和体脂比,还有基础代谢量,为 了方便后续使用,增加了获取基础代谢的方法
  • 考虑到除了根据体脂比来判断是否肥胖,很多情况下也 需要考虑腰臀比,所以增加一个腰臀比参数

症状/问题3

  • 过度设计了,方法只有测试用例调用
  • 参数并非真正需要,方法中也没有使用
/**
 * 健康指标计算服务
 */
public class HealthServiceImp implements HealthService<PersonForHealth> {
    @Override
    public double getBodyFatPercentage(PersonForHealth person) {
        double bodyFatPercentage =
            1.2 * getBodyMassIndex(person) + 0.23 * person.getAge() - 5.4 - 10.8 * person.getGender().getCode();
        return BigDecimal.valueOf(bodyFatPercentage).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }

    @Override
    public boolean isObese(PersonForHealth person, double waistHipRatio) {
        double bodyFatPercentage = getBodyFatPercentage(person);
        if (person.getGender().equals(Gender.FEMALE) && bodyFatPercentage >= 32) {
            return true;
        }
        return person.getGender().equals(Gender.MALE) && bodyFatPercentage >= 25;
    }

    private double getBodyMassIndex(PersonForHealth person) {
        return person.getBodyMass().getBodyMassIndex();
    }

    @Override
    public double getBasalMetabolism(PersonForHealth person) {
        double basalMetabolism;
        if (person.getGender().equals(Gender.FEMALE)) {
            basalMetabolism = 665.1 + 9.6 * person.getBodyMass().getWeight() + 180 * person.getBodyMass().getHeight()
                - 4.7 * person.getAge();
        } else {
            basalMetabolism = 66.5 + 13.8 * person.getBodyMass().getWeight() + 500 * person.getBodyMass().getHeight()
                - 6.8 * person.getAge();
        }
        return BigDecimal.valueOf(basalMetabolism).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }
}

改进手法3: 移除死代码,先删除测试用例,再删除未使用函数 改变函数声明,删除无用参数

改进后的代码

/**
 * 健康指标计算服务
 */
public class HealthServiceImp {
    public double getBodyFatPercentage(Person person) {
        double bodyFatPercentage =
            1.2 * person.getBodyMassIndex() + 0.23 * person.getAge() - 5.4 - 10.8 * person.getGender().getCode();
        return BigDecimal.valueOf(bodyFatPercentage).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }

    public boolean isObese(Person person) {
        double bodyFatPercentage = getBodyFatPercentage(person);
        if (person.getGender().equals(Gender.FEMALE) && bodyFatPercentage >= 32) {
            return true;
        }
        return person.getGender().equals(Gender.MALE) && bodyFatPercentage >= 25;
    }
}
/**
 * 人员健康信息
 */
public class Person {

    private final String name;

    private final int age;

    private final Gender gender;
    // 身高,单位m
    private final double height;

    // 体重,单位kg
    private final double weight;

    public Person(String name, int age, Gender gender, double height, double weight) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Gender getGender() {
        return gender;
    }

    /**
     * 计算体脂比
     *
     * @return 体脂比
     */
    public double getBodyMassIndex() {
        return weight / (height * height);
    }

    public double getHeight() {
        return height;
    }

    public double getWeight() {
        return weight;
    }
}

/**
 * 性别
 */
public enum Gender {
    FEMALE(0),
    MALE(1);

    private final int code;

    Gender(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}
/**
 * 模拟client调用
 */
public class Client {
    public static void main(String[] args) {
        Person person = new Person("John", 18, Gender.MALE, 1.80, 75.0);
        HealthServiceImp healthServiceImp = new HealthServiceImp();
        double bodyFatPercentage = healthServiceImp.getBodyFatPercentage(person);
        System.out.println("John body fat percentage is " + bodyFatPercentage);
        System.out.println("is John obese: " + healthServiceImp.isObese(person));
    }
}

重构前后类关系对比

操作手法

操作快捷键(推荐)Ctrl+Alt+Shift+T(或:鼠标右键“Refactor”)
移除超类Ctrl+Alt+NInline Super Class
删除参数Alt+DeleteSafe Delete

补充:IDEA-Analyze-Inspect Code 可辅助识别冗余的参数、方法、类和接口等