提示是自动检查源代码中的程序和风格错误。这种检查是由一个叫做 "林特 "的静态代码分析工具进行的。而代码格式化器则是一种关注源代码格式化的工具,使其严格遵守一套预先配置的规则。林特器通常会报告违规行为,但通常是由程序员来解决这个问题,而代码格式化器倾向于将其规则直接应用于源代码,从而自动纠正格式化错误。
在一个项目中创建一个更一致的代码风格的任务通常需要引入一个单独的提示和格式化工具,但在某些情况下,一个工具将能够解决这两个问题。后者的一个很好的例子是RuboCop,它是我们在本文中要广泛考虑的工具。你将学习如何在你的Ruby项目中设置它,并调整其配置选项,使其输出符合你的期望。除了将它集成到你的本地开发过程中,你还将学习如何使它成为你持续集成工作流程的一部分。
安装RuboCop
通过RubyGems安装RuboCop是很简单的。
$ gem install rubocop
检查所安装的版本。
$ rubocop --version
1.18.3
如果你想使用Bundler,把下面的片段放在你的Gemfile ,然后运行bundle install 。require: false 部分告诉Bundler.require ,不要在你的代码中要求特定的宝石,因为它只在命令行中使用。
gem 'rubocop', require: false
检查所安装的版本。
$ bundle exec rubocop --version
1.18.3
运行RuboCop
你可以通过输入rubocop (如果与Bundler一起安装,则输入bundle exec rubocop ),在你的项目中使用其默认设置运行RuboCop。如果你没有给该命令传递任何参数,它将检查当前目录下的所有Ruby源文件,以及所有子目录。另外,你也可以传递一个需要分析的文件和目录的列表。
$ bundle exec rubocop
$ bundle exec rubocop src/lib
不需要任何配置,RuboCop就会执行社区驱动的Ruby风格指南中列出的许多准则。运行该命令后,你可能会得到几个错误(罪行)。每一个报告的违法行为都有解决它所需的所有信息,如违法行为的描述,以及发生错误的文件和行号。

在报告的底部,你会看到一行描述被检查文件的数量、违法行为的总数,以及有多少违法行为可以被自动修复。如果你附加了-a 或--auto-correct 参数,RuboCop将尝试自动纠正在你的源文件中发现的问题(那些以[Correctable] 为前缀的文件)。
$ bundle exec rubocop -a

请注意,现在每个被纠正的违法行为的前缀是[Corrected] 。在报告的底部还显示了一个被纠正的违法行为的数量摘要。在上面的例子中,还有一个可纠正的违法行为没有被自动纠正,即使在附加了-a 标志之后。这是因为一些自动更正可能会稍微改变代码的语义,所以RuboCop认为它是不安全的。如果你也想自动纠正这些违法行为,请使用-A 或--auto-correct-all 标志。
$ bundle exec rubocop -A

一个好的经验法则是在使用自动更正功能后运行你的测试套件,以确保你的代码的行为没有发生意外的变化。
配置RuboCop
RuboCop可以通过放置在项目根部的.rubocop.yml 文件进行配置。如果你想对所有项目使用相同的检查,你可以在你的主目录(~/.rubocop.yml)或XDG配置目录(~/.config/rubocop/config.yml)中放置一个全局配置文件。如果在当前目录或连续的父目录中没有找到本地范围的项目配置文件,这个全局配置文件将被使用。
RuboCop的默认配置放在它的配置主目录下(~/.config/rubocop/default.yml),所有其他的配置文件都继承于它。这意味着在设置你的项目配置时,你只需要做一些与默认值不同的修改。这可能意味着启用或禁用某些检查,或者改变它们的行为,如果它们接受任何参数的话。
RuboCop将每个单独的检查称为警察,每个警察负责检测特定的违法行为。可用的警察也被归入以下部门:
- 风格警察主要是基于前面提到的Ruby风格指南,他们检查你的代码的一致性。
- 布局检查员捕捉与格式有关的问题,例如空白的使用。
- Lint警察检测你的代码中可能存在的错误,类似于
ruby -w,但有大量的附加检查。 - 公制警察处理与源代码测量有关的问题,如类的长度和方法的长度。
- 命名警察关注的是命名惯例。
- 安全警察帮助捕捉潜在的安全问题。
- Bundler警察检查Bundler文件中的不良做法(如
Gemfile)。 - Gemspec警察检查
.gemspec文件中的不良行为。
也可以通过附加的linters和formatters来扩展RuboCop。你可以建立你自己的扩展或利用现有的 扩展,如果它们与你的项目有关的话。例如,一个Rails扩展可以用来执行Rails的最佳实践和编码惯例。

当你第一次创建你的配置文件时,你会收到一连串的消息,警告你有新的警察被添加但没有配置。这是因为RuboCop在每个版本中都会添加新的警察,这些警察被设置为特殊的待定状态,直到它们在用户配置中被明确启用或禁用。你可以单独启用或禁用信息中列出的每个警察,或者使用下面的片段来启用所有新警察(推荐)。之后,这些消息将被抑制。
# .rubocop.yml
AllCops:
NewCops: enable
如果你不想摆弄配置文件和RuboCop提供的大量选项,可以考虑看一下标准项目。它主要是RuboCop的预配置版本,目的是在你的Ruby项目中执行统一的风格,而不允许定制任何规则。它首次公布的闪电演讲给出了关于其起源和动机的更多细节。
你可以在你的Gemfile ,然后运行bundle install 来安装它。
# Gemfile
gem "standard", group: [:development, :test]
之后,你可以从命令行中执行Standard,方法如下。
$ bundle exec standardrb
将RuboCop添加到一个现有的项目中
大多数Ruby主义者没有机会在新项目中工作。我们的大部分开发时间都花在了传统的代码库上,这些代码库可能会产生大量无法立即解决的刷新问题。幸运的是,RuboCop有一个有用的功能,可以生成一个现有违规行为的允许列表,可以随着时间的推移慢慢解决。这样做的好处是,它允许你在现有的项目中引入品评,而不至于被一堆无法处理的品评错误所困扰,同时又能标记出任何新的违规行为。
$ bundle exec rubocop
523 files inspected, 1018 offenses detected
创建 allowlist 配置文件可以通过下面的命令完成。
$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.
--auto-gen-config 选项收集了所有的违规行为和它们的数量,并在当前目录下生成一个.rubocop_todo.yml 文件,其中所有当前的违规行为都被忽略了。最后,它使.rubocop.yml 继承自.rubocop_todo.yml 文件,这样在代码库上再次运行RuboCop就不会产生任何违法行为。
$ bundle exec rubocop
523 files inspected, no offenses detected
在生成allowlist文件时,如果违法行为的数量超过某个阈值(默认为15),RuboCop将完全关闭一个警察。这通常不是你想要的,因为它阻止了新的代码因为现有的违规数量而被检查。幸运的是,我们可以提高阈值,这样即使违规数量很高,也不会禁用警察。
$ bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000
--auto-gen-only-exclude 选项确保允许列表中的每个条子都有一个Exclude 块,列出所有发生违规的文件,而不是Max ,它设置了一个条子的最大排除文件数。设置--exclude-limit ,也改变了每个cop可以被添加到Exclude 块的最大文件数。指定一个大于被检查的文件总数的任意数字,可以确保没有警察会被直接禁用,任何添加到现有或新文件的新代码都会被相应地检查。
修复现有的违规行为
在生成.rubocop_todo.yml 文件之后,重要的是不要忘记现有的违规行为,而是要慢慢地一个接一个地解决它们。你可以通过从cop的Exclude 块中删除一个文件来做到这一点,然后修复报告的违规行为,运行你的测试套件以避免引入bug,并提交。一旦你从一个cop中删除了所有的文件,你可以从文件中手动删除该cop,或者再次重新生成allowlist文件。不要忘记在可能的情况下利用--auto-correct 选项,以使这个过程更快。
采用风格指南
RuboCop是非常可配置的,这使得它对任何类型的项目都是可行的。然而,要按照你的要求配置规则可能需要很长的时间,特别是如果你不同意许多默认的规则。在这种情况下,采用一个现有的风格指南可能是有益的。一些公司已经发布了他们的Ruby风格指南供公众使用,如Shopify和Airbnb。在RuboCop中使用你喜欢的风格指南可以通过添加相关的宝石到你的Gemfile 来实现。
# Gemfile
gem "rubocop-shopify", require: false
然后,在你的项目配置中要求它。
# .rubocop.yml
inherit_gem:
rubocop-shopify: rubocop.yml
抑制提示性错误
尽管RuboCop是一个很好的工具,但它有时会产生误报,或者建议以不利于程序员意图的方式修复代码。当出现这种情况时,你可以在源代码中用注释来忽略这种违规行为。你可以提到要禁用的个别警察或部门,如下所示。
# rubocop:disable Layout/LineLength, Style
[..]
# rubocop:enable Layout/LineLength, Style
或者你可以一次性地禁用代码中某一部分的所有警察。
# rubocop:disable all
[..]
# rubocop:enable all
如果你使用行末注释,指定的警察将被单独禁用在该行。
for x in (0..10) # rubocop:disable Style/For
编辑器集成
当你在编辑器中输入代码时,查看由RuboCop产生的警告和错误是很方便的,而不是每次都要通过命令行进行检查。值得庆幸的是,RuboCop在大多数流行的代码编辑器和IDE中都可以集成,主要是通过第三方插件。在Visual Studio Code中,你所需要做的就是安装这个Ruby扩展,并在你的用户settings.json 文件中放置以下内容。
{
"ruby.lint": {
"rubocop": true
}
}
如果你使用Vim或Neovim,你可以通过coc.nvim显示RuboCop的诊断程序。你需要安装Solargraph语言服务器(gem install solargraph),然后是coc-solargraph扩展(:CocInstall coc-solargraph)。之后,如下面所示配置你的coc-settings.json 文件。
{
"coc.preferences.formatOnSaveFiletypes": ["ruby"],
"solargraph.autoformat": true,
"solargraph.diagnostics": true,
"solargraph.formatting": true
}
设置一个预提交钩子
要确保项目中的所有Ruby代码在被检查到源码控制之前被正确地加注和格式化,一个很好的方法是设置一个Git预提交钩子,在每个阶段的文件上运行RuboCop。本文将告诉你如何用Overcommit来设置,Overcommit是一个管理和配置Git预提交钩子的工具,但如果你已经有一个现有的预提交工作流程,你也可以将RuboCop与其他工具集成。
首先,通过RubyGems安装Overcommit,然后在你的项目中安装它。
$ gem install overcommit
$ overcommit --install # at the root of your project
上面的第二条命令将在当前目录下创建一个针对 repo 的设置文件 (.overcommit.yml),并备份任何现有的钩子。这个文件扩展了默认配置,所以你只需要在默认的基础上指定你的配置。例如,你可以通过以下片段启用RuboCop预提交钩子。
# .overcommit.yml
PreCommit:
RuboCop:
enabled: true
on_warn: fail
problem_on_unmodified_line: ignore
command: ['bundle', 'exec', 'rubocop']
on_warn: fail 设置导致Overcommit将警告视为失败,而problem_on_unmodified_line: ignore 导致未被分档的行的警告和错误被忽略。你可以在项目的GitHub页面上浏览所有可用的钩子选项和它们的可接受值范围。你可能需要在修改配置文件后运行overcommit --sign ,以使修改生效。
偶尔,如果你想提交一个没有通过所有检查的文件(比如一个正在进行的工作),你可以根据具体情况跳过个别检查。
$ SKIP=RuboCop git commit -m "WIP: Unfinished work"
将RuboCop添加到你的CI工作流程中
在每个拉动请求上运行RuboCop检查是防止格式不好的代码被合并到项目中的另一种方法。尽管你可以用任何CI工具进行设置,但本文只讨论如何通过GitHub Actions运行RuboCop。
第一步是在你的项目根部创建一个.github/workflows 目录,并在新目录下创建一个rubocop.yml 文件。在编辑器中打开该文件,按如下方式更新。
# .github/workflows/rubocop.yml
name: Lint code with RuboCop
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Run RuboCop
run: bundle exec rubocop
上面的工作流文件描述了一个单一的工作,当代码被推送到GitHub或针对任何分支提出拉取请求时,该工作将被执行。job 是一连串的步骤,按顺序运行。这个特定的工作将在 GitHub Actions 提供的最新 Ubuntu、MacOS 和 Windows 版本上运行一次(如runs-on 和strategy.matrix 所定义)。第一步是检查仓库中的代码,而下一步是设置Ruby工具链和依赖关系,最后一步是执行RuboCop。
一旦你完成了对文件的编辑,保存它,提交,并推送到GitHub。之后,你会得到一个内联显示,在随后的签入和拉动请求中显示任何报告的问题。
其他自动格式化工具
尽管RuboCop提供了全面的自动格式化功能,但如果它不能充分满足你的需求,了解其他工具也很重要。
伪装
Prettier最初是一个针对JavaScript的有意见的代码格式化工具,但它现在支持许多其他语言,包括Ruby。安装它的Ruby插件很简单:将prettier gem添加到你的Gemfile ,然后运行bundle 。
# Gemfile
gem 'prettier'
在这一点上,你可以通过以下命令用Prettier格式化你的Ruby代码。
$ bundle exec rbprettier --write '**/*.rb'
Prettier的一些规则与RuboCop的规则相冲突,所以有必要禁用后者的格式化检查,这样它就不会干扰Prettier。幸运的是,关闭与Prettier冲突或不必要的RuboCop检查很容易。你所需要做的就是在你项目的.rubocop.yml 文件的顶部继承Prettier的RuboCop配置。
# .rubocop.yml
inherit_gem:
prettier: rubocop.yml
当你从现在开始运行RuboCop(与bundle exec rubocop)时,它将不会报告任何与布局有关的违法行为,为Prettier根据自己的规则纠正它们铺平道路。你也可以通过它的配置文件来配置Prettier的输出,它可以在同一个项目的JavaScript和Ruby代码之间共享。
RubyFmt
RubyFmt是一个全新的代码格式化器,用Rust编写,目前正在积极开发中。像Prettier一样,它的目的是成为一个格式化器而不是一个代码分析工具。它还没有一个稳定的版本,所以你也许应该暂时不要采用它,但它绝对是一个值得关注的工具。
总结
提示和自动格式化代码给代码库带来了很多好处,特别是在一个开发团队的背景下。即使你不喜欢别人告诉你如何格式化你的代码,你也需要记住,提示不仅仅是为了你自己。它也是为与你合作的其他人准备的,这样每个人都能坚持同样的惯例,从而消除了在同一个项目中处理多种编码风格的弊端。
同样重要的是,不要把linter的输出当作福音,所以要努力把它配置成能给你带来最大好处的方式,同时又不分散你的主要目标。有了RuboCop广泛的配置设置,这应该不是一个问题。然而,如果你发现配置RuboCop占用了你太多的时间,你可以使用预定义的风格指南,就像前面讨论的那样,或者采用Standard的无配置替代方案,每个人都可以直接使用而不用担心这些小细节。
谢谢你的阅读,并祝你编码愉快