Allen's blog Allen's blog
首页
面经
算法 (opens new window)
分类

Allen

前端CV工程师
首页
面经
算法 (opens new window)
分类
  • Javascript

    • npm包管理工具
    • requestAnimationFrame
    • JS获取屏幕、浏览器、网页宽度和高度
    • JS获取子节点、父节点、兄弟节点
    • 24种坏味道以及重构手法
    • 消除过多的if-else
    • TypeScript

    • CSS

    • Vue

    • React

    • 框架和构建工具

    • 工具库

    • 常见业务场景

    • Bug

    • 项目实战

    • 前端
    • Javascript
    Allen
    2023-10-23
    目录

    消除过多的if-else

    原文章:6 个实例详解如何把 if-else 代码重构成高质量代码 (opens new window) 写 if-else 不外乎两种场景:异常逻辑处理和不同状态处理。

    //举例一:异常逻辑处理例子
    Object obj = getObj();
    if (obj != null) {
        //do something
    }else{
        //do something
    }
    
    //举例二:状态处理例子
    Object obj = getObj();
    if (obj.getType == 1) {
        //do something
    }else if (obj.getType == 2) {
        //do something
    }else{
        //do something
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    重构 if-else 时,心中无时无刻把握一个原则:尽可能地维持正常流程代码在最外层。

    实现的手段有:减少嵌套、移除临时变量、条件取反判断、合并条件表达式等。

    # 异常逻辑处理型重构方法实例一:

    重构前:

    double disablityAmount(){
        if(_seniority < 2)
            return 0;
    
        if(_monthsDisabled > 12)
            return 0;
    
        if(_isPartTime)
            return 0;
    
        //do somethig
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    重构后:

    double disablityAmount(){
        if(_seniority < 2 || _monthsDisabled > 12 || _isPartTime)
            return 0;
    
        //do somethig
    }
    
    1
    2
    3
    4
    5
    6

    这里的重构手法叫合并条件表达式:如果有一系列条件测试都得到相同结果,将这些结果测试合并为一个条件表达式。

    # 异常逻辑处理型重构方法实例二:

    重构前:

    double getPayAmount(){
        double result;
        if(_isDead) {
            result = deadAmount();
        }else{
            if(_isSeparated){
                result = separatedAmount();
            }
            else{
                if(_isRetired){
                    result = retiredAmount();
                else{
                    result = normalPayAmount();
                }
            }
        }
        return result;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    重构后:

    double getPayAmount(){
        if(_isDead)
            return deadAmount();
    
        if(_isSeparated)
            return separatedAmount();
    
        if(_isRetired)
            return retiredAmount();
    
        return normalPayAmount();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    重构前后最大的区别是减少 if-else 嵌套。

    可以看到,最初的版本 if-else 最深的嵌套有三层,看上去逻辑分支非常多,进到里面基本都要被绕晕。其实,仔细想想嵌套内的 if-else 和最外层并没有关联性的,完全可以提取最顶层。

    改为平行关系,而非包含关系,if-else 数量没有变化,但是逻辑清晰明了,一目了然。 另一个重构点是废除了 result 临时变量,直接 return 返回。好处也显而易见直接结束流程,缩短异常分支流程。原来的做法先赋值给 result 最后统一 return,那么对于最后 return 的值到底是那个函数返回的结果不明确,增加了一层理解难度。

    总结重构的要点:如果 if-else 嵌套没有关联性,直接提取到第一层,一定要避免逻辑嵌套太深。尽量减少临时变量改用 return 直接返回。

    # 异常逻辑处理型重构方法实例三:

    重构前:

    public double getAdjustedCapital(){
        double result = 0.0;
        if(_capital > 0.0 ){
            if(_intRate > 0 && _duration >0){
                resutl = (_income / _duration) *ADJ_FACTOR;
            }
        }
        return result;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    第一步,运用第一招:减少嵌套和移除临时变量:

    public double getAdjustedCapital(){
        if(_capital <= 0.0 ){
            return 0.0;
        }
        if(_intRate > 0 && _duration >0){
            return (_income / _duration) *ADJ_FACTOR;
        }
        return 0.0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    这样重构后,还不够,因为主要的语句(_income / _duration) *ADJ_FACTOR;在 if 内部,并非在最外层,根据优化原则(尽可能地维持正常流程代码在最外层),可以再继续重构:

    public double getAdjustedCapital(){
        if(_capital <= 0.0 ){
            return 0.0;
        }
        if(_intRate <= 0 || _duration <= 0){
            return 0.0;
        }
    
        return (_income / _duration) *ADJ_FACTOR;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    这才是好的代码风格,逻辑清晰,一目了然,没有 if-else 嵌套难以理解的流程。

    这里用到的重构方法是:将条件反转使异常情况先退出,让正常流程维持在主干流程。

    # 异常逻辑处理型重构方法实例四:

       /* 查找年龄大于18岁且为男性的学生列表 */
        public ArrayList<Student> getStudents(int uid){
            ArrayList<Student> result = new ArrayList<Student>();
            Student stu = getStudentByUid(uid);
            if (stu != null) {
                Teacher teacher = stu.getTeacher();
                if(teacher != null){
                    ArrayList<Student> students = teacher.getStudents();
                    if(students != null){
                        for(Student student : students){
                            if(student.getAge() > = 18 && student.getGender() == MALE){
                                result.add(student);
                            }
                        }
                    }else {
                        logger.error("获取学生列表失败");
                    }
                }else {
                    logger.error("获取老师信息失败");
                }
            } else {
                logger.error("获取学生信息失败");
            }
            return result;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    典型的"箭头型"代码,最大的问题是嵌套过深,解决方法是异常条件先退出,保持主干流程是核心流程:

    重构后:

       /* 查找年龄大于18岁且为男性的学生列表 */
        public ArrayList<Student> getStudents(int uid){
            ArrayList<Student> result = new ArrayList<Student>();
            Student stu = getStudentByUid(uid);
            if (stu == null) {
                logger.error("获取学生信息失败");
                return result;
            }
    
            Teacher teacher = stu.getTeacher();
            if(teacher == null){
                logger.error("获取老师信息失败");
                return result;
            }
    
            ArrayList<Student> students = teacher.getStudents();
            if(students == null){
                logger.error("获取学生列表失败");
                return result;
            }
    
            for(Student student : students){
                if(student.getAge() > 18 && student.getGender() == MALE){
                    result.add(student);
                }
            }
            return result;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # 状态处理型重构方法实例一

    重构前:

    double getPayAmount(){
        Object obj = getObj();
        double money = 0;
        if (obj.getType == 1) {
            ObjectA objA = obj.getObjectA();
            money = objA.getMoney()*obj.getNormalMoneryA();
        }
        else if (obj.getType == 2) {
            ObjectB objB = obj.getObjectB();
            money = objB.getMoney()*obj.getNormalMoneryB()+1000;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    重构后:

    double getPayAmount(){
        Object obj = getObj();
        if (obj.getType == 1) {
            return getType1Money(obj);
        }
        else if (obj.getType == 2) {
            return getType2Money(obj);
        }
    }
    
    double getType1Money(Object obj){
        ObjectA objA = obj.getObjectA();
        return objA.getMoney()*obj.getNormalMoneryA();
    }
    
    double getType2Money(Object obj){
        ObjectB objB = obj.getObjectB();
        return objB.getMoney()*obj.getNormalMoneryB()+1000;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    这里使用的重构方法是:把 if-else 内的代码都封装成一个公共函数。函数的好处是屏蔽内部实现,缩短 if-else 分支的代码。代码结构和逻辑上清晰,能一下看出来每一个条件内做的功能。

    # 状态处理型重构方法实例二

    针对状态处理的代码,一种优雅的做法是用多态取代条件表达式(《重构》推荐做法)。

    你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。将这个表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。

    重构前:

    double getSpeed(){
        switch(_type){
            case EUROPEAN:
                return getBaseSpeed();
            case AFRICAN:
                return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
            case NORWEGIAN_BLUE:
                return (_isNailed)?0:getBaseSpeed(_voltage);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    重构后:

    class Bird{
        abstract double getSpeed();
    }
    
    class European extends Bird{
        double getSpeed(){
            return getBaseSpeed();
        }
    }
    
    class African extends Bird{
        double getSpeed(){
            return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
        }
    }
    
    class NorwegianBlue extends Bird{
        double getSpeed(){
            return (_isNailed)?0:getBaseSpeed(_voltage);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    可以看到,使用多态后直接没有了 if-else,但使用多态对原来代码修改过大,需要一番功夫才行。最好在设计之初就使用多态方式。

    上次更新: 2023/12/16, 09:22:46
    24种坏味道以及重构手法
    TS中的类型

    ← 24种坏味道以及重构手法 TS中的类型→

    最近更新
    01
    rollup使用配置文件rollup.config.ts打包
    12-08
    02
    package.json导出类型
    12-08
    03
    关键问题方案
    11-17
    更多文章>
    Theme by Vdoing | Copyright © 2023-2023 Allen | Github
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式