提升你的 Ruby 技能:编写简洁的代码
布尔值/条件语句
变量赋值
警戒条款
调用方法
安全导航员
哈希和数组
等等,还有更多!
我们都经历过这种情况:提交了一个自认为非常完美、无比优秀的拉取请求(PR)。它被标记为“已批准”,但实际上却并非如此。我们的代码并没有想象中那么严谨。于是,我们向大家开放了PR,收到了许多关于如何进一步优化代码的宝贵建议。
这在编程新手阶段非常常见,事实上,我也是这么学到很多技巧的。多亏了许多资深开发者从一开始就阅读并点评我的代码,我才学会了如何编写简洁的代码。但我可以告诉你一个秘密:学习永无止境。我已经从事编程工作7年了,但仍然会收到关于如何改进代码的建议。
以下是一些常用的方法,您可以使用一些语法糖来简化基本的 Ruby 逻辑。语法糖是指编程语言(例如 Ruby)中的一种语法,旨在使代码更易于阅读或表达。
布尔值/条件语句
三元运算符
让我们从一些基本的布尔逻辑开始,来设置一个变量。如果y是偶数,我们想把它设置x为 1;如果y是奇数,我们想把它设置x为 2。以下是我们在 Ruby 入门时学到的方法。
if y.even?
x = 1
else
x = 2
end
上面的代码包含了两条赋值语句。这意味着如果我们需要修改变量 x,就必须在两处进行修改,这并不理想。一种简化代码的方法是:
x = if y.even?
1
else
2
end
现在,我们只需要做x一次赋值,这稍微简单一些。但即使这样,也需要 5 行代码才能完成。让我们看看如何才能把它简化成一行。
x = y.even? ? 1 : 2
?这被称为三元运算符。三元运算符的逻辑使用“(条件) ? (真值) : (假值)”语句来简化 if/else 结构。它首先将表达式求值为真或假,然后根据求值结果执行两个给定语句中的一个。语法如下:
test-expression ? if-true-expression : if-false-expression
这在完善逻辑语句方面非常有用。
内联布尔值/条件语句
除了简洁的三元运算符之外,我们再来看看另一种将布尔逻辑简化为一行代码的方法。下面是一个包含布尔逻辑的基本方法。
def say_number_type(number)
if number.even?
puts "I am even!"
end
end
为了使代码更简洁,我们可以这样写这个布尔值:
def say_number_type(number)
puts "I am even!" if number.even?
end
同样的方法也适用于unless
def say_number_type(number)
puts "I am even!" unless number.odd?
end
注意:使用内联布尔值或条件语句有时被称为使用“修饰符语法”。
变量赋值
假设我们有一个场景,需要根据一个变量是否存在来赋予另一个变量不同的值。例如,我们有两个变量a。b如果a变量存在(非空),我们希望将变量赋值x为a。如果a变量为空,我们希望将x变量赋值为b。我们可以这样做。
if a.nil?
x = b
else
x = a
end
或者,根据我们上面学到的知识,我们可以这样做
x = a.nil? ? b : a
但是,当处理可能出现的 nil 值时,我们可以使用管道运算符使代码更加简洁,如下所示:
x = a || b
如果a`is` 不为 nil,则该赋值语句会将 `is` 的值赋给a`x` x。如果a`is` 为 nil,则它会将 `is` 的值赋给b`x`。请查看下面的控制台示例。
irb(main)> a = 1
=> 1
irb(main)> b = 2
=> 2
irb(main)> x = a || b
=> 1
irb(main)> a = nil
=> nil
irb(main)> x = a || b
=> 2
我们可以使用管道运算符连接两个变量,也可以将其与等于运算符一起使用。假设我们想将变量赋值x为nil a,但前提x是 nil 为 nil。如果xnil 不为 nil,我们希望保留 nil 的值x。为了实现这一点,我们可以这样做:
x ||= a
以下是一个控制台示例,详细展示了其工作原理:
irb(main)> x = 1
=> 1
irb(main)> a = 2
=> 2
irb(main)> x ||= a
=> 1
irb(main)> x = nil
=> nil
irb(main)> x ||= a
=> 2
a这告诉 Ruby 仅当 ` to`为 `nil`x时才赋值x。当你期望有一个默认值但又不能保证它一定存在时,这非常有用。假设你有以下方法
def add_one(number)
number + 1
end
如果number值为 nil,那么当我们尝试向其中加 1 时就会抛出错误NoMethodError。我们可以通过类似这样的方式避免抛出错误:
def add_one(number)
number ||= 0
number + 1
end
number ||= 0这样可以确保,如果number未定义,我们的方法不会引发错误,因为它会被设置为 0。如果你想更进一步,还可以将赋值运算符与参数一起放入函数中!🤯
def add_one(number = 0)
number + 1
end
调用此方法时,如果不传递数字,Ruby 会将数字设置为 0。
irb(main)> add_one
=> 1
irb(main)> add_one(1)
=> 2
警戒条款
守卫子句是指在满足特定条件时提前退出方法。守卫子句可以帮助您简化方法逻辑并编写更优化的代码。让我们来看一个使用守卫子句的例子。假设您有以下方法。tester
def tester(arg)
if arg == 1
# logic
else
'error'
end
end
在这种情况下,如果我们的参数arg不等于 1,则返回字符串“error”。我们可以tester通过使用如下所示的 guard 子句来简化此方法。
def tester(arg)
return 'error' unless arg == 1 # Guard Clause
# logic
end
方法开始时,我们立即检查 `a` 的值是否arg等于 1。如果等于,则立即返回。由于我们知道要做什么,因此无需在方法中处理其他所有事情。这在代码优化方面尤其有用。例如,请注意以下情况:
def tester(arg)
user = User.find(123)
event = Event.find(123)
if arg == 1
# logic
else
'error'
end
end
在这里,我们在执行 if/else 布尔语句之前就已经收集数据并执行逻辑了。最终,如果arg结果不等于 1,那么我们就浪费了大量时间和资源来收集不需要的数据。通过在方法的第一行检查条件,可以使用 guard 语句来确保不会浪费这些资源。
我还要补充一点,如果你想使用 guard 子句根据某个条件返回 nil,你可以这样做:
def tester(arg)
return unless arg == 1
# logic
end
在上述例子中,return单独使用就相当于你写了return nil。
def tester(arg)
# nil is unnecessary
return nil unless arg == 1
# logic
end
不过,在 Ruby 中,这种情况下你不需要显式地定义 nil。第一个例子return unless arg == 1就是你想要的写法,也是你很可能会看到其他人这样写的。
调用方法
假设你正在处理一组响应某个方法的对象,并且你想使用类似 `getResult` 的方法来收集该方法的结果map。通常,你会这样做:
a = [1, 2, 3, 4]
string_list = a.map{|obj| obj.to_s}
# string_list = ["1", "2", "3", "4"]
我们可以使用一些语法糖进一步简化它,并且仍然可以得到相同的结果。
string_list = a.map(&:to_s)
# string_list = ["1", "2", "3", "4"]
任何我们这组对象(在本例中是整数)响应的方法都可以使用&:method相同的语法在每个对象上调用。如果你想了解 Ruby 的底层工作原理,我建议你阅读这篇博客文章。
安全导航员
你们当中有多少人以前写过类似的代码?✋ 我肯定写过!
def friends_name(user)
return unless user.present?
friend = user.friend
if friend.present?
friend.name
end
end
这里做了很多存在性检查,只是为了确保我们永远不会对 nil 对象调用方法,从而避免出现可怕的错误# NoMethodError。与其进行所有这些检查,我们可以使用 Ruby 的安全导航器 (Safety Navigator)!如果对 nil 对象调用方法,安全导航器会直接返回 nil。这意味着我们可以将方法简化为:
def friends_name(user)
user&.friend&.name
end
如果该链中的任何对象缺失,我们将直接返回 nil。以下是另一个简单的控制台示例,希望能进一步帮助您理解安全导航器的工作原理。
irb> [].last.to_s
=> ""
irb> [].last.even?
NoMethodError: undefined method `even?' for nil:NilClass
from (irb):3
from /usr/bin/irb:11:in `<main>'
irb> [].last&.even?
=> nil
哈希和数组
括号 vs do/end
在我的哈希和数组教程中,我大量使用了 do/end 代码块来清晰地展示代码逻辑。然而,方括号表示法是编写更简洁代码的绝佳方式。正如我们在教程中看到的,它非常适合将多个逻辑串联起来。
result = [1, 2, 3, 4].map{|n| n + 2}.select{|n| n.even?}.reject{|n| n == 6}
# result = [4]
现在你可能会想,面对这么多选择,我该如何知道该用哪个呢?!通常来说,一个好的经验法则是:
- 如果代码块中只执行一行代码,则使用括号。
- 如果你的逻辑超过一行,请使用 do/end 表示法。
单行括号示例:
a = [1, 2, 3]
a.map{|number| number + 10}
多行 do/end 示例:
a = [1, 2, 3]
a.map do |number|
if number.even?
puts "I am even"
number/2
else
puts "I am odd"
number
end
end
请记住,不同的地方或不同的人可能有不同的风格。刚到一个新的地方时,一定要留意其他人的做法。你一开始很可能会想效仿他们。随着经验的积累,你会逐渐形成自己的风格和习惯。
连锁
我想快速回顾一下我上面用到的链式调用示例:
result = [1, 2, 3, 4].map{|n| n + 2}.select{|n| n.even?}.reject{|n| n == 6}
# result = [4]
这段代码非常简单明了,因此将每个步骤串联起来是使其更简洁的绝佳选择。但是,如果每个步骤的逻辑比较复杂,为了提高可读性,有时您可能需要将它们拆分开来。以下是一个为了提高可读性而拆分代码的示例。
plus_two_array = [1, 2, 3, 4].map{|n| n + 2}
even_numbers_array = plus_two_array.select{|n| n.even?}
remove_the_sixes_array = even_numbers_array.reject{|n| n == 6}
每个步骤及其结果都定义得非常清晰,这使得更复杂的逻辑更容易理解和遵循。我之所以要指出这一点,是因为有时编写最简洁的代码并非最佳选择。所有这些策略都很棒,但请注意,有时您可能需要为了代码的可读性而牺牲一些简洁性。
等等,还有更多!
显然,编写更简洁高效的代码的方法还有很多。这里只列举了一小部分,但我认为这是一个很好的起点。如果你发现了其他有助于改进代码的技巧,欢迎在评论区分享。一如既往,如果你有任何疑问或需要澄清的地方,请随时提出!
如果您想快速查阅这些方法,我制作了一个方便的速查表,其中只包含每个示例的代码。
文章来源:https://dev.to/molly/level-up-your-ruby-skillz-writing-compact-code-438k
