欢迎来到我的Ruby on Rails模式和反模式系列的最后部分。写作和研究所有这些主题的过程是相当漫长的。在这篇博文中,我们将讨论这些年来我在构建和运送Ruby on Rails应用程序时遇到的最常见的问题。
我在这里要介绍的观点几乎适用于代码中的任何地方。所以,把它们看作是一般的想法,而不是与模型-视图-控制器模式有关的东西。如果你对与Rails MVC相关的模式和反模式感兴趣,你可以查看模型、视图和控制器的博文。
所以,让我们跳到一般的问题和启示。
自私的物品(德米特法则)
德米特法则是一个启发式方法,它的名字是在一群人从事德米特项目时得到的。它的意思是,只要你的对象一次只调用一个方法,不连锁调用多个方法,就不会有问题。这在实践中意味着以下情况:
# Bad
song.label.address
# Good
song.label_address
所以现在,song 对象不再需要知道地址来自哪里--地址是label 对象的责任。我们鼓励你只连锁调用一个方法,并使你的对象变得 "自私",这样它们就不会直接分享它们的全部信息,而是通过辅助方法:
def Song < ApplicationModel
belongs_to :label
delegate :address, to: :label
end
你可以去玩玩delegete的文档中委托接受的选项。但这个想法和执行是非常简单的。通过应用Demeter定律,你可以减少结构耦合。再加上强大的delegate ,你可以用更少的行来实现,并且包含了很多选项。
另一个与德墨忒尔法则非常相似的想法是单一责任原则(简称SRP)。它指出,一个模块、类或函数应该对系统的单一部分负责。或者,以另一种方式提出。
把那些因相同原因而变化的东西聚集在一起。把那些因不同原因而变化的东西分开。
人们往往对SRP有不同的理解,但想法是让你的构件负责一件事。随着你的Rails应用的扩展,实现SRP可能是一个挑战,但在重构时要注意这一点。
在增加功能和增加LOC的时候,我发现人们经常会伸手去找一个快速的解决方案。因此,让我们通过抓取快速解决方案。
我认识一个人 (你需要那颗红宝石吗?)
早在Rails成为热门话题的时候,就出现了开源合作的热潮,每个角落都有新的Ruby宝石出现(就像现在所有新兴的JavaScript库一样,但规模要小得多)。

来自模块计数的信息
总之,一个常见的方法是找到一个现有的宝石来解决你的问题。
这并没有错,但我想在你决定安装gem之前分享一些建议。
首先,问自己这些问题:
- 你打算使用该宝石的哪一部分功能?
- 是否有一个类似的gem是 "更简单的 "或更最新的?
- 你能轻松而自信地实现你需要的功能吗?
如果你不打算使用创业板的全部功能,那么评估一下是否值得去实现它。或者,如果创业板的实现过于复杂,而你相信你可以更简单地完成它,那就选择一个自定义的解决方案。
我考虑的另一个因素是宝石库的活跃程度--是否有活跃的维护者?上一次发布是什么时候?
另一个需要注意的是宝石的依赖性。你不希望被锁定在某个特定版本的依赖关系中,所以一定要检查Gemfile.spec 文件。在这种情况下,要经常查阅.NET文件。
你还应该注意宝石的依赖关系。你不想被锁定在一个特定版本的依赖关系中,所以一定要检查Gemfile.spec文件。咨询RubyGems指定gem版本的方法。
当我们在讨论宝石的时候,我遇到了一个相关的想法:适用于Rails/Ruby世界的 "并非在此发明"(或NIH)现象。让我们在下一节中看看它是怎么回事。
不是在这里发明的 (也许你终究需要那颗红宝石?)
在我职业生涯中的几次事件中,我有机会体验到人们(包括我)被'不是在这里发明的'综合症所吸引。这个想法类似于 "重新发明车轮"。有时,团队和组织不信任他们无法控制的图书馆(宝石)。缺乏信任可能是他们重新发明已经存在的宝石的一个触发因素。
有时,体验NIH可能是一件好事。做一个内部的解决方案可能是很好的,特别是如果你比外面的其他解决方案有所改进。如果你决定将解决方案开源,那可能会更好(看看Ruby on Rails或React)。但是,如果你想为了重新发明轮子而重新发明,那就不要这样做。轮子本身已经很不错了。
这个话题相当棘手,如果你曾经陷入这种情况,请问自己这些问题:
- 我们是否有信心能做出比现有的更好的解决方案?
- 如果现有的开源解决方案与我们的需求不同,我们能不能做出开源的贡献并加以改进?
- 此外,我们能否成为开源解决方案的维护者,并可能改善许多开发者的生活?
但有时,你必须走自己的路,自己创建一个库。也许你的组织不喜欢授权一个开源库,所以你被迫建立自己的库。但无论你做什么,我都会说避免重新发明轮子。
执勤的救生员 (过度救援的异常)
人们往往会抢救出比他们最初目标更多的例外。
这个话题与前面的话题相比,与代码的关系更大一些。对一些人来说,这可能是常识,但在代码中可以时常看到。比如说:
begin
song.upload_lyrics
rescue
puts 'Lyrics upload failed'
end
如果我们不指定要救援的异常,我们就会捕捉到一些我们不打算捕捉的异常。
在这种情况下,问题可能是:song 对象是nil 。当这个异常被报告给错误跟踪器时,你可能会认为上传过程出了问题,而实际上,你可能遇到的是完全不同的情况。
所以,为了安全起见,在抢救异常时,要确保你获得所有可能发生的异常的列表。如果你因为某些原因不能获得每一个异常,那么少救比多救要好。抢救你知道的异常,并在以后的阶段处理其他的异常。
你问得太多了(太多的SQL查询)
在本节中,我们将通过另一个网络开发,关系数据库问题。
你在一个请求中用太多的SQL查询来轰炸Webserver。这个问题是如何产生的呢?嗯,如果你试图在一个请求中从多个表中获取多条记录,它就会发生。但最常发生的是臭名昭著的N+1查询问题。
想象一下下面的模型:
class Song < ApplicationRecord
belongs_to :artist
end
class Artist < ApplicationRecord
has_many :songs
end
如果我们想显示一个流派中的几首歌曲和他们的艺术家:
songs = Song.where(genre: genre).limit(10)
songs.each do |song|
puts "#{song.title} by #{song.artist.name}"
end
这段代码将触发一个SQL查询来获得十首歌曲。之后,将进行一次额外的SQL查询以获取每首歌曲的艺术家。这总共是十一(11)个查询。
想象一下,如果我们加载更多的歌曲--我们将把数据库放在一个更重的负载下,试图获取所有的艺术家。
另外,可以使用Rails的includes :
songs = Song.includes(:artists).where(genre: genre).limit(10)
songs.each do |song|
puts "#{song.title} by #{song.artist.name}"
end
在使用includes ,我们现在只得到两个SQL查询,无论我们决定显示多少首歌曲。多么巧妙啊。
在开发过程中,你可以诊断出太多的SQL查询的方法。如果你看到一组类似的SQL查询从同一张表中获取数据,那么那里就会发生一些可疑的事情。这就是为什么我强烈建议你为你的开发环境打开SQL日志。另外,Rails支持粗略的查询日志,显示代码中查询的调用位置。
如果你不喜欢看日志,或者你想要更严肃的东西,可以试试AppSignal的性能测量和N+1查询检测。在那里,你会得到一个很好的指标,说明你的问题是否来自N+1查询。下面是它的样子。
总结
谢谢你阅读本系列博文。我很高兴你加入了我这个有趣的旅程,我们从介绍Rails中的模式和反模式,到探索它们在Rails MVC模式中的作用,再到最后这个关于一般问题的博文。
我希望你能学到很多东西,或者至少修改和确立你已经知道的东西。不要强调要记住所有的内容。如果你在任何领域遇到困难,你可以随时咨询这一系列的内容。
你肯定会同时遇到模式和反模式,因为这个世界(尤其是软件工程)并不理想。这也不应该让你担心。
掌握模式和反模式将使你成为一个伟大的软件工程师。但让你更出色的是知道何时打破这些模式和模子,因为没有完美的解决方案。
再次感谢您的加入和阅读。下期见--干杯!