设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

10项 Ruby on Rails的最佳实践

2016-11-29 21:50| 发布者: joejoe0332| 查看: 1513| 评论: 0|原作者: leoxu, Tony, wnull|来自: oschina

摘要: Ruby on Rails 是一款被宽泛使用的 Web 应用程序框架。 Rails 使我们办公更有效率,让我们更专注于手头的任务而不是技术本身。在初学阶段,坚持 Rails 的最佳实践非常重要 。因此,在这篇文章中,我们将对 Ruby on R ...

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 *

1
2
3
for i in 1..100
  ...
end

* each *

1
2
3
(1..100).each do |i|
  ...
end

看到效果了吗?

条件:使用 unless 而不是!if:

当你发现自己在使用 if 语句进行条件判断,如:

1
2
3
if !true
  do_this
end

或者

1
2
3
if name != "sarmad"
  do_that
end

你应该立马改用 Ruby 独有的 unless 语句,如:

1
2
3
unless true
  do_this
end

或者

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 程序员天生就善于做测试。

让我们来列举下测试的一些好处:

  • 测试为功能或应用程序提供详尽规范。

  • 测试可用作其他开发者的文档,帮助他们理解你在程序实现中的意图。

  • 测试有助于事先捕捉并修复 bug。

  • 测试为你重构代码或提高性能,但不影响程序的其它方面,为你的编码建立信心。

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