我总是在我的 Rails 顶部获得大量的代码models
。我正在寻找使用标准 Ruby 样式分解它们的最佳方法的建议。例如,我现在正在查看的一行是:
delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true
打破这些长方法调用行的传统风格是什么?
我总是在我的 Rails 顶部获得大量的代码models
。我正在寻找使用标准 Ruby 样式分解它们的最佳方法的建议。例如,我现在正在查看的一行是:
delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true
打破这些长方法调用行的传统风格是什么?
简短的回答是视情况而定。
首先,您可以使用“新”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)
}
这可能是最好的方法。
不幸的是,人们倾向于写长线,这很糟糕:
git diff
从控制台中排长队之类的东西时会很痛苦我的编辑器中有一把尺子,这样我就知道何时要越过该行的第 80 个字符。但很少通过几个字符越过这条线,它实际上比拆分它更好。
有几种方法可以保持 80 年代以下的线条,并且通常取决于具体情况。长线的问题不仅仅是风格不好,长线通常是过于复杂的症状。
类似于以下内容:
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 重新格式化期间可能会丢失它,但如果我使用新语法,我有点喜欢上面的样子。
尽管这个问题已经有了两个很好的答案,但我想将未来的读者推荐给Ruby Style Guide来解决这些问题。
目前,源代码布局部分有大量关于如何在各种情况下换行的信息:
# 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
根据我的经验,似乎惯例实际上并不是为了打破界限。我见过的大多数项目,包括 rails 本身的代码库,似乎对有很长的不间断线路没有问题。
所以我想说如果你想遵循惯例,不要打破界限。如果您决心打破界限,那么就没有广泛遵循的惯例来说明如何做到这一点。您可以使用您喜欢的任何编码风格。