如何分解Ruby的长线

Mat*_*hew 39 ruby coding-style ruby-on-rails

我总是在我的Rails顶部获得大量代码models.我正在寻找建议,以最好的方式打破标准的Ruby风格.例如,我现在看的一行是:

delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true
Run Code Online (Sandbox Code Playgroud)

打破这些长方法调用线的传统方式是什么?

Ald*_*uca 70

简短的答案取决于它.

基本

首先,您可以使用"新"Ruby哈希语法保存少量字符:

result = very_long_method_name(something: 1, user: user, flange_factor: 1.34)
Run Code Online (Sandbox Code Playgroud)

result = very_long_method_name(:something => 1, :user => user, :flange_factor => 1.34)
Run Code Online (Sandbox Code Playgroud)

散列/阵列

有时您需要初始化数组或散列,特别是对于散列,以这种方式编写它们很好:

args = {
  first_name: "Aldo",
  email: "nospam@mail.example.com",
  age: Float::INFINITY
}
Run Code Online (Sandbox Code Playgroud)

同一行上的相同哈希值(不太好):

args = {first_name: "Aldo", email: "nospam@mail.example.com", age: Float::INFINITY}
Run Code Online (Sandbox Code Playgroud)

各种方法调用

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

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

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

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

它仍然不是很漂亮但我觉得不那么难看.

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
Run Code Online (Sandbox Code Playgroud)

再一次,不是地球上最美丽的代码,但它有某种结构.

此外,有时您可以使用变量来分割线条.这只是一个示例,但基本上您可以命名事物块(有时在此之后您会发现实际上可以在方法中移动该块)

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
Run Code Online (Sandbox Code Playgroud)

说到块(闭包):

User.all.map { |user| user.method_name }
Run Code Online (Sandbox Code Playgroud)

可以像这样写:

User.all.map(&:method_name)
Run Code Online (Sandbox Code Playgroud)

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

nicotine_level = User.all.map do |user|
  user.smoker? ? (user.age * 12.34) : 0.1234
end
Run Code Online (Sandbox Code Playgroud)

条件

不要使用三元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
Run Code Online (Sandbox Code Playgroud)

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

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

在方法中移动东西可能更好,并且使事情不仅更短而且可读:

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
Run Code Online (Sandbox Code Playgroud)

长串

Heredoc是你的朋友......我总是需要google以获得正确的语法,但一旦你做对了就会让某些东西更好看:

execute <<-SQL
  UPDATE people
  SET smoker = 0
  OK, this is a very bad example.
SQL
Run Code Online (Sandbox Code Playgroud)

查询

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

# 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
Run Code Online (Sandbox Code Playgroud)

有时查询甚至更糟.如果我使用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 
      )
    )
  )
}
Run Code Online (Sandbox Code Playgroud)

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

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)
}
Run Code Online (Sandbox Code Playgroud)

这可能是最好的方式.

现实

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

  • 长线难以阅读(如果印刷书籍没有超大页面,则有一个原因)
  • 确实,我们大多使用2个屏幕,但是当使用git diff来自长线路控制台的东西时会很痛苦
  • 有时你使用13英寸的笔记本电脑,屏幕更少
  • 即使我喜欢使用2个屏幕,我也喜欢拆分我的编辑器同时编辑2个文件 - 长线迫使我使用水平滚动条(地球上最讨厌的东西)
  • 是的,您可以在编辑器中启用自动换行,但它仍然不是很好(恕我直言)

我的编辑器中有一个标尺,这样我就知道什么时候我要越过第80个字符.但是很少通过几个字符越过它,它实际上比分裂它更好.

结论

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

  • @joeloui,您可以使用dig方法。请在这里查看https://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig您的示例将类似于`foo.dig(:bar1,:bar2,:bar3)` (2认同)

Dav*_*ton 18

有点像:

delegate :occupation, :location, :picture_url, 
         :homepage_url, :headline, :full_name, 
         :to => :profile, :prefix => true, :allow_nil => true
Run Code Online (Sandbox Code Playgroud)

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

delegate :occupation, :location, :picture_url, 
         :homepage_url, :headline, :full_name, 
     :to => :profile, :prefix => true, :allow_nil => true
Run Code Online (Sandbox Code Playgroud)

把所有这些全部放在一行上的想法让我觉得这是一个讽刺性的想法,这意味着你必须滚动任意数量才能看到被委派的内容.EW.

我可能也会对某些内容进行排序,也许是字母顺序排列.

delegate :full_name, :headline,   :homepage_url,
         :location,  :occupation, :picture_url,
     :to => :profile, :prefix => true, :allow_nil => true
Run Code Online (Sandbox Code Playgroud)

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

不是说我曾经想过这种东西.

编辑我想我做:/

这些天我可能会按照"相似性"对委派的方法进行分组,粗略地说:

delegate :full_name, :headline,
         :location,  :occupation,
         :homepage_url, picture_url,
     to: :profile, prefix: true, allow_nil: true
Run Code Online (Sandbox Code Playgroud)

当值也是符号时,我的陪审团挂在1.9哈希语法上; 我觉得它看起来很有趣.我还不确定我在哪里缩进它 - 可能会在IDE重新格式化过程中失去它,但我有点像我在使用新语法时看起来如何.

  • @WizardofOgz我怀疑是更多的疾病.当我无法轻易阅读内容时,我才会感到愚蠢:/ (2认同)

jib*_*iel 7

虽然这个问题已经有两个很好的答案,但我想将未来的读者引用到Ruby样式指南中来解决这些问题.

目前," 源代码布局"部分提供了大量有关如何在各种情况下断行的信息:

# 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
Run Code Online (Sandbox Code Playgroud)

 

# 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
Run Code Online (Sandbox Code Playgroud)

对于过于复杂的代码,它经常被证明是一个"解决方案",正如@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
Run Code Online (Sandbox Code Playgroud)