iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >如何避免写出糟糕if...else语句
  • 839
分享到

如何避免写出糟糕if...else语句

2024-04-02 19:04:59 839人浏览 独家记忆
摘要

如何避免写出糟糕if...else语句,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。在写代码的日常中,if...

如何避免写出糟糕if...else语句,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

在写代码的日常中,if...else语句是极为常见的。正因其常见性,很多同学在写代码的时候并不会去思考其在目前代码中的用法是否妥当。而随着项目的日渐发展,糟糕的if...else语句将会充斥在各处,让项目的可维护性急剧下降。故在这篇文章中,笔者想和大家谈谈如何避免写出糟糕if...else语句。

由于脱密等原因,下面中的示例代码将会从一些开源软件摘抄或者经过抽象的生产代码挑选出来作为示范。

| 问题代码

当我们看到一组if...else时,一般是不会有什么阅读负担的。但当我们看到这样的代码时:

private void validate(apiCreateSchedulerMessage msg) {
    if (msg.getType().equals("simple")) {
        if (msg.getInterval() == null) {
            if (msg.getRepeatCount() != null) {
                if (msg.getRepeatCount() != 1) {
                    throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat more than once"));
                }
            } else {
                throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat forever"));
            }
        } else if (msg.getInterval() != null) {
            if (msg.getRepeatCount() != null) {
                if (msg.getInterval() <= 0) {
                    throw new ApiMessageInterceptionException(argerr("interval must be positive integer"));
                } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() < 0 ) {
                    throw new ApiMessageInterceptionException(argerr("duration time out of range"));
                } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() > 2147454847000L) {
                    throw new ApiMessageInterceptionException(argerr("stopTime out of Mysql timestamp range"));
                }
            }
        }
        if (msg.getStartTime() == null) {
            throw new ApiMessageInterceptionException(argerr("startTime must be set when use simple scheduler"));
        } else if (msg.getStartTime() != null && msg.getStartTime() < 0) {
            throw new ApiMessageInterceptionException(argerr("startTime must be positive integer or 0"));
        } else if (msg.getStartTime() != null && msg.getStartTime() > 2147454847 ){
            //  mysql timestamp range is '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.
            //  we accept 0 as startDate means start from current time
            throw new ApiMessageInterceptionException(argerr("startTime out of range"));
        }
        if (msg.getRepeatCount() != null && msg.getRepeatCount() <= 0) {
            throw new ApiMessageInterceptionException(argerr("repeatCount must be positive integer"));
        }
    }
    if (msg.getType().equals("cron")) {
        if (msg.getCron() == null || ( msg.getCron() != null && msg.getCron().isEmpty())) {
            throw new ApiMessageInterceptionException(argerr("cron must be set when use cron scheduler"));
        }
        if ( (! msg.getCron().contains("?")) || msg.getCron().split(" ").length != 6) {
            throw new ApiMessageInterceptionException(argerr("cron task must follow fORMat like this : \"0 0/3 17-23 * * ?\" "));
        }
        if (msg.getInterval() != null || msg.getRepeatCount() != null || msg.getStartTime() != null) {
            throw new ApiMessageInterceptionException(argerr("cron scheduler only need to specify cron task"));
        }
    }
}

亦或是这样的代码:

try {
  for (int j = myConfig.getContentStartNum(); j <= rowNum; j++) {
    row = sheet.getRow(j);
    T obj = target.newInstance();
    for (int i = 0; i < colNum; i++) {
        Field colField = excelUtil.getOneByTitle(metaList, titleList[i]);
        colField.setAccessible(true);
        String fieldType = colField.getType().getSimpleName();
        HSSFCell cell = row.getCell(i);
        int cellType = cell.getCellType();
        System.out.println(colField.getName()+"|"+fieldType+" | "+cellType);
        if(HSSFCell.CELL_TYPE_STRING == cellType){
            if("Date".equals(fieldType)){
                colField.set(obj, DateUtil.parse(cell.getStrinGCellValue()));
            }else {
                colField.set(obj, cell.getStringCellValue());
            }
        }else if(HSSFCell.CELL_TYPE_BLANK == cellType){
            System.out.println("fieldName"+colField.getName());
            if("Boolean".equals(fieldType)){
                colField.set(obj, cell.getBooleanCellValue());
            }else{
                colField.set(obj, "");
            }
        }else if(HSSFCell.CELL_TYPE_NUMERIC == cellType){
            if("Integer".equals(fieldType) || "int".equals(fieldType)){
                colField.set(obj, (int)cell.getNumericCellValue());
            }else {
                colField.set(obj, cell.getNumericCellValue());
            }
        }else if(HSSFCell.CELL_TYPE_BOOLEAN == cellType){
            colField.set(obj, cell.getBooleanCellValue());
        }
    }
    result.add(obj);
}
} catch (InstantiationException | IllegalAccessException | ParseException e) {
e.printStackTrace();
}

看完这两段代码,相信大家和我的心情是一样的: 

如何避免写出糟糕if...else语句

阅读它们的负担实在是太大了——我们要记住好几个逻辑判断分支,才能知道到底什么情况下才能得到那个结果。更别说维护的成本有多高了,每次维护时都要读一遍,然后再基于此来改。长此以往,我们的代码就变成"箭头式代码"了。

//...............
    //...............
         //...............
             //...............
                 //...............
                 //...............
             //...............
         //...............
   //...............
//...............

| 目标和关键指标

前面说过,我们的目标是减少糟糕的if...else代码。那么什么是糟糕的if...else代码呢?我们可以简单的总结一下:

  • 两重以上的嵌套

  • 一个逻辑分支的判断条件有多个,如:A && B || C这种。其实这也可以看作变种的嵌套

这样就可以看出来,我们的关键指标就是减少嵌套

| 常见Tips

1. 三元表达式

三元表达式在代码中也是较为常见的,它可以简化一些if...else,如:

public Object getFromOpaque(String key) {
    return opaque == null ? null : opaque.get(key);
}

为什么说是一些呢?因此三元表达式必须要有一个返回值。

这种情况下就没法使用三元表达式

public void putToOpaque(String key, Object value) {
    if (opaque == null) {
        opaque = new LinkedHashMap();
    }
    opaque.put(key, value);
}

2. switch case

在Java中,switch可以关注一个变量( byte short int 或者 char,从Java7开始支持String),然后在每个case中比对是否匹配,是的话则进入这个分支。

在通常情况下,switch case的可读性比起if...else会好一点。因为if中可以放复杂的表达式,而switch则不行。话虽如此,嵌套起来还是会很恶心。

因此,如果仅仅是对 byte,short,int和char以String简单的值判断,可以考虑优先使用switch。

3. 及时回头


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 {
                throw new MyException("获取学生列表失败");
            }
        }else {
            throw new MyException("获取老师信息失败");
        }
    } else {
        throw new MyException("获取学生信息失败");
    }
    return result;
}

针对这种情况,我们应该及时抛出异常(或者说return),保证正常流程在外层,如:


public ArrayList<Student> getStudents(int uid){
    ArrayList<Student> result = new ArrayList<Student>();
    Student stu = getStudentByUid(uid);
    if (stu == null) {
         throw new MyException("获取学生信息失败");
    }
    Teacher teacher = stu.getTeacher();
    if(teacher == null){
         throw new MyException("获取老师信息失败");
    }
    ArrayList<Student> students = teacher.getStudents();
    if(students == null){
        throw new MyException("获取学生列表失败");
    }
    for(Student student : students){
        if(student.getAge() > 18 && student.getGender() == MALE){
            result.add(student);
        }
    }
    return result;
}

| 使用设计模式

除了上面的几个tips,我们还可以通过设计模式来避免写出糟糕的if...else语句。在这一节,我们将会提到下面几个设计模式:

  1. State模式

  2. Mediator模式

  3. Observer模式

  4. Strategy模式

1. State模式

在代码中,我们经常会判断一些业务对象的状态来决定在当前的调用下它该怎么做。我们举个例子,现在我们有一个银行的接口:

public interface Bank {

void lock();

void unlock();

void doAlarm();
}

让我们来看一下它的实现类

public class BankImpl implements Bank {
@Override
public void lock() {
    //保存这条记录
}
@Override
public void unlock() {
    if ((BankState.Day == getCurrentState())) {
        //白天解正常
        //仅仅保存这条记录
    } else if (BankState.Night == getCurrentState()) {
        //晚上解锁,可能有问题
        //保存这条记录,并报警
        doAlarm();
    }
}
@Override
public void doAlarm() {
    if ((BankState.Day == getCurrentState())) {
        //白天报警,联系当地警方,并保留这条记录
    } else if (BankState.Night == getCurrentState()) {
        //晚上报警,可能有事故,不仅联系当地警方,还需要协调附近的安保人员,并保留这条记录
    }
}
private BankState getCurrentState() {
    return BankState.Day;
}
}

显然,我们涉及到了一个状态:

public enum BankState {
Day,
Night
}

在不同的状态下,同一件事银行可能会作出不同的反应。这样显然很挫,因为在真实业务场景下,业务的状态可能不仅仅只有两种。每多一种,就要多写一个if...else。所以,如果按照状态模式,可以这样来重构:

public class BankDayImpl implements Bank {
@Override
public void lock() {
    //保存这条记录
}
@Override
public void unlock() {
    //白天解锁正常
    //仅仅保存这条记录
}
@Override
public void doAlarm() {
    //白天报警,联系当地警方,并保留这条记录
}
}
public class BankNightImpl implements Bank {
@Override
public void lock() {
    //保存这条记录
}
@Override
public void unlock() {
    //晚上解锁,可能有问题
    //保存这条记录,并报警
    doAlarm();
}
@Override
public void doAlarm() {
    //晚上报警,可能有事故,不仅联系当地警方,还需要协调附近的安保人员,并保留这条记录
}
}

2. Mediator模式

在本文的第一段的代码中,其实是ZStack 2.0.5版本中某处的代码,它用来防止用户使用Cli时传入不当的参数,导致后面的逻辑运行不正常。为了方便理解,我们可以对其规则做一个简化,并画成图的样子来供大家理解。

如何避免写出糟糕if...else语句

假设这是一个提交定时重启VM计划任务的“上古级”界面(因为好的交互设计师一定不会把界面设计成这样吧...).规则大概如下:

2.1 Simple类型的Scheduler

Simple类型的Scheduler,可以根据Interval,RepeatCount,StartTime来定制一个任务。

2.1.1 当选择Simple类型的任务时,Interval,StartTime这两个参数必填

如何避免写出糟糕if...else语句

2.1.2 当填好Interval,和StartTime,这个时候已经可以提交定时任务了

如何避免写出糟糕if...else语句

2.1.3 RepeatCount是个可选参数

如何避免写出糟糕if...else语句

2.2 Cron类型的Scheduler

Cron类型的Scheduler,可以根据cron表达式来提交任务。

如何避免写出糟糕if...else语句

2.2.1 当填入cron表达式后,这个时候已经可以提交定时任务了

如何避免写出糟糕if...else语句

在这里请大家思考一个问题,如果要写这样的一个界面,该怎么写?——在一个windows类里,先判断上面的可选栏是哪种类型,然后根据文本框里的值是否被填好决定提交按钮属否亮起...这算是基本逻辑。上面还没有提到边界值的校验——这些边界值的校验往往会散落在各个组件的实例里,并通过互相通信的方式来判断自己应该做出什么样的变化,相信大家已经意识到了直接无脑堆if...else代码的恐怖之处了吧。

2.3 使用仲裁者改善它

接下来,我们将会贴上来一些伪代码,方便读者更好的理解这个设计模式


public interface Colleague {

void setMediator(Mediator mediator);

void setColleagueEnabled(boolean enabled);
}

public interface Mediator {

void colllectValueChanged(String value);
}

public interface TextField {
String getText();
}

public interface ValueListener {

void valueChanged(String str);
}

定义了几个接口之后,我们开始编写具体的类:

用于表示Simple和Cron的checkBox

public class CheckBox {
private boolean state;
public boolean isState() {
    return state;
}
public void setState(boolean state) {
    this.state = state;
}
}

Button

public class ColleagueButtonField implements Colleague, ValueListener {
private Mediator mediator;
@Override
public void setMediator(Mediator mediator) {
    this.mediator = mediator;
}
@Override
public void setColleagueEnabled(boolean enabled) {
    setEnable(enabled);
}
private void setEnable(boolean enable) {
    //当true时去掉下划线,并允许被按下
}
@Override
public void valueChanged(String str) {
    mediator.colllectValueChanged(str);
}
}

以及几个Text

public class ColleagueTextField implements Colleague, ValueListener, TextField {
private Mediator mediator;
private String text;
@Override
public void setMediator(Mediator mediator) {
    this.mediator = mediator;
}
@Override
public void setColleagueEnabled(boolean enabled) {
    setEnable(enabled);
}
private void setEnable(boolean enable) {
    //当true时去掉下划线,并允许值输入
}
@Override
public void valueChanged(String str) {
    mediator.colllectValueChanged(str);
}
@Override
public String getText() {
    return text;
}
}

SchedulerValidator的具体实现SchedulerValidatorImpl就不贴上来了,里面仅仅是一些校验逻辑.

接着是我们的主类,也就是知道全局状态的窗口类

public class MainWindows implements Mediator {
private SchedulerValidator validator = new SchedulerValidatorImpl();
ColleagueButtonField submitButton, cancelButton;
ColleagueTextField intervalText, repeatCountText, startTimeText, cronText;
CheckBox simpleCheckBox, cronCheckBox;
public void main() {
    createColleagues();
}

@Override
public void colllectValueChanged(String str) {
    if (simpleCheckBox.isState()) {
        cronText.setColleagueEnabled(false);
        simpleChanged();
    } else if (cronCheckBox.isState()) {
        intervalText.setColleagueEnabled(false);
        repeatCountText.setColleagueEnabled(false);
        startTimeText.setColleagueEnabled(false);
        cronChanged();
    } else {
        submitButton.setColleagueEnabled(false);
        intervalText.setColleagueEnabled(false);
        repeatCountText.setColleagueEnabled(false);
        startTimeText.setColleagueEnabled(false);
        cronText.setColleagueEnabled(false);
    }
}
private void cronChanged() {
    if (!validator.validateCronExpress(cronText.getText())) {
        submitButton.setColleagueEnabled(false);
    }
}
private void simpleChanged() {
    if (!validator.validateIntervalBoundary(intervalText.getText())
            || !validator.validateRepeatCountBoundary(repeatCountText.getText())
            || !validator.validateStartTime(startTimeText.getText())) {
        submitButton.setColleagueEnabled(false);
    }
}
private void createColleagues() {
    submitButton = new ColleagueButtonField();
    submitButton.setMediator(this);
    cancelButton = new ColleagueButtonField();
    cancelButton.setMediator(this);
    intervalText = new ColleagueTextField();
    intervalText.setMediator(this);
    repeatCountText = new ColleagueTextField();
    repeatCountText.setMediator(this);
    startTimeText = new ColleagueTextField();
    startTimeText.setMediator(this);
    cronText = new ColleagueTextField();
    cronText.setMediator(this);
    simpleCheckBox = new CheckBox();
    cronCheckBox = new CheckBox();
}
}

在这个设计模式中,所有实例状态的判断全部都交给了仲裁者这个实例来判断,而不是互相去通信。在目前的场景来看,其实涉及的实例还不是特别多,但在一个复杂的系统中,涉及的实例将会变得非常多。假设现在有A,B两个实例,那么会有两条通信线路: 

如何避免写出糟糕if...else语句

而有A,B,C时,则有6条线路 

如何避免写出糟糕if...else语句

  • 当有4个实例时,将会有12个通信线路

  • 当有5个实例时,会有20个通信线路

  • 以此类推...

这个时候,仲裁者模式的优点就发挥出来了——这些逻辑如果分散在各个角色中,代码将会变得难以维护。

如何避免写出糟糕if...else语句

3. Observer模式

ZStack源码剖析之设计模式鉴赏——三驾马车

https://segmentfault.com/a/1190000012903365

结合本文的主题,其实观察者模式做的更多的是将if...else拆分到属于其自己的模块中。以ZStack的为例,当主存储重连时,主存储模块可能要让模块A和模块B去做一些事,如果不使用观察者模式,那么代码就会都耦合在主存储模块下,拆开if...else也就不太可能了。

改进之前的仲裁者例子

观察者模式一般是通过事件驱动的方式来通信的,因此Observer和Subject一般都是松耦合的——Subject发出通知时并不会指定消费者。而在之前仲裁者模式的例子中,仲裁者和成员之间紧耦合的(即他们必须互相感知),因此可以考虑通过观察者模式来改进它。

如何避免写出糟糕if...else语句

4. Strategy模式

通常在编程时,算法(策略)会被写在具体方法中,这样会导致具体方法中充斥着条件判断语句。但是Strategy却特意将算法与其他部分剥离开来,仅仅定义了接口,然后再以委托的方式来使用算法。然而这种做法正是让程序更加的松耦合(因为使用委托可以方便的整体替换算法),使得整个项目更加茁壮。

看完上述内容,你们掌握如何避免写出糟糕if...else语句的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程网数据库频道,感谢各位的阅读!

您可能感兴趣的文档:

--结束END--

本文标题: 如何避免写出糟糕if...else语句

本文链接: https://www.lsjlt.com/news/67971.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何避免写出糟糕if...else语句
    如何避免写出糟糕if...else语句,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。在写代码的日常中,if......
    99+
    2024-04-02
  • JavaScript如何尽量避免if else嵌套
    小编给大家分享一下JavaScript如何尽量避免if else嵌套,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!尽量避免if else 嵌套例如const&nb...
    99+
    2023-06-27
  • PHP如何使用if...else语句
    这篇文章给大家分享的是有关PHP如何使用if...else语句的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。PHP - if...else 语句请使用 if....else 语句...
    99+
    2024-04-02
  • Java中如何替换if-else语句
    今天就跟大家聊聊有关Java中如何替换if-else语句,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。场景日常开发,if-else语句写的不少吧??当逻辑分支非常多的时候,if-el...
    99+
    2023-06-20
  • 如何使用Shell脚本if else语句
    这篇文章主要介绍“如何使用Shell脚本if else语句”,在日常操作中,相信很多人在如何使用Shell脚本if else语句问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用Shell脚本if els...
    99+
    2023-06-09
  • 如何在Shell中使用if else语句
    如何在Shell中使用if else语句?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。if 语句最简单的用法就是只使用 if 语句,它的语法格式为:if condit...
    99+
    2023-06-06
  • Java如何优雅替换if-else语句
    目录场景1.优先判断条件,不满足及时中断2.策略模式改造3.策略模式+工厂+单例模式,锦上添花场景 日常开发,if-else语句写的不少吧??当逻辑分支非常多的时候,if-else套...
    99+
    2024-04-02
  • 如何避免在Java项目里大批量使用if-else?
    避免大批量使用if-else 可能初学者都会忽略掉一点,其实if-else是一种面向过程的实现。 那么,如何避免在面向对象编程里大量使用if-else呢? 网络上有很多解决思路,有工...
    99+
    2024-04-02
  • MySQL IF ELSE 语句如何在存储过程中使用?
    MySQL IF ELSE 语句在表达式计算结果为 false 时实现基本条件构造。其语法如下 -IF expression THEN statements; ELSE else-statements; END IF;语句必须以...
    99+
    2023-10-22
  • MySQL IF ELSE 语句如何在存储过程中使用
    在MySQL中,可以使用IF ELSE语句在存储过程中进行条件判断和逻辑分支控制。以下是一个示例:```mysqlDELIMITER...
    99+
    2023-10-10
    MySQL
  • 如何用c++表驱动替换if/else和switch/case语句
    目录C++的表驱动法 一、常用示例 二、表驱动法三、C++实现注意 四、实用案例C++的表驱动法 目的:使用表驱动法,替换复杂的if/else和switch/ca...
    99+
    2024-04-02
  • php输出查询语句如何写
    本篇内容介绍了“php输出查询语句如何写”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、常规方法常规的输出查询语句的方法非常简单。我们只需...
    99+
    2023-07-06
  • 如何避免PHP写入txt文件时出现乱码
    在PHP中,当我们将文本数据写入到txt文件时,有时候会出现乱码的问题。这种问题通常是因为编码不一致导致的,下面给出一些具体的方法和代码示例,来避免PHP写入txt文件时出现乱码问题。...
    99+
    2024-04-02
  • Java开发中,如何避免文件读写出现的常见问题?
    Java开发中,文件读写是常见的操作之一。然而,由于各种原因,文件读写可能会出现一些常见问题。这些问题可能会导致数据损坏,程序崩溃等严重后果。因此,在Java开发中,我们需要特别注意文件读写操作,并采取一些措施来避免常见问题的出现。 本文将...
    99+
    2023-09-12
    关键字 开发技术 文件
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作