在编码时,我注意到当else块只有一行代码时,我开始养成不使用if-else语句的习惯。例如,如果我有可以这样解决的代码:

public Person(int initialAge) {
    if(initialAge < 0){
        System.out.println("Age is not valid, setting age to 0.");
        age = 0;
    }
    else{
        age = initialAge;
    }
   }


我将完全删除else语句,以减少几行代码。这导致我的代码看起来更像这样:养成习惯,或者继续这样做是否可以。当我进入更复杂的程序时,这样做会成为一个更大的问题吗?提供的代码段是编码挑战的一部分,其中要求之一就是将输入的任何负数设置为0。

评论

是的,这是一个坏习惯,因为这会使您的代码更难以理解(将来,当您回顾代码时,这对于其他人和您自己来说)。 if-else非常简单;但是,不是。

在您的特定情况下,if-else设置会更好。但是,似乎您应该抛出异常,而不是将其设置为0

很难理解的是,您正在使用initialAge设置年龄,然后再检查其值。

@AhmedMasud Java 8具有使用int的方法,就好像它们是未签名的一样。但是您不能声明一个本身没有符号的原语。请参阅stackoverflow.com/q/25556017/1310566

如果年龄是最终年龄,则第二种可能性无效,因此这可能不是可行的选择。

#1 楼


如果值无效,则应引发异常。根据Simon Forsberg的建议,您可以抛出IllegalArgumentException,或者如果要自定义它,可以创建用户定义的异常。
您应该完全避免这种习惯,因为在所提供的问题中,您只有一段简单的代码,在某些领域中,您需要处理复杂的数据结构和值/数据库交互。如果您继续遵循这种习惯,那么总有一天,您将在检查事物之前初始化要返回的值,之后便会陷入您可能没有想到的异常陷阱。


#2 楼

我在这里看到的最大问题是,您正在向System.out打印警告消息,然后使用听起来像是Exception的东西的默认值。

为什么根本允许负值?

if (initialAge < 0) {
    throw new IllegalArgumentException("Initial age cannot be negative, was specified as " + initialAge);
}
age = initialAge;


这样,始终由调用者决定是否传递有效年龄,而不是默认使用零值的方法,否则会带来其他问题。最好是因为有错误而使程序早点中断,而不是打印出可能表明有错误的警告消息。

评论


\ $ \ begingroup \ $
注意:如果绝对必须在选项之间进行选择。与if-else一起使用。我在回答中没有提及这一点,因为我不认为这是正确的方法。正确的方法是让调用者确定默认值(如果出于某种奇怪的原因,它是负数)。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 18'在17:41

\ $ \ begingroup \ $
年龄无效,将年龄设置为0并且0也不是有效年龄
\ $ \ endgroup \ $
– Hanky Panky
16 Dec 20'在10:40

\ $ \ begingroup \ $
@HankyPanky如果您刚出生,可能是。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 20'在11:15

\ $ \ begingroup \ $
你好@SimonForsberg,感谢您的答复!我提供的代码段是我提交编码挑战的一部分。要求之一是将输入的任何负数设置为0。
\ $ \ endgroup \ $
–罗斯·威尔基(Russ Wilkie)
16 Dec 20 '14:37

\ $ \ begingroup \ $
@RussWilkie仍然可以在Person构造函数之外完成。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 20'在15:12

#3 楼

您将主要功能放在输入验证之前。首先验证

if(!foo) {
  throw new IllegalArgumentException("not foo");
}
doStuff();


,或者先将if-else与主要功能配合使用

if(foo) {
  doStuff();
} else {
  handleWrongFoo();
}


最丑对我来说,您解决方案的一部分是做无效的事情,然后“不,请稍候”。

do {
  doStuff();
} ormaybeif(!foo) {
  handleWrongFoo();
}


#4 楼

正如其他人指出的那样,您可能应该在这里抛出异常。一个较小的风格问题是,如果可能的话,我通常将程序的主要流程放在if部件中,而将非标准处理放在else部件中。
if (initialAge >= 0) {
  age = initialAge;
} else {
   System.out.println("Age is not valid, setting age to 0.");
   age = 0;
}


::

if (initialAge >= 0) {
  age = initialAge;
} else {
  throw new InvalidParameterException("Person(): invalid parameter. initialAge = " + initialAge)
}


我也更喜欢if (...),在if和左括号之间有一个空格。这有助于将ifwhen和其他语言语句与没有空间的方法调用区分开。

评论


\ $ \ begingroup \ $
哪条语句的位置(先是普通情况还是先是边缘情况)是基于观点的。在正常情况下抛出异常的积极之处在于,那么您不需要其他
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 19'在8:44

\ $ \ begingroup \ $
如果不能使用elseif,则将主流放入。我通常会像if(wrong){throw new Exception1} elseif(also_wrong){throw new Exception2} else {everything =“ OK”}
\ $ \ endgroup \ $
–丹·查尔蒂尔(Dan Chaltiel)
16 Dec 19'在10:46

\ $ \ begingroup \ $
怎么样:如果(OK){doNormal(); }否则,如果(wrongType1){抛出新的Type1Exception(); } else {抛出新的OtherTypeException(); }。不是重点,更多的是风格问题。
\ $ \ endgroup \ $
–rossum
16/12/19在11:56



#5 楼

这取决于情况,但是对于代码中提到的情况,不仅会降低性能,还会降低代码的可读性。性能问题是由于在某些情况下两次设置了变量。作为代码阅读器,我想知道为什么还要再次更改设置变量(出于代码可读性)。

评论


\ $ \ begingroup \ $
我会说性能问题并不是真正的问题,这是无关紧要的。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 18'在19:47

\ $ \ begingroup \ $
@SimonForsberg嗨,西蒙,您的回应吸引了我。为什么性能不会成为问题?这是否主要是因为示例中的外围问题超过了任何性能问题,还是与类似的编码示例也不相关?
\ $ \ endgroup \ $
–罗斯·威尔基(Russ Wilkie)
16年12月21日在15:56

\ $ \ begingroup \ $
@RussWilkie这是因为计算机如此之快,您几乎无法分辨出它们之间的区别。也许如果您执行十亿次,则可能节省了几毫秒。无论您将值设置一次还是两次,它都是非常便宜的恒定时间操作。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16年12月21日在17:17

#6 楼

对于某些情况,我仍然使用no-else结构,发现它比常规的if / else更干净。当然,该示例并没有避免其他情况,但是确实说明了我使用的顺序:默认,好情况,失败情况。

function Person(int initialAge) 
{
    age = 0 ; // initialise to the default value

    if (initialAge > 0)    // if passed a good value
    {
        age = initialAge ; // use it 
    }
    else  // but if passed-in value not good, complain
    {
        System.out.println("Supplied age not valid, age set to 0.");
    }
}


#7 楼

其他人已经指出了这种编码习惯中的缺陷,但是它有一个好处。如果性能对您很重要,那么这应该是一种不错的编码做法。

现代处理器会进行大量预测以帮助优化执行。您添加的每个分支都会使此预测变得更难或更不可能,这会损害性能。 if语句是一个分支,else语句是另一个分支。如果跳过else语句并抢先执行if语句之前在else语句内执行的操作,那么这将使处理器更容易进行预测,从而提高性能。当然,这取决于else语句中的操作有多昂贵。只要将值分配给几个变量就可以了。

资料来源:我的老师在游戏组装的优化课程中学习游戏编程。我知道有些程序员会嘲笑这类“微优化”是不必要的或毫无意义的,但是性能在游戏开发中非常重要。如果这段代码每帧要运行数千次,那么进行这种优化是非常值得的。

编辑:性能的提高可能会受到所用语言的影响。本课程以C ++进行,具有出色的性能,广泛用于游戏开发中。

#8 楼

我会同意这是个坏习惯,我总是尝试寻找if-else语句的替代方法。

我的第一次尝试是:

class Person {
    private final int age;

    /**
     *  Creates a new person with age 0.
     */
    Person() {
        age = 0;
    }

    /**
     * Creates a new person with age
     * @param age the age of the person. Must be <code>>0</code>
     */
    Person(int age) {
        assert age>0;
        this.age = age;
    }
}


第二次尝试将由一个接口和两个类组成:

/**
 * Anything with an age.
 */
interface HasAge {
    /**
     * @return the age
     */
    int getAge();
}



/**
 * A Person has name, gender, address etc
 */
class Person {
}


/**
 * A Person with a fixed age.
 */
class PersonWithAge extends Person implements HasAge{
    private final int age;

    PersonWithAge(int age) {
        this.age = age;
    }

    @Override
    public int getAge() {
        return age;
    }
}


评论


\ $ \ begingroup \ $
为什么要为此实现一个接口?另外,断言语句通常会被JVM忽略,除非您在启用断言的情况下运行(不在生产​​环境中)
\ $ \ endgroup \ $
–西蒙·福斯伯格
16 Dec 19'在20:39

#9 楼

正如其他人指出的那样,可能存在问题,但这取决于上下文。在某些情况下,上述模式可能是最有利的。


有人提到使用语言支持的异常处理,但是a)并非所有语言都有它们,否则编码人员可能不知道如何处理。正确使用它们,b)在给定的情况下,异常处理可能会引入过多的开销(就像在某些情况下可能相反地提高性能一样)。
该模式可能会降低运行时性能,但可能会改善它。首先,在某些语言中,可以使编译器编译类似于模式的else语句,反之亦然;但是,乍一看确实会发现模式可能会减慢速度,因此编译器可能不会这样做,也可能不会将其删除。并非所有的语言环境都有编译器。这意味着在某些情况下,只有避免使用该模式(或手动放置),才能提高性能。让我们看一下模式(无其他)可以提高性能的一些原因。答:对于解释性场景,在所有其他条件相同的情况下,减少语言标记的数量将节省时间。 B:由于各种原因,机器指令中存在的分支越多,通常执行越差的cpus。原因是在进行分支时,常常必须冲洗管道。 cpu中嵌入了预测分支算法。在某些情况下,这些分支甚至可能具有特定的分支来提高性能,但是通常您希望减少紧密循环中的分支数量,以提高性能(并且上述模式可以满足该要求)。有时可以免费拥有切出分支的权限,但是,使用这种模式,可能会产生成本,例如额外时间来设置变量。但是,如果额外的写入很少发生,则可以通过减少分支机构来弥补这些成本。通常,尽管如此,您将这种测试保留到最后。除非您对给定的情况有其他预期,否则这种模式很可能会使性能变差,或者更有可能只是一种或两种方式都没有实际的性能影响。
代码可读性:可以采用或不采用图案都更清洁。这取决于具体情况。如问题中所述,它确实减少了代码量。缩进层少了又可以使特定代码段看起来更井井有条,更易于遵循。
更正确的程序:如建议的一个答案所述,您可能更希望尽早发现错误在设计阶段;但是,并不是所有的编程任务都花时间。在时常压力之下,有时使用额外的保护层进行编程可以以最少的问题最快地解决所需的问题。在避免其他Bug的负面影响的同时,引入或隐藏难以检测的Bug的更大机会可能被认为不那么重要。在给定的情况下,确保在代码的此部分中设置“年龄”可能非常重要,并且通过尽早在单个位置进行设置,可以避免在添加额外的或更长的分支(可能会失败)期间避免将来的错误
有些投诉是针对关于问题所用代码的假设(“年龄”代表什么,平均情况是什么,等等),但更多的是反对。从根本上讲,这个问题是关于一种通用实践的,所以这些抱怨不是针对该模式的抱怨。当考虑您的整体生产率时,可能是最好的选择。
随着我们步入一个世界,在这个世界中,机器人和计算机变得比人类更擅长于创造性的工作,并有一天要奴役人类,这一威胁越来越大,编写的代码根本无法遵循模式或使用语言的特性来澄清意图,这可能会结束成为人类的主要防御手段。我建议将这种样式的代码随处散布作为廉价保险。在那些时候,我会遵循某些代码的模式,而不是在其他地方遵循它。 [[如果您担心自己在工作中流离失所,这也可能会派上用场,但可能适得其反。您已被警告。]


评论


\ $ \ begingroup \ $
欢迎使用CodeReview!请花一点时间阅读如何回答问题的指南。具体来说,请尝试在答案中提供新的内容,而不仅仅是从其他所有答案中借用。顺便说一句,在这个问题上,我认为性能或机器人不是什么大问题。
\ $ \ endgroup \ $
–avojak
16年12月21日在19:19

\ $ \ begingroup \ $
我提到的许多要点没有在其他地方提及。特别是关于性能以及关于时间限制/避免特定错误如何使这种模式更出色的讨论(例如,在设置变量方面很明显)。我引用了其他注释以增加连续性和上下文(点/对点),并且我意识到它们的价值,但显示出存在其他可能不适用的情况。 [整个帖子都在提供其他评论遗漏的场景]。我仍然希望此评论至少对其他一些读者有价值,因为它带来了新的观察结果,并带来了轻松的触感。
\ $ \ endgroup \ $
–Jose_X
16 Dec 25 '13:09

#10 楼

可能会出现严重错误的示例:您有多个线程可以访问变量foo。设想一种情况,首先将线程1中的foo设置为else值,然后将CPU交给线程2,该线程2在线程1能够设置正确的值之前从foo读取错误的值。

当然,有很多方法可以避免这种情况,但是老实说-每个人都会犯错误。当错误是在复杂的计算中使用稍有错误的值时,祝您好运调试。

奖金信息:C ++和Java(以及其他语言?)都允许您为单个语句省略else的花括号,例如:

if (foo) 
    bar = 1;
else
    bar = 2;


评论


\ $ \ begingroup \ $
使用if-else语句不会使代码不容易受到线程错误的影响。另外,我强烈建议您不要使用花括号-如果您不小心,花括号可能很容易导致难以发现的错误。
\ $ \ endgroup \ $
–avojak
16 Dec 19'在4:14

\ $ \ begingroup \ $
@Drascam可以随时执行bar = foo吗? 1:2 ;.三元的万岁
\ $ \ endgroup \ $
– A T
16 Dec 19'在5:23

\ $ \ begingroup \ $
问题中的代码是在构造函数中编写的,直到构造函数完成后该对象才可用,只要它没有从构造函数中泄漏出来(在这种情况下就不会出现)。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16年12月19日在8:43