Ruby on Rails 是一款被宽泛使用的 Web 应用程序框架。 Rails 使我们办公更有效率,让我们更专注于手头的任务而不是技术本身。在初学阶段,坚持 Rails 的最佳实践非常重要 。因此,在这篇文章中,我们将对 Ruby on Rails 中的最佳实践做系列介绍。
毁灭之路
如果你忽略了 Web
应用程序框架的最佳实践的重要性,那么你就不算了解框架。最坏的情况,你在开发应用程序的过程中会遇到许多问题,你需要不断地进行处理。并且,对最佳实践
缺乏了解,还会给新功能的开发,项目的维护以及开发者的引入带来困难。因此,为保持工作的高能和高效,避免你(或你的团队)在问题出现时焦头烂额,抓耳挠
腮,你应当对这项内容有所了解。
正如标题所示:最佳实践,它们因为某些原因被广泛使用着,以下便列出了几点使用这些实践的好处:
1.可维护性 2.可读性 3.优雅 4.快速发展 5.DRY 代码 让我们一起来了解下。 Ruby on Rails 社区风格指南:每种编程语言都同时包含有精彩的代码和糟糕的代码。代码风格也因人而异,所以,当有新开发成员加入到项目时,需要对代码风格进行一段时间的适应,这就会导致项目的完成时间延迟。因此,拥有一份由社区汇总的编码风格指南就显得尤为重要。因为它能有效地统一代码风格,这有利于代码库的管理。项目的建立通常先由小团队接手,再换到大团队改进,编码风格和背景也就随着开发团队的改变而改变。遵循 Ruby 社区风格指南是第一个我要介绍的最佳实践,这里有几项非常好的风格,我强调一下: 两个空格缩进这是 Ruby 社区中最被广泛采用和最被支持的风格之一。使用 2 空格缩进代替 4 空格缩进。让我们看一个例子: 4 个空格缩进
1 2 3 4 5 6 7 8 | def some_method
some_var = true
if some_var
do_something
else
do_something_else
end
end
|
2 个空格缩进
1 2 3 4 5 6 7 8 | def some_method
some_var = true
if some_var
do_something
else
do_something_else
end
end
|
后者更加简洁明了,也更具可读性。此外,在含有多级别缩进的大文件中,2 格缩进带来的效果更加明显。 用 a 来定义判断方法?在 Ruby 中有一些用以返回 true 或者 false 约定的方法。这些方法就是判断方法,约定是以带有问号(?)的名称结尾。在大多数编程语言中,你能看到各种各样的定义方法或变量名称,如 is_valid 和 is_paid 等。但 Ruby 并不鼓励这种风格,它们希望能使用更易理解的语言来命名,如:object.valid?或 orfee.paid?(注意,这里没有is_ 前缀),这种风格是 Ruby 通用性和可读性的体现。 迭代: 使用 each 而不是 for绝大多数 Ruby 程序员在迭代集合时都是使用 each,而不是 for。因为 each 更简单易读。 * for * * each * 1 2 3 | ( 1 .. 100 ).each do |i|
...
end
|
看到效果了吗? 条件:使用 unless 而不是!if:当你发现自己在使用 if 语句进行条件判断,如: 或者 1 2 3 | if name != "sarmad"
do_that
end
|
你应该立马改用 Ruby 独有的 unless 语句,如: 或者 1 2 3 | unless name == "sarmad"
do_that
end
|
这同样和易读性有关。但需要注意一点,当你的条件中需要使用到 else 语句时,千万不要用 unless-else。 错误的语句 1 2 3 4 5 | unless user.save
# throw error
else
# return success
end
|
正确的语句 1 2 3 4 5 | if user.save
# return success
else
# throw error
end
|
条件判断捷径**
条件判断捷径是一种术语,用于在某些条件下提前退出方法,参考如下示例:
1 2 3 4 5 6 7 | if user.gender == "male" && user.age > 17
do_something
elsif user.gender == "male" && user.age < 17 && user.age > 5
do_something_else
elsif user.age < 5
raise StandardError
end
|
在这种情况下,它需要根据所有条件的来判断用户是否低于5岁,然后抛出异常。首选方法: 1 2 3 4 5 6 | raise StandardError if user.age < 5
if user.gender == "male" && user.age > 17
do_something
elsif user.gender == "male" && user.age < 17 #we saved a redundant check here
do_something_else
end
|
当满足某个条件时,尽早的返回能让程序效率更高。
提示:我强烈建议你仔细阅读这些编码风格指南这里(Ruby)和这里 (Rails)。 编写测试如果你熟悉 Rails 的话,你就会知道 Rails 社区对测试有多么重视。我曾听人说过,对于新手,测试工作是学习 Rails 的一个拦路虎。不过也有人说,从一开始就这样做,有助于掌握 Rails 的基础知识(以及在某些普通的网页开发场景中)。不过,所有这些都不会阻止测试成为软件开发界实至名归的最佳实践。事实上,我也听到过抱怨的声音:如果在完成一个功能的基础上,还要加上测试工作,这就需要花费大量的时间。但是一旦他们进入到 Rails 开发的测试环节,并且一开始就承受了编写测试的“麻烦”,那么在实际构建功能时,立马就能构建好。另外,这样做也能使程序涵盖到许多的边缘场景,然后给项目带来更好的设计。不得不说,一个好的 Ruby 程序员天生就善于做测试。 让我们来列举下测试的一些好处: DRY (不要浪费时间)
要尽可能的确保你没有在浪费时间做重复的工作。 让我们来讨论一下 Ruby 的面向对象规则中避免重复的方法。 使用抽象类: 假设你有下面这样两个类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class Mercedes
def accelerate
"60MPH in 5 seconds"
end
def apply_brakes
"stopped in 4 seconds"
end
def open_boot
"opened"
end
def turn_headlights_on
"turned on"
end
def turn_headlights_off
"turned off"
end
end
class Audi
def accelerate
"60MPH in 6.5 seconds"
end
def apply_brakes
"stopped in 3.5 seconds"
end
def open_boot
"opened"
end
def turn_headlights_on
"turned on"
end
def turn_headlights_off
"turned off"
end
end
|
这两个类彼此中有三个重复的方法 open_boot, turn_headlights_on, 以及 turn_headlights_off。在此我们不讨论为什么不要写重复的代码,如果你想了解,你可以读一读这个。现在我们只就 DRY 这个原则进行讨论。这里要使用的最佳实践就是使用继承和/或者抽象类。下面我们来重写这个类以解决问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class Car
# Uncomment the line below if you want this class to be uninstantiable
# i.e you can't make an instance of this class .
# You can only inherit other classes from it.
# self. abstract = true
def open_boot
"opened"
end
def turn_headlights_on
"turned on"
end
def turn_headlights_off
"turned off"
end
end
class Mercedes < Car
def accelerate
"60MPH in 5 seconds"
end
def apply_brakes
"stopped in 4 seconds"
end
end
class Audi < Car
def accelerate
"60MPH in 6.5 seconds"
end
def apply_brakes
"stopped in 3.5 seconds"
end
end
|
看出差异了吗? 这样好多了! 使用模块 模块,从另外一方面来看,是一种在类之间共享行为的灵活方式。(对于人们应该使用其他类模块,而不是继承类(组件)的原因,我们在此也不做讨论。)这里我只说明一下模块是类和行为之间一种“has-a”关系而继承是一种“is-a”关系: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class Newspaper
def headline
#code
end
def sports_news
#code
end
def world_news
#code
end
def price
#code
end
end
class Book
def title
#code
end
def read_page(page_number)
#code
end
def price
#code
end
def total_pages
#code
end
end
|
假设我们需要向两个类都添加一个 print 方法,而又不想重复编写代码,就可以使用模块,像下面这样: 1 2 3 4 5 | module Printable
def print
#code
end
end
|
修改类的代码,让它们引入这个模块: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class Newspaper
#This wil add the module's methods as instance methods to this class
include Printable
def headline
#code
end
def sports_news
#code
end
def world_news
#code
end
def price
#code
end
end
class Book
#This wil add the module's methods as instance methods to this class
include Printable
def title
#code
end
def read_page(page_number)
#code
end
def price
#code
end
def total_pages
#code
end
end
|
这是一种非常强大且实用的技术。我们也可以使用 extend Printable 而不是 include Printable 来让模块的方法成为类的方法。 枚举类型的巧妙使用
假如说你有了一个叫做 Book 的模型,这个模型拥有一个列/域,你想要在这个列/域里面存储这本书,而不管是草稿、完成还是发布的状态。你发现自己正要做的会是像下面这样: 1 2 3 4 5 6 7 | if book.status == "draft"
do_something
elsif book.status == "completed"
do_something
elsif book.status == "published"
do_something
end
|
或者: 1 2 3 4 5 6 7 | if book.status == 0 #draft
do_something
elsif book.status == 1 #completed
do_something
elsif book.status == 2 #published
do_something
end
|
如果是这种情况的话,你应该看看枚举类型。你要将这个状态列定义成整型,理想情况下不能为空(null:false), 并且这个模型再创建了之后还需要其状态有一个默认值,例如,默认为 0。现在,你就可以像下面这样定义枚举了: 1 | enum status: { draft: 0 , completed: 1 , published: 2 }
|
现在,你可以将代码进行重写,如下: 1 2 3 4 5 6 7 | if book.draft?
do_something
elsif book.completed?
do_something
elsif book.published?
do_something
end
|
看起来很棒不是吗? 这样的做法不仅让你得到了对应状态名称的判断方法,还给你提供了可以在你所定义的状态之间进行切换的方法。 1 2 3 | book.draft!
book.completed!
book.published!
|
这些方法也可以切换状态来匹配。看,你的工具库新增了一个多么优雅的工具啊。 胖的模型,瘦的控制器和关注点另外一个最佳实践就是,坚持不对控制器之外的相关逻辑进行响应。那些你不想将其放到一个控制器的代码的示例,应该就业务的逻辑或持久化/模型变更的逻辑。例如,有些人可能会让他们的模型看起来像下面这个样子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|