Homebrew 中的大部分代码都是用动态语言 Ruby 编写的。为了利用静态类型检查的优势,我们在代码库中设置了 Sorbet,它为 Ruby 等动态语言提供了静态类型检查的优势。
如果您想深入了解 Sorbet 及其功能,Sorbet 文档 是一个不错的起点。
sig
方法用于注释方法签名。这是一个简单的示例
class MyClass
sig { params(name: String).returns(String) }
def my_method(name)
"Hello, #{name}!"
end
end
使用 params
,我们指定有一个参数 name
,它必须是 String
,而使用 returns
,我们指定此方法始终返回 String
。
有关如何表达更复杂类型的更多信息,请参阅官方文档
.rbi
)RBI 文件 帮助 Sorbet 了解常量、祖先和以它本机无法理解的方式定义的方法。我们还可以创建一个 RBI 文件来帮助 Sorbet 理解动态定义。
有时需要显式包含 Kernel
模块,以便 Sorbet 知道在给定上下文中可以使用 puts
等方法。这对于模块来说是必需的,因为它们既可以在 BasicObject
(不包括 Kernel
)中使用,也可以在 Object
(默认情况下包括 Kernel
)中使用。在这种情况下,有必要创建一个 .rbi
文件(示例),因为在实际代码中重新包含 Kernel
模块可能会破坏某些内容。
Library/Homebrew/sorbet
目录rbi
目录包含通过运行 brew typecheck --update
自动生成的所有 Ruby 接口(.rbi
)文件
srb rbi hidden-definitions
生成的。srb rbi todo
生成缺失常量的定义。config
文件是传递给 srb tc
的参数的新行分隔列表,就像在命令行中传递一样。config 文件中的参数始终首先传递,然后是命令行中提供的参数。我们使用它来忽略我们不想进行类型检查的 Gem 目录。
代码库中的每个 Ruby 文件顶部都有一个神奇的 # typed: <level>
注释,其中 <level>
是 Sorbet 的严格级别 之一,通常是 false
、true
或 strict
。false
文件只报告与语法、常量解析和方法签名的正确性相关的错误,但不报告类型错误。我们的长期目标是将所有 false
文件移至 true
,并在这些文件上也开始报告类型错误。因此,在添加新文件时,你应该理想地用 # typed: true
标记它,并解决任何由此产生的类型错误。
brew typecheck
在不带任何参数运行时,brew typecheck
将在考虑 Homebrew 核心代码库中每个 Ruby 文件中设置的严格级别的情况下运行。但是,当它在特定文件或目录上运行时,可能会出现更多错误,因为 Sorbet 无法解析指定文件范围之外定义的常量。这些问题可以用 RBI 文件解决。目前 brew typecheck
提供 --quiet
、--file
、--dir
和 --ignore
选项,但你可以使用 srb tc --help
探索更多选项,并用 srb tc
传递它们。
Sorbet 会报告类型错误以及错误参考代码,该代码可用于查找有关如何调试错误或导致错误的原因的更多信息,请参阅 Sorbet 文档。以下是调试一些常见类型错误的方法
使用 T.reveal_type
:在 true
或更高的文件中,通过将变量或方法调用包装在 T.reveal_type
中,Sorbet 将向我们展示它认为该变量在 srb tc
的输出中拥有的类型。这在编写 方法签名 和调试时特别有用。确保在提交更改之前从代码中删除此行,因为这只是一个调试工具。
我们遇到的最常见的错误之一是 7003:方法不存在。
由于 Ruby 是一种非常动态的语言,因此可以以 Sorbet 无法静态看到的方式定义方法。在这种情况下,检查该方法在运行时是否存在;如果不存在,那么 Sorbet 已经捕获到了未来的错误!但是,即使在运行时存在方法,Sorbet 也可能无法看到它。在这种情况下,我们使用 .rbi
文件。
由于 Sorbet 不会自动假定 Kernel 要包含在模块中,因此在尝试使用诸如 puts
、ohai
、odebug
等方法时,我们可能会遇到许多错误。一个简单的解决方法是在相应的 RBI 文件中添加额外的 include Kernel
行。
以上提示非常通用,适用于许多情况。有关使用 Sorbet 时的一些常见问题,请参阅 Sorbet 错误参考 和 常见问题解答。