【问题标题】:How do you assign a variable with the result of a if..else block?如何使用 if..else 块的结果分配变量?
【发布时间】:2010-05-27 21:20:39
【问题描述】:

我与一位同事争论在 if..else 块中分配变量的最佳方法。他的原始密码是:

@products = if params[:category]
  Category.find(params[:category]).products
else
  Product.all
end

我是这样重写的:

if params[:category]
  @products = Category.find(params[:category]).products
else
  @products = Product.all
end

这也可以使用三元运算符 (? :) 用单行符重写,但让我们假设产品分配超过 100 个字符并且不能放在一行中。

这两者中哪一个对你来说更清楚?第一个解决方案占用的空间少一点,但我认为声明一个变量并在三行之后分配它可能更容易出错。我也希望看到我的ifelse 对齐,让我的大脑更容易解析它!

【问题讨论】:

  • 我不是 Ruby 程序员,但我希望您可以将三元运算符(或任何)表达式延伸到多行。
  • 所有以 “我不是 Ruby 程序员,但是……” 开头的答案是什么? 那好吧,不要回答这个问题。我知道这是 5 年前问的……但在 Rails 2.0 大受欢迎之后仍然如此。老实说,我不赞成以道歉开头的所有内容。

标签: ruby coding-style


【解决方案1】:

作为badp's answer 中语法的替代方案,我想建议:

@products = 
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end

我声称这有两个优点:

  1. 统一缩进:每一级逻辑嵌套都缩进两个空格(好吧,也许这只是个人喜好问题)
  2. 横向紧凑性:较长的变量名不会将缩进的代码推到超过 80(或其他)列标记

它确实需要额外的代码行,这我通常不喜欢,但在这种情况下,将垂直极简主义换成水平极简主义似乎是值得的。

免责声明:这是我自己的独特方法,我不知道它在 Ruby 社区其他地方的使用程度。

编辑:我应该提一下matsadler's answer 也与这个类似。我确实认为有 some 缩进是有帮助的。我希望这足以证明将其作为单独的答案是合理的。

【讨论】:

  • 正是我搜索的内容。在大多数情况下,这是最好的解决方案。
  • 最重要的是,我喜欢 Ruby,因为它美丽富有表现力,这意味着我可以将想法快速转化为软件,这实际上是一种乐趣阅读和操作。这种语法体现了 Ruby 的这一方面 - 太棒了!
【解决方案2】:

作为一名 Ruby 程序员,我发现第一个更清晰。它清楚地表明整个表达式是一个赋值,赋值的东西是基于某种逻辑确定的,它减少了重复。对于不习惯一切都是表达的语言的人来说,这看起来很奇怪,但是为不了解该语言的人编写代码并不是 IMO 的重要目标,除非他们特别是您的目标用户。否则,人们应该对它有一个短暂的熟悉。

我也同意bp's suggestion 的观点,您可以通过缩进整个 if 表达式使其更清晰地阅读,以便它在视觉上位于赋值的右侧。它完全是美学的,但我认为这使它更容易浏览,即使对于不熟悉该语言的人来说也应该更清楚。

顺便说一句:这种if 并不是Ruby 独有的。它存在于所有的 Lisp(Common Lisp、Scheme、Clojure 等)、Scala、所有的 ML(F#、OCaml、SML)、Haskell、Erlang 甚至 Ruby 的直接前身 Smalltalk。它在大多数人使用的基于 C(C++、Java、C#、Objective-C)的语言中并不常见。

【讨论】:

  • 是的,但这不是更容易出错吗?特别是如果随着时间的推移 if..else 块变得更长。 Ruby 非常宽松(不要让我开始讨论可选括号),但这并不总是更好。
  • @Pierre:按照这种逻辑,函数也必须更容易出错,因为它们也可以变得更大。或者就此而言,包含作业的行也可能会增长(我实际上已经看到了这样的事情)。 Ruby 代码天生就容易出错,但我看不出函数式 if 表达式比命令式更容易出错。
  • Python 也是 :) 虽然语法略有不同:products = Category.find(params["category"]).products() if params["category"] else Products.all()
【解决方案3】:

我不喜欢您在第一个块中使用空格。是的,我是 Pythonista,但我相信我说第一个 可能 在其他代码中间看起来令人困惑,也许在其他 if 块周围时,我的观点是公平的。

怎么样...

@products = if params[:category] Category.find(params[:category]).products
            else                 Product.all
            end

@products = if params[:category]
              Category.find(params[:category]).products
            else                
              Product.all
            end

你也可以试试……

@products = Product.all #unless a category is specified:
@products = Category.find(params[:category]).products if params[:category]

...但是如果 Product.all 实际上是一个类似函数的函数,那么这就是一个坏主意。

【讨论】:

  • all 在这种情况下会运行一个数据库查询,所以第二个不是一个好主意。如果您使用 DataMapper,all 将被延迟评估,因此这只会对该框架进行一次查询。
  • TBH 我发现你的第二个例子比 OP 的第一个带有“错误”空格的块可读性差(顺便说一句,这仍然是一个有效的语句)。
  • 我不会以 that 的方式缩进(这与编写条件语句的通常方式相去甚远,一目了然),但我同意缩进它是正确的等号有助于提高可读性。
  • 我绝对这么认为。这对我来说非常清楚,即使是一目了然。
  • -1 不,错。这不是在 Ruby 中完成的。不是任何核心 Ruby/stdlib 代码,不是任何 Ruby 书籍,不是任何流行的社区编写的库/框架。对不起。您可能只需要更好的语法突出显示和/或视觉检查再培训。
【解决方案4】:

封装...

@products = get_products

def get_products
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

【讨论】:

  • 如果他只使用一次我不建议添加新方法。
  • 如果分配给@products 是原始方法中唯一发生的事情怎么办?看来你在回避这个问题。 ;-)
  • molf - 如果是这种情况,他有一个命令/查询违规,应该将代码重构为我写的内容。
  • 废话。除了更改对象的状态之外,您的示例仍会将数据返回给调用者( 发出数据库查询)。您的重构不会改变任何事情。如果你喜欢严格的 CQS,Ruby 的隐式 return 不是你的朋友。
  • @klew 为什么不呢?这是鲁比。方法应该被大量使用,以帮助程序员组织代码来传达底层逻辑——而不是在 80 年代早期的大型机上运行的 Smalltalk,每个方法调用都会对性能造成重大影响。
【解决方案5】:

只是另一种方法:

category = Category.find(params[:category]) if params[:category]
@products = category ? category.products : Product.all

【讨论】:

  • 干净、美观、分离的关注点。这是我喜欢编写和查看的那种 Ruby 代码。
【解决方案6】:

另一种方法是使用块来包装它。

@products = begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

这解决了分配问题。不过,对于这种“复杂”的代码来说,它的行数太多了。如果我们只想初始化变量一次,这种方法会很有用:

@products ||= begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

这是你用重写的代码无法做到的事情,而且它是正确对齐的。

【讨论】:

    【解决方案7】:
    @products =
    if params[:category]
      Category.find(params[:category]).products
    else
      Product.all
    end
    

    是另一种选择,它既避免重复 @products 又保持 ifelse 对齐。

    【讨论】:

    • 我以前从未见过这个,但它很有创意!
    【解决方案8】:

    假设您的模型如下所示:

    class Category < ActiveRecord::Base
      has_many :products
    end
    class Product < ActiveRecord::Base
      belongs_to :category
    end
    

    你可以做一些更疯狂的事情,像这样:

    #assuming params[:category] is an id
    @products = Product.all( params[:category] ? {:conditions => { :category_id => params[:category]}} : {})
    

    或者,您可以使用性感、延迟加载的 named_scope 功能:

    class Product < ActiveRecord::Base
      ...
    
      #again assuming category_id exists
      named_scope :all_by_category, lambda do |cat_id|
        if cat_id
          {:conditions => {:category_id => cat_id}}
        end
      end
    
      #if params[:category] is a name, and there is a has and belongs to many
      named_scope :all_by_category, lambda do |cat_name|
        if cat_name
          {:joins => :categories, :conditions => ["categories.name = ?",cat_name]}
        end
      end
      ...
    end
    

    习惯了

    @products = Product.all_by_category params[:category]
    

    【讨论】:

    • 这是扭转问题的好方法!但我希望辩论更多地围绕语法而不是代码的实际功能。但我会好好记录的!
    • 你提出的两个选项中,我更喜欢第一个。
    • 我认为您的命名范围令人困惑。我希望Product.all_by_category(nil) 只返回那些不属于任何类别的产品,而不是所有 产品。
    • 我试图构建一个 named_scope,其行为类似于 Daniel Ribeiro 的 retrieve_products,但你是对的,它的行为令人困惑。我的想法是 a) 我希望发现者都在 Product 类上,因为这就是行动的目的。 b) 我通常更喜欢将尽可能多的行为推入模型中。
    【解决方案9】:

    第一个如果使用三元,第二个如果不是。

    第一个几乎无法阅读。

    【讨论】:

    • 是的,但是如果一行太长,你会做多行三进制吗?我觉得不合适!
    • 为什么几乎无法阅读?您是否有很多编写函数式 Ruby 代码的经验,还是因为您正试图将 Ruby 作为另一种语言来阅读?
    • @Chuck:第一次看到它的每个人都“几乎无法阅读”。
    • 几乎不可能阅读纯粹是一种观点,显然。我很少用 Ruby 编程(尽管我非常喜欢它),而且它在我看来非常干净。
    • 我对可读性的不满之一是在不需要时将声明与赋值分开。恕我直言,变量应该尽可能晚地声明,并尽可能接近它们的声明。此处的分隔是不必要的,并且已通过已接受答案中的空白清理来修复。
    【解决方案10】:

    我也不是 Ruby 人,但警报会立即响起第二个命令的范围,在你的 if 块结束后该变量是否可用?

    【讨论】:

    • 是的,它会的。它是一个实例变量。
    • 在 ruby​​ 中,如果您在变量前面加上 @,它会使变量成为在块外可用的实例变量。
    • 除此之外,Ruby 没有块作用域(传统意义上的块,本例中为 if/then/else)。
    【解决方案11】:

    我会说第二个版本对于不熟悉 ruby​​ 结构的人来说更具可读性。所以+为它!另一方面,第一次施工更干燥。

    随着时间的推移,我发现第一个解决方案更有吸引力。我是一名 ruby​​ 程序员,但我之前没有使用它。我肯定会开始的!

    【讨论】:

      【解决方案12】:

      我认为最好的代码是:

      @products = Category.find(params[:category])&.products.presence || Product.all
      

      de find 后的“&”确保方法“products”在 category 为 nil 时不会求值。

      【讨论】:

        【解决方案13】:

        在我看来,第二个对于典型的程序员来说更易读。我不是一个红宝石人,所以我没有意识到 if/else 返回一个值......所以,以我为例(是的,这是我的观点:D),第二个看起来像一个不错的选择。

        【讨论】:

        • 你说你自己不懂Ruby,那我们为什么要拿你来举例呢?您编写代码是为了迎合不懂任何编程语言且只会说藏语的人吗?
        • @Chuck 他用 Ruby 编写的“hello world”程序可能有 2000 行长,只是为了确保它足够冗长,以至于任何了解任何编程或书面语言的人都能理解它。你知道……而不是 1 行长。
        【解决方案14】:

        如果您正在浏览代码,我会说第二个代码块(您的)绝对是我认为最容易快速理解的代码块。

        你的伙伴的代码很好,但是正如 bp 指出的,缩进在这个意义上会产生很大的不同。

        【讨论】:

          【解决方案15】:

          我不喜欢您在第一个块中使用括号。是的,我是 LISP 用户,但我相信我说的很公平>

          怎么样...

          @products = (if (params[:category])
                  ((Category.find params[:category]).
                      products)
              else
                  (Product all)
              end
          )
          

          (^ 诙谐,加剧@badp's answer 的问题)

          【讨论】:

          • 就是这样... lispy...虽然喜欢它:)
          【解决方案16】:

          这也可以通过使用三元运算符 (? :) 的单行符重写,但我们假设产品分配超过 100 个字符并且不能放在一行中。

          让我们假装没有——因为在许多情况下,优化变量名和条件复杂性的长度会为可读性带来回报,以便赋值包括。条件适合一行。

          但不要使用三元运算符(unnecessary in Ruby 且难以阅读),而是使用 if … then … elsethen(或;)在将它们全部放在一行中时是必需的。像这样:

          cat = params[:category]
          @products = if cat then Category.find(cat).products else Product.all end
          

          为了可读性和自记录代码,我喜欢我的程序读起来像英文句子。到目前为止,Ruby 是我找到的最好的语言。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-03-05
            • 1970-01-01
            • 2015-05-24
            • 2015-12-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多