Buggy Rails代码:Rails开发人员犯的10个最常见的错误

本文概述

Ruby on Rails(” Rails”)是一种流行的开源框架, 基于Ruby编程语言, 致力于简化和简化Web应用程序的开发过程。

Rails建立在约定优于配置的原则上。简而言之, 这意味着默认情况下, Rails假定其专家开发人员将遵循”标准”最佳实践惯例(例如命名, 代码结构等), 并且如果你这样做, 则一切将对你有效。 -魔术”, 而无需指定这些详细信息。尽管这种范例有其优势, 但也并非没有缺陷。最值得注意的是, 框架中幕后发生的”魔术”有时会导致假象, 混乱和”到底发生了什么?”问题类型。它还可能在安全性和性能方面产生不良后果。

因此, 尽管Rails易于使用, 但也不难滥用。本教程研究了10个常见的Rails问题, 包括如何避免它们以及它们引起的问题。

常见错误1:在控制器中放置太多逻辑

Rails基于MVC架构。在Rails社区中, 已经有一段时间讨论胖模型, 瘦控制器了, 但是我继承的几个最近的Rails应用程序都违反了这一原理。将视图逻辑(最好放在助手中)或域/模型逻辑移到控制器中太容易了。

问题在于, 控制器对象将开始违反单一责任原则, 从而使将来对代码库的更改变得困难且容易出错。通常, 控制器中应具有的唯一逻辑类型是:

  • 会话和cookie处理。这可能还包括身份验证/授权或你需要执行的任何其他cookie处理。
  • 型号选择。给定从请求中传入的参数, 查找正确的模型对象的逻辑。理想情况下, 这应该是对单个find方法的调用, 该方法设置了一个实例变量, 以稍后用于呈现响应。
  • 请求参数管理。收集请求参数并调用适当的模型方法以保留它们。
  • 渲染/重定向。渲染结果(html, xml, json等)或进行适当的重定向。

尽管这仍然突破了单一责任原则的局限性, 但这还是Rails框架要求我们将其包含在控制器中的最低限度。

常见错误2:在视图中放置太多逻辑

开箱即用的Rails模板引擎ERB是构建具有可变内容的页面的好方法。但是, 如果你不小心, 很快就会遇到一个大文件, 该文件是HTML和Ruby代码的混合体, 可能难以管理和维护。这也是一个可能导致大量重复的领域, 导致违反DRY(请勿重复自己)原则。

这可以通过多种方式体现出来。一种是在视图中过度使用条件逻辑。举一个简单的例子, 考虑一种情况, 其中我们有一个current_user方法可用于返回当前登录的用户。通常, 视图文件中最终会出现这样的条件逻辑结构:

<h3>
  Welcome, <% if current_user %>
    <%= current_user.name %>
  <% else %>
    Guest
  <% end %>
</h3>

处理此类问题的一种更好方法是, 确保始终设置current_user返回的对象(无论是否有人登录), 并以合理的方式回答视图中使用的方法(有时称为null)。宾语)。例如, 你可以像这样在app / controllers / application_controller中定义current_user帮助器:

require 'ostruct'

helper_method :current_user

def current_user
  @current_user ||= User.find session[:user_id] if session[:user_id]
  if @current_user
    @current_user
  else
    OpenStruct.new(name: 'Guest')
  end
end

然后, 这将使你能够用此简单的代码行替换之前的视图代码示例:

<h3>Welcome, <%= current_user.name -%></h3>

其他一些建议的Rails最佳实践:

  • 适当使用视图布局和局部视图来封装在页面上重复的内容。
  • 使用演示者/装饰者(例如Draper gem)将视图构建逻辑封装在Ruby对象中。然后, 你可以将方法添加到该对象中, 以执行逻辑操作, 而这些逻辑操作原本可能已放入视图代码中。

常见错误3:在模型中放置太多逻辑

在最小化视图和控制器逻辑的指导下, 在MVC架构中唯一可以放置所有逻辑的地方就是模型了, 对吧?

好吧, 不完全是。

许多Rails开发人员实际上犯了这个错误, 最终将所有内容粘贴在ActiveRecord模型类中, 从而导致mongo文件不仅违反了单一责任原则, 而且也是维护的噩梦。

诸如生成电子邮件通知, 与外部服务接口, 转换为其他数据格式之类的功能与ActiveRecord模型的核心职责没有多大关系, ActiveRecord模型的主要职责应该是在数据库中查找并持久存储数据。

因此, 如果逻辑不应该出现在视图中, 逻辑不应该出现在控制器中, 逻辑也不应出现在模型中, 那么应该去哪里呢?

输入普通的旧Ruby对象(PO​​RO)。使用Rails之类的全面框架, 新开发人员通常不愿意在框架之外创建自己的类。但是, 将逻辑从模型中移出到PORO中通常是医生为了避免过于复杂的模型而下达的命令。使用PORO, 你可以将诸如电子邮件通知或API交互之类的内容封装到自己的类中, 而不是将其粘贴到ActiveRecord模型中。

因此, 总的来说, 记住这一点, 模型中应保留的唯一逻辑是:

  • ActiveRecord配置(即关系和验证)
  • 简单的变异方法封装了一些属性的更新并将其保存在数据库中
  • 访问包装器以隐藏内部模型信息(例如, 将数据库中的first_name和last_name字段组合在一起的full_name方法)
  • 复杂的查询(即比简单的查找要复杂的查询);一般来说, 你永远不要在模型类本身之外使用where方法或类似它的任何其他查询构建方法

相关:8个基本的Ruby on Rails面试问题

常见错误4:将通用帮助程序类用作垃圾场

这个错误实际上是上述错误#3的必然结果。如上所述, Rails框架着重于MVC框架的命名组件(即模型, 视图和控制器)。属于每个组件类的事物的种类都有很好的定义, 但是有时我们可能需要看起来不适合这三个组件的方法。

Rails生成器可以方便地构建一个帮助程序目录和一个新的帮助程序类, 以与我们创建的每个新资源一起使用。但是, 开始将那些正式不适合模型, 视图或控制器的功能填充到这些帮助器类中变得太诱人了。

尽管Rails当然是以MVC为中心的, 但没有什么可以阻止你创建自己的类类型并添加适当的目录来保存这些类的代码。当你具有其他功能时, 请考虑将哪些方法组合在一起, 并为包含这些方法的类找到好名字。使用像Rails这样的全面框架并不是让优秀的面向对象设计最佳实践顺其自然的借口。

常见错误5:使用过多的宝石

Ruby和Rails得到了丰富的宝石生态系统的支持, 这些生态系统共同提供了开发人员可以想到的几乎所有功能。这对于快速构建复杂的应用程序非常有用, 但是我也看到许多many肿的应用程序, 与提供的功能相比, 该应用程序的Gemfile中的gem数量成比例地过大。

这会导致多个Rails问题。过度使用宝石会使Rails流程的规模变得比所需的更大。这会降低生产性能。除了使用户感到沮丧之外, 这还可能导致需要更大的服务器内存配置并增加运营成本。启动大型Rails应用程序还需要花费更长的时间, 这会导致开发速度变慢, 并使自动化测试花费的时间也更长(通常, 慢速测试根本不会经常运行)。

请记住, 你带入应用程序的每个gem可能反过来又依赖于其他gem, 而那些反过来又可能依赖于其他gem, 依此类推。因此, 添加其他宝石可以产生复合效果。例如, 添加rails_admin gem将总共带来11个gem, 比基本的Rails安装增加了10%以上。

在撰写本文时, 全新的Rails 4.1.0安装包括Gemfile.lock文件中的43个gem。显然, 这比Gemfile中包含的更多, 它代表了少数标准Rails gem作为依赖项带来的所有gem。

在添加每颗宝石时, 请仔细考虑额外的开销是否值得。例如, 开发人员通常会随意添加rails_admin gem, 因为它本质上为模型结构提供了一个不错的Web前端, 但实际上并不仅仅是一个花哨的数据库浏览工具。即使你的应用程序要求管理员用户具有其他特权, 你也可能不希望授予他们原始数据库访问权限, 因此, 开发自己的更简化的管理功能比添加此gem更好。

常见错误#6:忽略日志文件

尽管大多数Rails开发人员都知道在开发和生产过程中可用的默认日志文件, 但他们通常对这些文件中的信息没有给予足够的重视。尽管许多应用程序在生产中都依赖于Honeybadger或New Relic等日志监视工具, 但在开发和测试应用程序的整个过程中, 始终密切注意日志文件也很重要。

如本教程前面所述, Rails框架为你带来了很多”魔术”, 尤其是在模型中。在模型中定义关联可以很容易地建立关系并使所有可用的视图可用。会为你生成填充模型对象所需的所有SQL。那很棒。但是你怎么知道所生成的SQL是有效的呢?

你经常会遇到的一个示例称为N + 1查询问题。虽然已很好地理解了问题, 但观察到问题发生的唯一真正方法是查看日志文件中的SQL查询。

例如, 假设你在一个典型的博客应用程序中有以下查询, 你将在其中显示一组精选帖子的所有评论:

def comments_for_top_three_posts
  posts = Post.limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

当我们查看调用此方法的请求的日志文件时, 将看到类似以下内容的内容:在其中进行单个查询以获取三个发布对象, 然后进行三个查询以获取每个对象的注释:

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (5.6ms)  ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]
  Comment Load (1.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 3]]
  Rendered posts/some_comments.html.erb within layouts/application (12.5ms)
Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

ActiveRecord在Rails中急切的加载功能使你可以提前指定要加载的所有关联, 从而显着减少查询数量。这是通过在要构建的Arel(ActiveRecord :: Relation)对象上调用include(或preload)方法来完成的。借助include, ActiveRecord可确保使用尽可能少的查询数来加载所有指定的关联;例如。:

def comments_for_top_three_posts
  posts = Post.includes(:comments).limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

执行上述修改后的代码后, 我们在日志文件中看到所有注释都是在单个查询中收集的, 而不是三个:

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.5ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (4.4ms)  SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3)
  Rendered posts/some_comments.html.erb within layouts/application (12.2ms)
Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

效率更高。

解决N + 1问题的方法实际上仅是作为示例, 说明如果你没有引起足够的重视, 可能会在你的应用程序中”掩盖”这种低效率。这里的要点是, 你应该在开发过程中检查开发和测试日志文件, 以检查(并解决!)构建响应的代码效率低下。

查看日志文件是一种很好的方式, 可以解决代码中的低效率问题, 并在应用程序投入生产之前进行更正。否则, 在系统上线之前, 你可能不会意识到由此引起的Rails性能问题, 因为在开发和测试中使用的数据集可能比生产中的数据集小得多。如果你使用的是新应用, 则即使你的生产数据集也可能很小, 并且你的应用看起来运行良好。但是, 随着你的生产数据集的增长, Rails这样的问题将导致你的应用程序运行越来越慢。

如果你发现自己的日志文件被一堆信息所阻塞, 那么你可以采取一些措施来清理它们(其中的技术既可以用于开发日志, 也可以用于生产日志)。

常见错误7:缺乏自动化测试

Ruby和Rails默认情况下提供强大的自动化测试功能。许多Rails开发人员使用TDD和BDD样式编写非常复杂的测试, 并使用功能更强大的测试框架以及诸如rspec和黄瓜之类的宝石。

尽管将自动化测试添加到你的Rails应用程序很容易, 但是, 我对我继承或加入了多少个项目而以前几乎没有编写任何测试(或充其量, 很少)感到非常不满意。开发团队。尽管关于测试的全面性存在很多争论, 但很明显, 对于每个应用程序至少应存在一些自动化测试。

一般而言, 对于控制器中的每个动作, 至少应编写一个高级集成测试。在将来的某个时候, 其他Rails开发人员很可能希望扩展或修改代码, 或者升级Ruby或Rails版本, 并且该测试框架将为他们提供一种清晰的方式来验证应用程序的基本功能是否正确。工作。这种方法的另一个好处是, 它为将来的开发人员提供了由应用程序提供的全部功能的清晰描述。

常见错误8:阻止对外部服务的呼叫

Rails服务的第三方提供商通常可以非常轻松地通过包装API的gem将其服务集成到你的应用程序中。但是, 如果你的外部服务中断或开始运行非常缓慢, 会发生什么?

为了避免阻塞这些调用, 而不是在正常处理请求期间直接在Rails应用程序中调用这些服务, 应在可行的情况下将它们移至某种后台作业排队服务。为此, 在Rails应用程序中使用了一些流行的宝石:

  • 延迟工作
  • 福利
  • Sidekiq

如果将处理委托给后台作业队列是不切实际或不可行的, 那么你将需要确保你的应用程序具有足够的错误处理和故障转移准备, 以应对外部服务出现故障或出现问题的那些不可避免的情况。你还应该在不使用外部服务的情况下测试你的应用程序(也许是通过从网络上删除你的应用程序所在的服务器)来验证它不会导致任何意料之外的后果。

常见错误9:与现有数据库迁移结婚

Rails的数据库迁移机制使你可以创建说明来自动添加和删除数据库表和行。由于包含这些迁移的文件是按顺序命名的, 因此你可以从开始就进行播放, 以将空数据库带入与生产相同的架构。因此, 这是管理对应用程序数据库模式进行细化更改并避免Rails问题的好方法。

尽管这在项目开始时肯定会很好, 但是随着时间的流逝, 数据库创建过程可能会花费相当长的时间, 有时迁移会放错位置, 插入顺序混乱或使用同一数据库服务器从其他Rails应用程序引入。

Rails在名为db / schema.rb的文件中创建当前模式的表示(默认情况下), 该文件通常在运行数据库迁移时更新。通过运行rake db:schema:dump任务, 甚至在不存在任何迁移时, 甚至可以生成schema.rb文件。 Rails的一个常见错误是将新迁移检查到源存储库中, 而不是检查相应更新的schema.rb文件。

当迁移变得不可用并且花费太长时间才能运行, 或者不再正确地创建数据库时, 开发人员应该不必担心清除旧的迁移目录, 转储新的架构并从那里继续。设置新的开发环境将需要rake db:schema:load而不是大多数开发人员所依赖的rake db:migrate。

《 Rails指南》中也讨论了其中一些问题。

常见错误10:将敏感信息检入源代码存储库

Rails框架使创建不受多种类型攻击的安全应用程序变得容易。其中一些是通过使用秘密令牌来保护与浏览器的会话来实现的。即使此令牌现在存储在config / secrets.yml中, 并且该文件从生产服务器的环境变量中读取令牌, Rails的早期版本也将令牌包含在config / initializers / secret_token.rb中。该文件经常与应用程序的其余部分一起错误地检入到源代码存储库中, 一旦发生这种情况, 任何有权访问该存储库的人现在都可以轻松地损害应用程序的所有用户。

因此, 你应确保你的存储库配置文件(例如, git用户使用.gitignore)将带有令牌的文件排除在外。然后, 你的生产服务器可以从环境变量或诸如dotenv gem提供的机制等机制中获取令牌。

教程总结

Rails是一个功能强大的框架, 它隐藏了构建健壮的Web应用程序所需的许多丑陋细节。尽管这可以使Rails Web应用程序的开发速度大大加快, 但是开发人员应注意潜在的设计和编码错误, 以确保其应用程序随其增长易于扩展和维护。

开发人员还需要意识到可能导致其应用程序运行缓慢, 可靠性降低和安全性降低的问题。研究框架并确保你充分了解在整个开发过程中要进行的架构, 设计和编码方面的权衡, 这一点很重要, 以帮助确保高质量和高性能的应用程序。

相关:Ruby on Rails有什么好处?经过两个十年的编程, 我使用了Rails

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?