【问题标题】:How to break up long lines of Ruby如何拆分长行的 Ruby
【发布时间】:2012-08-22 23:58:29
【问题描述】:

我总是在我的 Rails models 顶部获得大量的代码。我正在寻找使用标准 Ruby 样式分解它们的最佳方法的建议。例如,我现在正在看的一行是这样的:

delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true

拆分这些长方法调用行的常规样式是什么?

【问题讨论】:

    标签: ruby-on-rails ruby coding-style


    【解决方案1】:

    简短的回答是视情况而定

    基础知识

    首先,您可以使用“新”Ruby 哈希语法保存几个字符:

    result = very_long_method_name(something: 1, user: user, flange_factor: 1.34)
    

    对比

    result = very_long_method_name(:something => 1, :user => user, :flange_factor => 1.34)
    

    哈希/数组

    有时您需要初始化一个数组或散列,尤其是对于散列,最好这样写:

    args = {
      first_name: "Aldo",
      email: "nospam@mail.example.com",
      age: Float::INFINITY
    }
    

    同一行上的相同哈希将是(不那么好):

    args = {first_name: "Aldo", email: "nospam@mail.example.com", age: Float::INFINITY}
    

    各种方法调用

    有些方法需要很多参数,或者这些参数名称很长:

    %table
      %thead
        %th
          %td= t("first_name", scope: "activemodel.lazy_model.not_so_active_model", some_interpolation_argument: "Mr.", suffix: "(Jr.)")
    

    在这种情况下,我可能会这样写:

    %table
      %thead
        %th
          %td= t("first_name",
                 scope: "activemodel.lazy_model.not_so_active_model",
                 some_interpolation_argument: "Mr.",
                 suffix: "(Jr.)")
    

    它仍然不是很漂亮,但我想不那么丑了。

    class person < ActiveRecord::Base
      validates :n_cars, numericality: {
                           only_integer: true,
                           greater_than: 2,
                           odd: true,
                           message: t("greater_than_2_and_odd",
                                      scope: "activerecord.errors.messages")
                         }
    end
    

    再说一遍,不是世界上最漂亮的代码,但它有某种结构。

    此外,有时您可以使用变量来分割行。这只是一个例子,但基本上你命名事物的块(有时在这之后你意识到你实际上可以在一个方法中移动那个块)

    class person < ActiveRecord::Base
      NUMERICALITY_OPTS = {
        only_integer: true,
        greater_than: 2,
        odd: true,
        message: t("greater_than_2_and_odd", scope: "activerecord.errors.messages")
      }
      validates :n_cars, numericality: NUMERICALITY_OPTS
    end
    

    说到块(闭包):

    User.all.map { |user| user.method_name }
    

    可以这样写:

    User.all.map(&:method_name)
    

    如果你有适当的块尝试使用 do-end 而不是花括号:

    nicotine_level = User.all.map do |user|
      user.smoker? ? (user.age * 12.34) : 0.1234
    end
    

    有条件的

    不要将三元 if 运算符用于复杂的事情:

    nicotine_level = user.smoker? ? (user.age * 1.234 + user.other_method) : ((user.age - 123 + user.flange_factor) * 0)
    
    if user.smoker?
      nicotine_level = user.age * 1.234 + user.other_method
    else
      nicotine_level = (user.age - 123 + user.flange_factor) * 0
    end
    

    如果你有这样的复杂 if 语句:

    if user.vegetarian? && !user.smoker? && (user.age < 25) && (user.n_girlfriends == 0) && (user.first_name =~ /(A|Z)[0-1]+/)
    end
    

    在方法中移动东西可能会更好,让东西不仅更短而且可读:

    if user.healthy? && user.has_a_weird_name?
      # Do something
    end
    
    # in User
    def healthy?
      vegetarian? && !smoker? && (age < 25) && (n_girlfriends == 0)
    end
    
    def user.has_a_weird_name?
      user.first_name =~ /(A|Z)[0-1]+/
    end
    

    长字符串

    Heredoc 是你的朋友...我总是需要在谷歌上搜索以获取正确的语法,但一旦你做对了,某些内容会更易于阅读:

    execute <<-SQL
      UPDATE people
      SET smoker = 0
      OK, this is a very bad example.
    SQL
    

    查询

    对于简单的情况,我倾向于这样做:

    # Totally random example, it's just to give you an idea
    def cars_older_than_n_days(days)
      Car.select("cars.*, DATEDIFF(NOW(), release_date) AS age")
         .joins(:brand)
         .where(brand: {country: "German"})
         .having("age > ?", days)
    end
    

    有时查询甚至是最糟糕的。如果我使用squeel 并且查询非常大,我倾向于使用这样的括号:

    # Again, non-sense query
    Person.where {
      first_name = "Aldo" |
      last_name = "McFlange" |
      (
        age = "18" &
        first_name = "Mike" &
        email =~ "%@hotmail.co.uk"
      ) |
      (
        person.n_girlfriends > 1 &
        (
          country = "Italy" |
          salary > 1_234_567 |
          very_beautiful = true |
          (
            whatever > 123 &
            you_get_the_idea = true 
          )
        )
      )
    }
    

    我想说,如果可能的话,尽量避免复杂的查询,并将它们分成更小的范围或其他:

    scope :healthy_users, lambda {
      younger_than(25).
      without_car.
      non_smoking.
      no_girlfriend
    }
    
    scope :younger_than, lambda { |age|
      where("users.age < ?", age)
    }
    
    scope :without_car, lambda {
      where(car_id: nil)
    }
    
    scope :non_smoking, lambda {
      where(smoker: false)
    }
    
    scope :no_girlfriend, lambda {
      where(n_girlfriends: 0)
    }
    

    这可能是最好的方法。

    现实

    不幸的是,人们倾向于写长行,这很糟糕:

    • 排长队难以阅读(印刷书籍没有超大页面是有原因的)
    • 确实,我们主要使用 2 个屏幕,但是当从控制台使用 git diff 之类的东西时,排长队很痛苦
    • 有时您使用屏幕空间较小的 13 英寸笔记本电脑工作
    • 即使我喜欢使用 2 个屏幕,我也喜欢拆分我的编辑器来同时编辑 2 个文件 - 长行迫使我使用水平滚动条(地球上最讨厌的东西)
    • 是的,您可以在编辑器中启用自动换行,但效果仍然不理想(恕我直言)

    我的编辑器中有一把尺子,这样我就知道何时要越过该行的第 80 个字符。 但很少通过几个字符越界,这实际上比拆分更好。

    结论

    在 80 年代以下有几种保持线路的方法,并且通常取决于具体情况。 长线的问题不仅仅是风格不好,长线通常是过于复杂的症状

    【讨论】:

    • 但是什么深哈希例如foo[:bar1][:bar2][:bar3]...我还没有找到一个很好的方法来写几行。
    • 你好@joeloui,你可能会使用反斜杠 (\) 来分割你的行,但我想可能会有点麻烦,所以检查使用它的分割是否仍然有意义并且它的可读性足够。跨度>
    • @joeloui,你可以使用 dig 方法。请看这里ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig 你的例子看起来像foo.dig(:bar1, :bar2, :bar3)
    • 如果第一行太长:nicotine_level = User.all.map do |user: 'default_value, details: { .... } |,你会怎么断?
    【解决方案2】:

    类似的东西:

    delegate :occupation, :location, :picture_url, 
             :homepage_url, :headline, :full_name, 
             :to => :profile, :prefix => true, :allow_nil => true
    

    或者,如果您想突出显示选项哈希(合理的事情):

    delegate :occupation, :location, :picture_url, 
             :homepage_url, :headline, :full_name, 
         :to => :profile, :prefix => true, :allow_nil => true
    

    将所有内容放在一行中的想法让我觉得这是一个疯狂的想法,这意味着您必须滚动任意数量才能查看委派的内容。呃。

    我也可能会整理一些东西,也许按字母顺序排列。

    delegate :full_name, :headline,   :homepage_url,
             :location,  :occupation, :picture_url,
         :to => :profile, :prefix => true, :allow_nil => true
    

    如果文件没有太多/任何其他实质性内容,我可能会将每个方法符号放在自己的行中,以使编辑更容易。在较大的文件中,我不想为此占用空间。

    我从来没有想过这种事情。

    编辑我想我会这样做:/

    这些天我可能会按“相似性”对委托方法进行分组,大致如下:

    delegate :full_name, :headline,
             :location,  :occupation,
             :homepage_url, picture_url,
         to: :profile, prefix: true, allow_nil: true
    

    当值也是一个符号时,我的陪审团坚持使用 1.9 哈希语法;我觉得这看起来很有趣。我也不确定在哪里缩进它——在 IDE 重新格式化期间可能会丢失它,但如果我使用新语法,我有点喜欢上面的样子。

    【讨论】:

    • 哇,你比我更有条理:)
    • @WizardofOgz 更多的是一种疾病,我怀疑。当我不能很容易地阅读东西时,我就会变得脾气暴躁:/
    【解决方案3】:

    虽然这个问题已经有两个很好的答案,但我想将未来的读者推荐给Ruby Style Guide 了解此类问题。

    目前Source Code Layout 部分有大量关于如何在各种情况下换行的信息:

    # starting point (line is too long)
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text)
    end
    
    # bad (double indent)
    def send_mail(source)
      Mailer.deliver(
          to: 'bob@example.com',
          from: 'us@example.com',
          subject: 'Important message',
          body: source.text)
    end
    
    # good
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com',
                     from: 'us@example.com',
                     subject: 'Important message',
                     body: source.text)
    end
    
    # good (normal indent)
    def send_mail(source)
      Mailer.deliver(
        to: 'bob@example.com',
        from: 'us@example.com',
        subject: 'Important message',
        body: source.text
      )
    end
    

     

    # bad - need to consult first line to understand second line
    one.two.three.
      four
    
    # good - it's immediately clear what's going on the second line
    one.two.three
      .four
    

    正如@Aldo 已经提到的,对于过于复杂的代码,它通常是一个“解决方案”:

    # bad
    some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
    
    # good
    if some_condition
      nested_condition ? nested_something : nested_something_else
    else
      something_else
    end
    

    【讨论】:

      【解决方案4】:

      根据我的经验,似乎惯例实际上并不是为了打破界限。我见过的大多数项目,包括 rails 本身的代码库,似乎都没有问题,有很长的不间断行。

      所以我想说,如果您想遵循惯例,请不要打破界限。如果您决心打破界限,那么就没有广泛遵循的惯例来说明如何做到这一点。您可以使用您喜欢的任何编码风格。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-09
        • 2011-11-13
        • 1970-01-01
        • 1970-01-01
        • 2014-07-06
        • 2018-10-12
        • 2012-05-18
        • 1970-01-01
        相关资源
        最近更新 更多