摆脱丑陋的if语句

kof*_*cii 60 java coding-style

我有这个丑陋的代码:

if ( v > 10 ) size = 6;
if ( v > 22 ) size = 5;
if ( v > 51 ) size = 4;
if ( v > 68 ) size = 3;
if ( v > 117 ) size = 2;
if ( v > 145 ) size = 1;
return size;
Run Code Online (Sandbox Code Playgroud)

如何摆脱多个if语句?

mfl*_*yan 160

这种方法怎么样:

int getSize(int v) {
    int[] thresholds = {145, 117, 68, 51, 22, 10};

    for (int i = 0; i < thresholds.length; i++) {
        if (v > thresholds[i]) return i+1;
    }
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

功能:(在Scala中演示)

def getSize(v: Int): Int = {
  val thresholds = Vector(145, 117, 68, 51, 22, 10)
  thresholds.zipWithIndex.find(v > _._1).map(_._2).getOrElse(0) + 1
}
Run Code Online (Sandbox Code Playgroud)

  • +1,非常聪明.但乍一看恕我直言的可读性较差. (51认同)
  • 这种方法的好处在于它可以很容易地扩展到更多步骤或更宽范围. (10认同)
  • 这不太可读.使用if/elseif解决方案,代码可以立即理解.这段代码虽然易于理解,但仍需要一点点思考.完全相同数量的代码行,但需要更多的努力来读取==不太理想的解决方案IMO. (6认同)
  • 我也喜欢这个.为了安全起见,我会发表评论以防有人添加/更改`steps`而没有意识到顺序很重要.或者,在循环之前对"steps"进行反向排序也可以. (5认同)
  • 缺少最后一个return语句,这将无法编译(在java中) (2认同)
  • 我不喜欢这个想法,如果你必须改变并且22回17和51返回50怎么办?代码确实发生了变化,这是一场维护噩梦. (2认同)

bar*_*jak 88

使用NavigableMapAPI:

NavigableMap<Integer, Integer> s = new TreeMap<Integer, Integer>();
s.put(10, 6);
s.put(22, 5);
s.put(51, 4);
s.put(68, 3);
s.put(117, 2);
s.put(145, 1);

return s.lowerEntry(v).getValue();
Run Code Online (Sandbox Code Playgroud)

  • @JUST:我承认我不会在这么简单的情况下使用我的解决方案,而是一堆if语句.但是,如果需要一些灵活性(例如,如果这些数字不是硬编码的,而是来自配置文件),那么这个解决方案是有意义的.另外,如果键盘的"f"键坏了,这个解决方案会很有用:) (14认同)
  • 嗯,非常难看,直到你看到开销. (11认同)
  • 抽象规则:规则1:不要这样做.规则2(仅限专家):不要这样做. (6认同)
  • 非常好的解决方案(+1),实际上我会说这是唯一的非丑陋解决方案 (5认同)
  • `HashMap`在这里不起作用.`NavigableMap`允许为给定值找到最近的键(更低或更高),这在我们的例子中很重要. (4认同)
  • @badp如果您这么认为,那么OOP显然不是适合您的概念(因此Java不是正确的语言) (3认同)
  • 如果你像OP那样保留键,你可以使用lowerEntry而不是floorEntry.这样,数据与OP一致. (2认同)
  • 这实际上是一个非常难看的解决方案.更简单总是更好.为什么要滥用TreeMap来模拟ifs?一般的解决方案是将值放在数组中并进行二进制搜索. (2认同)
  • @seanizer:请学习一些历史,最好是一些理论.抽象数据类型不需要OOP,并且呈现从下面的工作中抽象出来的干净界面的概念已经存在了至少50年. (2认同)

Ale*_*ing 81

OPs解决方案最明显的问题是分支,所以我建议进行多项式回归.这将在表单上产生一个很好的无分支表达式

size = round(k_0 + k_1 * v + k_2 * v^2 + ...)
Run Code Online (Sandbox Code Playgroud)

你当然不会得到一个确切的结果,但如果你能容忍一些偏差,那么这是一个非常高效的选择.由于对于v<10无法用多项式建模的值的原始函数的"保留未修改"行为,我冒昧地假设该区域的零阶保持插值.

对于具有以下系数的45度多项式,

-9.1504e-91 1.1986e-87 -5.8366e-85 1.1130e-82 -2.8724e-81 3.3401e-78 -3.3185e-75  9.4624e-73 -1.1591e-70 4.1474e-69 3.7433e-67 2.2460e-65 -6.2386e-62 2.9843e-59 -7.7533e-57 7.7714e-55 1.1791e-52 -2.2370e-50 -4.7642e-48 3.3892e-46 3.8656e-43 -6.0030e-41 9.4243e-41 -1.9050e-36 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23  6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23 6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 3.6100e-01 -6.2117e-01 6.3657e+00
Run Code Online (Sandbox Code Playgroud)

,你得到一个漂亮的曲线:

替代文字

正如您所看到的,在0到200*的整个范围内,您只能获得1.73的1-norm误差!

*结果v?[0,200]可能会有所不同.

  • 这就是功能编程的工作原理? (9认同)
  • 哇!你能详细说明你用来产生输出的工具吗? (2认同)
  • +1 非常好,但有点难以理解它的作用并且更难以维护。 (2认同)

Jig*_*shi 75

if ( v > 145 ) size = 1;
else if ( v > 117 ) size = 2;
else if ( v > 68 ) size = 3;
else if ( v > 51 ) size = 4;
else if ( v > 22 ) size = 5;
else if ( v > 10 ) size = 6;

return size;     
Run Code Online (Sandbox Code Playgroud)

这对你的情况更好.

您可以选择尽可能选择Switch Case

Update: 如果你已经分析了'v'的值,那么在大多数情况下,你通常会在较低的范围内(<10),而不是你可以添加它.

if(v < 10)           size = SOME_DEFAULT_VALUE;
else if ( v > 145 )  size = 1;
else if ( v > 117 )  size = 2;
else if ( v > 68 )   size = 3;
else if ( v > 51 )   size = 4;
else if ( v > 22 )   size = 5;
else if ( v > 10 )   size = 6;   
Run Code Online (Sandbox Code Playgroud)

further : 您还可以根据您的分析更改条件序列.如果您知道大多数值小于10,然后在第二位置大多数值介于68-117之间,则可以相应地更改条件序列.

编辑:

if(v < 10)           return SOME_DEFAULT_VALUE;
else if ( v > 145 )  return 1;
else if ( v > 117 )  return 2;
else if ( v > 68 )   return 3;
else if ( v > 51 )   return 4;
else if ( v > 22 )   return 5;
else if ( v > 10 )   return 6;   
Run Code Online (Sandbox Code Playgroud)

  • 更好的是:只需在不使用中间`size`变量的情况下立即返回值. (92认同)
  • @ org.life.java这个"最佳实践"完全是主观的,而且很可能已经过时了(因为它是C语言时代的回归,你必须明确地管理资源).很多时候,通过多个return语句可以获得更易读和可维护的代码.智能编译器负责其余部分.请参阅http://consultingblogs.emc.com/anthonysteele/archive/2008/07/14/a-method-should-have-only-one-return-statement-discuss.aspx (59认同)
  • @org我听说这是最佳实践,但是当它使代码变得尴尬时,对我来说似乎很愚蠢.你有没有证据证明它实际上更好?请注意,这与"这是最佳实践的共识"不同,但证明这是最佳实践. (58认同)
  • @Rene Saarsoo最佳做法是只有一个return语句 (11认同)
  • @seanizer:为了代码"纯度",比使用大量无用的中间步骤更难理解? (7认同)
  • [只有那些有幽默感的人]对于那里的纯粹主义者来说,单个出口点与异常处理不相容.试图捕获每个可能的异常将导致catch-block代码膨胀并将返回远离方法体.为了便于阅读(也就是单个退出点),代码最终无法读取(填充了catch块)."单一出口点"方法是主观的.告诉别人"我更喜欢这种方式"并重新编写代码.当这样说时,没有合理的程序员会吝啬偏好. (6认同)
  • @org我认为当你想要的东西立刻回归时,代码会更"可读".如果我们将可读性定义为使代码的结构和意图对读者显而易见的速度,那么战略回报对我来说是有意义的.**期望**代码只会在程序结束时返回,这是一个问题,也是一个使编程更加费力的人为因素.称之为标准并不合理.我们可以使用CaP并将其称为标准,但这并不合理. (4认同)
  • @seanizer - 它是*程序员*,使代码可读或不可读,而不是刚性必须遵守的Stalanist正统. (3认同)
  • @dan*Stalanist*?你或许意味着*斯大林主义*?我不记得最近几次杀害数百万无辜的人,但后来我的记忆力不如以前那么好...... @ninjalj如果需要很多步骤,那就是代码设计不佳的迹象. (3认同)
  • 我更喜欢`switch`的Javascript实现:你可以做`switch(true){case size> 146:... case size> 117:...` (2认同)
  • 当`v == 9`时返回什么值? (2认同)
  • @RedFilter那是无关紧要的.无论你喜欢与否,java的开关都不会那样工作...... (2认同)
  • @seanizer:您推荐的方法过多,将返回结果保存为临时变量.在内部循环中使用它会变得更加丑陋. (2认同)
  • 如果某个函数中有多个自然退出点(它应该已经不是很多页面或者它违反了其他有用的原则),那么将"期望的退出状态"带到函数末尾所需的工作会使一切变得更加复杂.它还使得确保函数正确执行变得更加困难,因为所有分支都流过整个事物,并且一些条件可能很复杂.在这个问题的代码中,返回很有意义,因为之后什么也没做!没有人可能会对这里发生的事情感到困惑. (2认同)

dhb*_*lah 51

return v > 145 ? 1 
     : v > 117 ? 2 
     : v > 68 ? 3 
     : v > 51 ? 4 
     : v > 22 ? 5 
     : v > 10 ? 6 
     : "put inital size value here";
Run Code Online (Sandbox Code Playgroud)

  • 哈啊!我的眼睛!我的眼睛! (84认同)
  • 你让我和其他人混淆了.我的名字是Bauke Scholtz,而不是耶稣基督.正如评论中所说,我添加了一些换行符.它不那么难看,提高了可读性. (30认同)
  • 你能不能读一下最初的问题?"我怎样才能摆脱多个if语句?" 你可以看到我的答案中没有"if"语句. (13认同)
  • @gasan,当然,你删除了`if's`但是op声称他的代码是"丑陋的",并且打算让它更具可读性......你真的相信这是......更漂亮吗? (12认同)
  • 我喜欢这种形式,并发现它可读,但我认识到大多数人都有问题.我不明白为什么理解链接的`if/else-if/else`结构的人有链接三元`?:`运算符的问题.它们完全相互平行. (12认同)
  • 由于Balus的重新格式化,我觉得这比接受的if/else-if答案更容易阅读.我同意Bert,是的,人们似乎对此有疑问; 我只是不明白*为什么*人们发现三元??:`如此神秘.同意badp,OP的代码或接受的答案都不涉及默认的"其他"情况. (9认同)
  • 在重新格式化的形式中,这比if/elses链更好,更易读.至于让那些不了解三元运算符的人感到困惑 - 好吧,任何不理解的人?:但是应该很好地学习它,任何*不能*清楚地学习它的人作为程序员都会有更深层次的问题. (9认同)
  • 我添加了一些换行符.初学者之间的可读性仍然值得怀疑. (6认同)
  • 如果没有别的,这种形式迫使你写出"默认"的情况.+1 (5认同)
  • @BalusC当然,如果有任何代码可以证明以勋爵的名义徒劳无功,那就是它! (3认同)
  • @gasan:尽管可以回滚编辑,以便你可以吃掉downvotes :)只是有帮助. (3认同)
  • @seanizer:嗯,显然程序员应该学习整个语言(至少是基础知识),对此没有争议.此外,这是一个常见的习惯用来处理仅仅选择一个值并且远非被滥用的链式ifs,它是一个完美的用例.我实际上更喜欢*你的*(显然很有争议,虽然为什么会让我躲开)解决方案,但只是勉强. (3认同)
  • 它在技术上回答了如何摆脱"if"语句的问题,但它将"丑陋"因素增加了几个数量级. (2认同)
  • 看起来只是一行没有意义的字符,它实际上是相当规则和可预测的.有人会认为这很漂亮! (2认同)
  • @ st0le,我认为问题中的代码是丑陋的,没有任何意义,因此更好的解决方案是尽可能小而紧凑,不要惹恼一个人的眼睛(希望,一些理智的开发人员会在最近的将来删除它) .除此之外,这个函数(可能是一个函数)应该内联.最好以简洁的方式来做,而不是通过humpty-dumpty多行 - 多括号代码. (2认同)
  • 哈哈,是的!我喜欢它,对于知道语法的人来说,阅读起来应该不难:D (2认同)
  • @Stephen P - 我认为三元本身最终看起来很神秘,因为它比我们通常喜欢的离英语更远.但是,即使我熟悉并且可能过度使用三元语法,也会弄清楚如何处理链式三元组.我可能会记住,这是它的行为,但是,以供将来参考. (2认同)

Jim*_*ans 23

原始代码对我来说似乎很好,但是如果你不介意多次返回,你可能更喜欢更表格的方法:

if ( v > 145 ) return 1;
if ( v > 117 ) return 2;
if ( v >  68 ) return 3;
if ( v >  51 ) return 4;
if ( v >  22 ) return 5;
if ( v >  10 ) return 6;
return ...;     // The <= 10 case isn't handled in the original code snippet. 
Run Code Online (Sandbox Code Playgroud)

请参阅org.life.java的答案中的多次返回或不讨论.


cbm*_*eks 17

这里有很多答案和建议,但老实说,我认为它们中的任何一个都比原始方法"更漂亮"或"更优雅".

如果你有几十次或几百次的迭代检查,那么我可以很容易地看到一些for循环但老实说,对于你的少数比较,坚持使用if并继续前进.这不是那么难看.

  • 同意.但这些问题让人们展示了替代解决方案.例如,我从未听说过NavigableMap. (6认同)

mha*_*ler 14

return (v-173) / -27;
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,这不正确,只是近似值 (7认同)
  • 大声笑+1我想在生产环境中引入一些像这样的代码作为笑话^ _ ^ (2认同)

st0*_*0le 12

这是我的镜头......

更新:已修复.先前的解决方案给出了精确值的错误答案(10,22,51 ...).如果val <10,则默认值为6

   static int Foo(int val)
    {
                          //6, 5, 4, 3, 2 ,1
        int[] v = new int[]{10,22,51,68,117,145};
        int pos = Arrays.binarySearch(v, val-1);
        if ( pos < 0) pos = ~pos;
        if ( pos > 0) pos --;
        return 6-pos;
    }
Run Code Online (Sandbox Code Playgroud)

  • 这很聪明,但恕我直言比原来更清晰. (12认同)

Eri*_*ikE 11

我还有一个版本供您使用.我真的不认为它是最好的,因为当我100%确定这个函数永远不会是一个性能损失时,它会增加"性能"名称的不必要的复杂性(除非有人在一个紧凑的循环中计算大小一百万次...).

但我提出它只是因为我认为执行硬编码二进制搜索有点有趣.它看起来不是二元-y因为没有足够的元素去深入,但它确实具有在原始帖子中不超过3次测试而不是6次返回结果的优点.返回语句也按大小排序,这有助于理解和/或修改.

if (v > 68) {
   if (v > 145) {
      return 1
   } else if (v > 117) {
      return 2;
   } else {
      return 3;
   }
} else {
   if (v > 51) {
      return 4;
   } else if (v > 22) {
      return 5;
   } else {
      return 6;
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 高效和原创(+1),但维护噩梦 (5认同)
  • 显然是维护噩梦.但想起来很有趣!:) (2认同)

Geo*_*rge 7

7 - (x>10 + x>22 + x>51 + x>68 + x>117 + x>145)
Run Code Online (Sandbox Code Playgroud)

其中7是默认值(x <= 10).

编辑:最初我没有意识到这个问题是关于Java的.此表达式在Java中无效,但在C/C++中有效.我会留下答案,因为有些用户觉得它很有帮助.


Sea*_*oyd 5

这是一个面向对象的解决方案,这个类被称为Mapper<S,T>映射来自任何类型的值,这些类型实现了与任何目标类型相当的.

句法:

Mapper<String, Integer> mapper = Mapper.from("a","b","c").to(1,2,3);

// Map a single value
System.out.println(mapper.map("beef")); // 2

// Map a Collection of values
System.out.println(mapper.mapAll(
    Arrays.asList("apples","beef","lobster"))); // [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

码:

public class Mapper<S extends Comparable<S>, T> {

    private final S[] source;
    private final T[] target;

    // Builder to enable from... to... syntax and
    // to make Mapper immutable
    public static class Builder<S2 extends Comparable<S2>> {
        private final S2[] data;
        private Builder(final S2[] data){
            this.data = data;
        }
        public <T2> Mapper<S2, T2> to(final T2... target){
            return new Mapper<S2, T2>(this.data, target);
        }
    }


    private Mapper(final S[] source, final T[] target){
        final S[] copy = Arrays.copyOf(source, source.length);
        Arrays.sort(copy);
        this.source = copy;
        this.target = Arrays.copyOf(target, target.length);
    }

    // Factory method to get builder
    public static <U extends Comparable<U>, V> Builder<U> from(final U... items){
        return new Builder<U>(items);
    }

    // Map a collection of items
    public Collection<T> mapAll(final Collection<? extends S> input){
        final Collection<T> output = new ArrayList<T>(input.size());
        for(final S s : input){
            output.add(this.map(s));
        }
        return output;
    }

    // map a single item
    public T map(final S input){
        final int sourceOffset = Arrays.binarySearch(this.source, input);
        return this.target[
            Math.min(
                this.target.length-1,
                sourceOffset < 0 ? Math.abs(sourceOffset)-2:sourceOffset
            )
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:最后用更高效(更短)的版本替换了map()方法.我知道:对于大型阵列,搜索分区的版本仍然会更快,但抱歉:我太懒了.

如果您认为这太过臃肿,请考虑以下事项:

  1. 它包含一个构建器,允许您使用varargs语法创建Mapper.我会说这是可用性的必备条件
  2. 它包含单个项目和集合映射方法
  3. 它是不可变的,因此线程安全

当然,所有这些功能都可以轻松删除,但代码不太完整,可用性较低或稳定性较差.

  • Holy Grace Hopper,这是什么怪物?他们只是想收紧一些IF. (30认同)
  • 太复杂了......为什么有人会把对象包含在像原始类型一样简单的事情上呢? (15认同)
  • @seanizer过早的抽象是所有邪恶的根源.不......失去标准库的基准并不是一种耻辱.无论如何*滚动你自己的解决方案*而不是通过标准库得到*某些东西是一个奇怪的选择,特别是当它意味着用54行Java替换6`if`语句时(不计算代码实际上*使用*这个东西) .此外,何时面向对象本身就具有优势? (9认同)
  • 您的答案获得了奖项:http://forums.thedailywtf.com/forums/p/20192/234306.aspx (7认同)
  • 在没有阅读Mapper类的情况下读取代码的直觉会让我认为10个映射到6等等,但事实并非如此.如果我在某个人的代码库中遇到这个问题,我必须阅读并理解你的课程(或者它的文档,如果你不愿意写任何内容)来了解它的真正含义.这比理解if语句链需要更长的时间.当然,你的解决方案更通用,甚至更聪明,但谁说在这种情况下需要? (4认同)
  • 我并不是说这是每个人都应该使用的解决方案,但它是一种有效的方法,当然比我们30或40年前所知道的if/else无聊更原始.我只是说:有不止一种方法可以做到这一点,并且来自OOP和一些功能背景,这是我喜欢的解决方案. (4认同)
  • @seanizer你是否认真相信这比原来更易于维护?当它有意义时,我全都是OOP,但是if语句链已经很容易阅读和修改了. (3认同)
  • 这编译,但有意想不到的结果:`Mapper <Integer,Integer> map = Mapper.from(145,117,68,51,22,10).to(1,2,3,4,5,6); System.out.println(map.mapAll(Arrays.asList(11,23,52,69,118,146)));`返回`[1,2,3,4,5,6]`我在哪里期待完全相反.这将更容易在if语句链中发现和修复. (2认同)
  • @seanizer老兄,现在你变得个性化了.寒意.我并没有要求人们嘲笑你 - 但由于这是你采取它的方式,我只是删除了评论,我为此道歉. (2认同)
  • 好吧,我的也走了.也许是时候有人关闭了这个帖子. (2认同)

Chr*_*gna 5

我的评论能力尚未开启,希望没有人会根据我的回答"正确地"说出来......

漂亮的丑陋代码可以/应该被定义为试图实现:

  1. 可读性(好的,说明显而易见的 - 可能是多余的问题)
  2. 性能 - 充其量只追求最佳,最糟糕的是它并不是一个很大的消耗
  3. 实用主义 - 大多数人做事的方式并不遥远,给定一个不需要优雅或独特解决方案的普通问题,以后改变它应该是一种自然的努力,而不需要太多的回忆.

IMO org.life.java给出的答案是最漂亮的,非常容易阅读.出于阅读和表现的原因,我也喜欢写条件的顺序.

看看关于这个主题的所有评论,在我写作的时候,似乎只有org.life.java提出了性能问题(也许mfloryan,也说明了某些东西会"更长").当然在大多数情况下,并且在这个例子中它不应该明显减慢但是你写它.

但是,通过嵌套条件并最佳地排序条件可以提高性能[值得,特别是如果这是循环的].

所有这一切,通过确定尽可能快地执行所带来的嵌套和排序条件(比您的示例更复杂)通常会产生不太可读的代码,并且代码更难以改变.我再次提到#3,实用主义......平衡需求.