显示未知错误_总结自1000多个Ruby on Rails项目的十大错误

本文由头条号『程序员新视界』原创翻译,转载必须在正文中标注并保留原文链接、译文链接和译者等信息。
原文链接:https://dzone.com/articles/top-10-errors-from-1000-ruby-on-rails-projects-and
原文标题:Top 10 Errors From 1000+ Ruby on Rails Projects (and How to Avoid Them)
译文连接:
翻译作者:程序员新视界

为了回馈开发者社区,我们通过查看Rollbar的数千个项目的数据库,总结出了Ruby on Rails项目中排位前十的错误。我们将详细说明导致错误发生的原因以及如何防止这些错误。如果你能够避免这些“陷阱”,那么你将成为一个更好的开发者。

正所谓数据是王道,所以我们收集、分析并排位Ruby on Rails应用程序中的前10个Ruby错误。Rollbar收集了每个项目的所有错误并合计每个错误发生的次数。我们根据fingerprinting算法分组错误来做到这一点。基本上,如果第二个错误只是第一个错误的重复,那么我们会将这两个错误分到一组。

前10位Rails错误

5b0db2e531420c5ddf19be3084ca80d8.png

有些可能是你熟悉的面孔。接下来我们会深入探究并仔细查看错误,以发现导致应用程序生产过程中出现错误的原因。

本文提供的示例解决方案基于Rails 5,如果您仍在使用Rails 4,那么这篇文章也可以为你指出正确的方向。

1. ActionController::RoutingError

我们从任意一个经典的Web应用程序开始,即404错误的Rails版本。ActionController::RoutingError意味着用户请求了一个应用程序中不存在的URL。 Rails会记录下来,看起来这像一个错误,但是,大多数情况下,它并非应用程序的错误。

这可能是由于应用程序中有指向或来自不正确的链接而导致的。也有可能是因为恶意用户或机器人程序在测试应用程序的常见弱点。如果是这样的话,你可能会在日志中找到类似这样的东西:

由应用程序而非错误用户而导致ActionController::RoutingError的一个常见原因是:如果你将应用程序部署到Heroku,或任何不允许提供静态文件的平台,那么你可能会发现CSS和JavaScript不加载。如果是这种情况,错误将会像这样:

ActionController::RoutingError (No route matches [GET] "/wp-admin"):

要解决这个问题并允许Rails提供静态资源,你需要在应用程序的config/environments/production.rb文件中添加代码:

如果你对记录由ActionController::RoutingError导致的404错误不感兴趣,那么你可以通过设置一条catch-all路由并自己提供404来避免。该方法由lograge项目建议。为此,可以在config/routes.rb文件的底部添加以下内容:

然后将route_not_found方法添加到ApplicationController中:

在实施之前,你应该考虑知道404错误对你是否重要。你还应该谨记,在应用程序加载后安装的任何路由或引擎之所以无法访问,是因为它们将被catch-all路径捕获。

2. NoMethodError: Undefined Method '[]' for Nil:NilClass

这意味着你正在使用方括号([])符号从对象中读取属性,但该对象缺失或nil,因此它不支持此方法。由于我们正在使用方括号,因此很可能我们正在通过散列或数组来访问属性,且此过程中缺失了一些东西。当你从JSON API或CSV文件中解析和提取数据,或者仅从控制器操作中的嵌套参数获取数据时,可能会发生这种情况。

假设用户通过表单提交地址详细信息。那么可能你期望参数如下所示:

然后,你可以通过调用params[:user][:address][:street]来访问street。如果没有address被传递,那么params[:user][:address]将为nil,并且调用[:street]会引发NoMethodError。

你可以对每个参数执行nil检查并使用&&运算符尽早返回,如下所示:

幸运的是,现在有更好的方法来访问哈希、数组中,以及甚至像ActionController::Parameters这样的事件对象中的嵌套元素。自Ruby 2.3,哈希、数组、和ActionController::Parameters有了dig方法。 dig允许你提供一个你想要检索的对象的路径。如果在任何阶段返回了nil,那么dig会返回nil而不会抛出NoMethodError。要从上面的参数中获取street,你可以调用:

你不会得到任何错误,尽管你确实需要清楚street可能仍然是nil。

另外,如果你还使用点符号来挖掘嵌套对象,则可以使用安全导航运算符在Ruby 2.3中安全地执行此操作。所以,与其调用

并得到nil:NilClass的NoMethodError: undefined method street,现在你可以调用:

以上将与使用dig相同。如果address是nil那么street 将为nil,并且你将需要处理nil,当你稍后涉及street的时候。如果所有对象都存在,则street将被正确分配。

虽然这可以防止向用户显示错误,但如果它仍影响用户体验,则可能需要创建内部错误以跟踪日志或跟踪Rollbar等错误跟踪系统,以便你可以查看并解决问题。

如果你不使用Ruby 2.3或更高版本,则可以使用 ruby_dig gem 和 ActiveSupport's 的 try 来获得与上面相同的结果。

3. ActionController::InvalidAuthenticityToken

排行第三的这个错误则需要仔细考虑,因为它与应用程序的安全性有关。当POST,PUT,PATCH或DELETE请求丢失或具有不正确的CSRF(Cross Site Request Forgery,跨站点请求伪造)令牌时,则会引发ActionController::InvalidAuthenticityToken。

CSRF是Web应用程序中的潜在漏洞——恶意网站以未知用户的名义向应用程序发出请求。如果用户登录,则他们的会话cookie将与请求一起发送,攻击者可以以用户的身份执行命令。

Rails通过在所有表单中包含网站已知并验证但第三方无法识别的安全令牌来缓解CSRF攻击。这通过我们熟悉的ApplicationController代码行执行:

因此,如果你的产品应用程序正在引发ActionController::InvalidAuthenticityToken错误,则可能意味着攻击者正在针对你网站的用户,但Rails安全措施正在保证安全。

还有其他原因可能也会导致你无意中收到此错误。

Ajax

例如,如果你从前端发出Ajax请求,则需要确保在请求中包含CSRF令牌。如果你正在使用jQuery和内置的Rails unobtrusive脚本适配器,那么它已经为你处理好了。如果你想以其他方式处理Ajax,例如使用Fetch API,那么你需要确保包含CSRF令牌。无论是哪种方法,你都需要确保应用程序布局在文档的中包含CSRF元标记:

这会输出如下所示的元标记:

在发出Ajax请求时,请阅读元标记内容并将其作为X-CSRF-Token表头添加到headers中。

Webhook/API

有时候我们有不得不关闭CSRF保护的原因。如果你想要在应用程序中从第三方接收传入的POST请求到某些URL,则你不会希望CSRF来阻止它们。如果你正在为第三方开发人员构建API,或者你希望从服务接收传入的webhook,那么你也需要关闭CSRF保护。

你可以关闭CSRF保护,但请确保将不需要此类保护的端点列入白名单。你可以在控制器中通过跳过身份验证来执行此操作:

如果你接受传入的webhook,则应该得能够验证请求是否来自可信来源,而不是验证CSRF令牌。

4. Net::ReadTimeout

当Ruby从一个套接字读取数据所需的时间比read_timeout值(默认为60秒)更长时则会引发Net::ReadTimeout。如果你使用Net::HTTP,open-uri或 HTTParty 发出HTTP请求,则可能会引发此错误。

值得注意的是,这并不意味着如果请求本身需要花费比read_timeout的时间更长,就会抛出错误,只是意味着如果特定的读取比read_timeout长。你可以阅读更多关于Net::HTTP和timeouts from Felipe Philipp的内容。

我们可以做一些事情来阻止得到Net::ReadTimeout错误。一旦你理解了引发错误的HTTP请求,那么你就可以尝试将read_timeout值调整为更明智的值。正如在上面的文章中所述,如果你发出请求的服务器在发送响应之前需要很长时间整合响应,那么你需要更长的read_timeout值。如果服务器以块的形式返回响应,那么你需要更短的read_timeout。

你可以通过在你使用的相应HTTP客户端上设置一个以秒为单位的值来设置read_timeout:

使用Net::HTTP

使用 open::uri

使用HTTParty

你不能总是相信另一台服务器在预期的超时时间内做出响应。如果你可以在带有重试的后台作业(如Sidekiq)中运行HTTP请求,则可以减轻来自其他服务器的错误。虽然你将需要处理服务器不会及时响应的情况。

如果你需要在控制器操作中运行HTTP请求,那么你应该解除Net::ReadTimeout错误,为用户提供替代体验并在错误监控解决方案中跟踪它。例如:

5. ActiveRecord::RecordNotUnique:PG::UniqueViolation

该错误消息专门针对PostgreSQL数据库,但针对MySQL和SQLite的ActiveRecord适配器会引发类似的错误。这里的问题是,应用程序中的数据库表在一个或多个字段上具有唯一索引,并且事务已发送到违反该索引的数据库。这是一个难以完全解决的难题,但我们先从简单的来。

假设你已经创建了一个User模型,并在迁移过程中确保用户的电子邮件地址是唯一的。迁移可能像这样:

为了避免大部分ActiveRecord::RecordNotUnique实例,你应该为你的User模型添加唯一性验证。

如果没有此验证,则在调用User#save时,所有电子邮件地址都将发送到数据库,并且如果它们不是唯一的,则会引发错误。但是,验证不能保证这不会发生。有关完整说明,你应该阅读validates_uniqueness_of文档的并发性和完整性部分。简单描述就是,Rails的唯一性检查倾向于根据多个请求的操作顺序进行竞争。作为竞争条件,这也使得这个错误很难在本地重现。

处理这个错误需要一些上下文。如果错误是由竞争条件引起的,那可能是因为用户错误地提交了两次表单。我们可以尝试用一些JavaScript代码——即在第一次点击后禁用提交按钮——来缓解这个问题。开始:

Coderwall上的这个使用ActiveRecord的first_or_create!的小技巧以及在出现错误时进行救援和重试是一种简洁的解决方法。你应该继续使用错误监控解决方案记录错误,以便保持对错误的了解。

ActiveRecord::RecordNotUnique可能看起来像是一个边缘案例,但它在这个错误名单中位列第5,所以对于用户体验来说绝对值得考虑。

6. NoMethodError:针对nil:NilClass的未定义方法'id'

NoMethodError再次出现,但是这次针对的是不同的解释性消息。这个错误通常在带关系的对象的create操作周围悄然出现。愉快路径——成功创建对象——通常有效,但验证失败时会弹出此错误。让我们来看一个例子。

这是一个控制器,其中包含用于创建课程应用程序的操作。

新模板中的表单看起来有点像这样:

这里的问题是当你从create操作调用render :new时,没有设置@course实例变量。你需要确保new模板所需的所有对象也都在create操作中初始化。为了解决这个错误,我们将create操作更新为如下所示:

点击了解Rails中的nil问题以及如何避免这些问题。https://blog.ragnarson.com/2015/05/06/problems-with-nil.html

7. ActionController::ParameterMissing

这个错误是Rails强参数实现的一部分。它虽然没有显示为500错误——但被ActionController::Base救援并返回为400 Bad Request。

完整的错误可能如下所示:

这将伴随着一个看起来有点像这样的控制器:

params.require(:user)意味着如果调用user_params并且params没有:user键或params[:user]为空,则会引发ActionController::ParameterMissing。

如果通过Web前端构建要使用的应用程序,并且已经构建了表单以将user参数正确发布到此操作,那么缺少user参数可能意味着有人正在搞乱你的应用程序。如果是这种情况,400 Bad Request响应可能是最佳响应,因为你不需要迎合潜在的恶意用户。

如果你的应用程序正在提供API,那么400 Bad Request也是对参数缺少的合适响应。

8. ActionView::Template::Error:未定义的本地变量或方法

这是前10位名单中唯一的ActionView错误,而且这是一个好的迹象。视图必须做的工作越少,渲染模板越好。更少的工作导致更少的错误。尽管如此,我们仍然留下了这个错误,当你期望存在的变量或方法根本不存在的时候。

这种情况最常见于partial中,可能是由于你可以在页面上包含带局部变量的partial有许多不同的方式。如果你在控制器中有一个命名为_post.html.erb的包含博客文章模板和实例变量@post集的partial,则可以像下面这样渲染partial:

或者

或者

Rails喜欢给我们提供大量选择,但这里的第二个和第三个选项可能会导致混淆。尝试像这样渲染一个partial:

或者

这会留给你一个未定义的局部变量或方法。为了避免这种情况,请保持一致并始终使用显式的部分语法来呈现partial,并在局部哈希中表示局部变量:

partial中还有一个地方是你可能会因为局部变量出错的。如果你只是有时将变量传递给partial,则在partial中对该变量到常规Ruby代码的测试是不同的。例如,如果你更新上面贴出的partial以获取一个局部变量——说明是否在partial中显示header图像,则可以像这样渲染partial:

然后partial本身可能看起来像这样:

当你传递show_header_image局部变量时,这将工作良好,但是当你调用

它会失败因为有一个未定义的局部变量。为了测试partial中是否存在局部变量,应在使用它之前检查它是否已定义。

更好的是,在partial中我们可以替代性地使用命名为local_assigns的散列。

对于不是布尔值的变量,我们可以使用其他散列方法(如fetch)来优雅地处理这些变量。以show_header_image为例,这个场景也可以工作:

总的来说,当你将变量传递给partials的时候要小心!

9. ActionController::UnknownFormat

这个错误,像ActionController::InvalidAuthenticityToken一样A,可能是由于粗心或恶意用户而非应用程序造成的。如果你已经构建了一个应用程序,其中的操作使用HTML模板进行响应,且有人请求该页面的JSON版本,则你会在日志中发现这样的错误,它看起来像这样:

用户将收到406 Not Acceptable的响应。在这种情况下,他们会看到此错误是因为你尚未为此响应定义模板。这是一个合理的响应,因为如果你不想返回JSON,那么他们的请求是不可接受的。

但是,你可能已经构建了Rails应用程序,以响应常规的HTML请求以及在同一控制器中的更多类似API的JSON请求。一旦你开始这样做,你就定义了你想要响应的格式,并且任何以外的格式都会导致ActionController::UnknownFormat返回一个406状态。假设你的博客文章索像这样:

提出JSON请求会导致406响应,并且你的日志会显示这种表达性较少的错误:

这次的错误并不是抱怨缺少模板——这是一个有意的错误,因为你已经定义了唯一的格式来作为HTML响应。如果这是无意的呢?

在你打算支持的响应中缺少格式很常见。在创建博客文章时,考虑操作要响应HTML和JSON请求,以便你的页面可以支持Ajax请求。它可能看起来像这样:

这里的错误是在博客文章未通过验证且未保存的情况下引发的。在respond_to块中,你需要在格式块的范围内调用render。 重写代码以改善失败:

现在所有的格式都被覆盖了,且不会再有无意的ActionController::UnknownFormat异常。

10. StandardError:发生错误,此次以及所有稍后的迁移均被取消

名单的最后一个错误让我有点失望。StandardError 是所有其他错误应继承的基本错误类,因此,在此处使用会使错误感觉非常通用,然而实际上,这是一个在数据库迁移过程中发生的错误。我更愿意将此错误视为ActiveRecord::MigrationError的派生物。但我离题了...

有很多事情会导致迁移失败。例如,迁移可能与实际生产数据库不同步。在这种情况下,你将不得不到处寻找发生了什么并修复它。

但是,这里应该包括一件事情:数据迁移。

如果你需要为表中的所有对象添加或计算某些数据,则你可能认为数据迁移是一个好主意。举个例子,如果你想给一个包含名字和姓氏的用户模型添加一个全名字段(实际不大可能发生,但是作为一个简单的例子来说足够了),你可以写下如下的迁移:

这种情况有很多问题。如果在我们的集合中存在有损坏数据的用户,那么user.save!命令将抛出错误并取消迁移。其次,在生产中,可能会有很多用户,这意味着数据库迁移需要很长时间,而这可能会使应用程序始终处于脱机状态。最后,随着应用程序随时间变化,你可能会删除或重命名User模型,这会导致此迁移失败。有些建议会提倡你在迁移中定义一个User模型以避免这种情况。为了更安全起见,Elle Meredith建议我们完全避免ActiveRecord迁移中的数据迁移,并建立临时数据迁移任务。

在迁移之外更改数据可确保执行一些操作。最重要的是,它使得你考虑如果数据不存在,你的模型如何工作。在我们的全名示例中,如果数据可用,你可能会为full_name属性定义一个访问器。如果不可用,则通过连接组成部分来建立全名。

将数据迁移作为单独的任务运行也意味着部署不再依赖于整个生产数据库的数据更改。 Elle的文章中有更多的理由说明为什么这种方法更好,且包含了编写任务的最佳实践。

结论

Rails错误可能来自应用程序中的任何地方。在这篇文章中,我们已经看到了模型,视图和控制器中表现出来的常见错误。有些错误并不一定需要担心,只是为了保护应用程序而言。有些错误则应该尽快探究源头并清除。

尽管如此,跟踪这些错误发生的频率总是好的。这有助于更好地查看影响用户或应用程序安全性的问题,以便你快速修复问题。否则,这些错误信息将显示给用户,但工程和产品管理团队却在用户抱怨要求支持之前一无所知。

希望你能从这篇文章中学到新的东西,帮助更好地避免将来出现这些错误。但是,即使采用最佳做法,生产中也会出现意想不到的错误。了解影响用户的错误并找到快速解决问题的好工具非常重要。

编码快乐!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值