简介
榜样很重要。
本 Ruby 风格指南推荐最佳实践,以便现实世界中的 Ruby 程序员可以编写可由其他现实世界中的 Ruby 程序员维护的代码。反映现实世界用法的风格指南会被使用,而坚持一个被人们拒绝的理想的风格指南则有风险根本不会被使用——无论它有多好。
本指南分为几个相关指南部分。我们尝试添加了指南背后的基本原理(如果省略了,我们假设它很明显)。
我们并非凭空想出所有指南——它们主要基于编辑们的专业经验、来自 Ruby 社区成员的反馈和建议,以及各种备受推崇的 Ruby 编程资源,例如 "Programming Ruby" 和 "The Ruby Programming Language"。
本风格指南随着时间的推移而发展,因为会识别出额外的约定,而过去的约定会因 Ruby 本身的更改而变得过时。
提示
|
如果您使用 Rails 或 RSpec,您可能想查看补充的 Ruby on Rails 风格指南 和 RSpec 风格指南。 |
提示
|
RuboCop 是一个基于本风格指南的静态代码分析器(代码风格检查器)和格式化程序。 |
指导原则
程序必须为人们编写,而仅仅是偶然地为机器执行。
计算机程序的结构和解释
众所周知,代码被阅读的次数远多于被编写的次数。这里提供的指南旨在提高代码的可读性,并使其在各种 Ruby 代码中保持一致。它们也旨在反映 Ruby 的现实世界用法,而不是随机的理想。当我们必须在非常成熟的做法和主观上更好的替代方案之间做出选择时,我们选择推荐成熟的做法。[1]
在 Ruby 社区中,某些领域没有明确的共识,例如字符串文字引号、哈希文字中的空格、多行方法链中的点位置等。在这些情况下,所有流行的风格都得到认可,您可以选择一种并始终如一地应用它。
在创建本指南之前,Ruby 已经存在了 15 年以上,该语言的灵活性以及缺乏通用标准导致了几乎所有内容的多种风格。让大家围绕社区标准的理念团结起来需要大量的时间和精力,我们还有很多工作要做。
Ruby 以其对程序员友好的优化而闻名。我们相信本指南将帮助您优化以实现最大的程序员幸福感。
关于一致性的说明
愚蠢的一致性是小人物的恶灵,受到小政治家、哲学家和神学家们的崇拜。
风格指南是关于一致性的。与本风格指南保持一致很重要。项目内部的一致性更为重要。一个类或方法内部的一致性是最重要的。
但是,要知道何时不一致——有时风格指南的建议并不适用。如有疑问,请使用您的最佳判断。查看其他示例并决定哪种看起来最好。不要犹豫,随时提问!
特别是:不要为了遵守本指南而破坏向后兼容性!
忽略特定指南的其他一些充分理由
-
当应用该指南会使代码可读性降低时,即使对于习惯阅读遵循本风格指南的代码的人来说也是如此。
-
为了与周围也违反该指南的代码保持一致(可能是出于历史原因)——尽管这也是清理他人混乱的机会(以真正的 XP 风格)。
-
因为所讨论的代码早于该指南的引入,并且没有其他理由修改该代码。
-
当代码需要与不支持风格指南推荐的功能的旧版 Ruby 保持兼容时。
源代码布局
几乎每个人都认为,除了他们自己的风格之外,其他所有风格都是丑陋且难以阅读的。去掉“除了他们自己的风格之外”,他们可能就对了……
制表符还是空格?
仅使用空格进行缩进。不要使用硬制表符。
缩进
每个缩进级别使用两个空格(也称为软制表符)。
# bad - four spaces
def some_method
do_something
end
# good
def some_method
do_something
end
行尾
使用 Unix 风格的行尾。[2]
提示
|
如果您使用的是 Git,您可能需要添加以下配置设置来保护您的项目免受 Windows 行尾的侵入
|
每行一个表达式
每行使用一个表达式。
# bad
puts 'foo'; puts 'bar' # two expressions on the same line
# good
puts 'foo'
puts 'bar'
puts 'foo', 'bar' # this applies to puts in particular
空格和运算符
在运算符周围、逗号、冒号和分号之后使用空格。空格可能(大部分)与 Ruby 解释器无关,但正确使用空格是编写易于阅读代码的关键。
# bad
sum=1+2
a,b=1,2
class FooError<StandardError;end
# good
sum = 1 + 2
a, b = 1, 2
class FooError < StandardError; end
有一些例外
-
指数运算符
# bad
e = M * c ** 2
# good
e = M * c**2
-
有理数文字中的斜杠
# bad
o_scale = 1 / 48r
# good
o_scale = 1/48r
-
安全导航运算符
# bad
foo &. bar
foo &.bar
foo&. bar
# good
foo&.bar
安全导航
避免 &.
的链式调用。用 .
和显式检查替换。例如,如果用户保证有地址,并且地址保证有邮政编码
# bad
user&.address&.zip
# good
user && user.address.zip
如果这种更改引入了过多的条件逻辑,请考虑其他方法,例如委托
# bad
user && user.address && user.address.zip
# good
class User
def zip
address&.zip
end
end
user&.zip
空格和大括号
(
、[
之后没有空格,]
、)
之前没有空格。在大括号周围和 }
之前使用空格。
# bad
some( arg ).other
[ 1, 2, 3 ].each{|e| puts e}
# good
some(arg).other
[1, 2, 3].each { |e| puts e }
{
和 }
需要一些澄清,因为它们用于块和哈希文字,以及字符串插值。
对于哈希文字,两种风格都被认为是可以接受的。第一个变体更易读(并且可以说在 Ruby 社区中更受欢迎)。第二个变体的优点是增加了块和哈希文字之间的视觉差异。无论您选择哪一个 - 一致地应用它。
# good - space after { and before }
{ one: 1, two: 2 }
# good - no space after { and before }
{one: 1, two: 2}
对于插值表达式,大括号内不应该有填充空格。
# bad
"From: #{ user.first_name }, #{ user.last_name }"
# good
"From: #{user.first_name}, #{user.last_name}"
将 when
缩进到 case
的级别
将 when
缩进到与 case
相同的级别。
# bad
case
when song.name == 'Misty'
puts 'Not again!'
when song.duration > 120
puts 'Too long!'
when Time.now.hour > 21
puts "It's too late"
else
song.play
end
# good
case
when song.name == 'Misty'
puts 'Not again!'
when song.duration > 120
puts 'Too long!'
when Time.now.hour > 21
puts "It's too late"
else
song.play
end
缩进条件赋值
将条件表达式的结果赋值给变量时,保留其分支的常规对齐方式。
# bad - pretty convoluted
kind = case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end
result = if some_cond
calc_something
else
calc_something_else
end
# good - it's apparent what's going on
kind = case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end
result = if some_cond
calc_something
else
calc_something_else
end
# good (and a bit more width efficient)
kind =
case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end
result =
if some_cond
calc_something
else
calc_something_else
end
方法之间空行
在方法定义之间使用空行,以及在方法内部将方法分解成逻辑段落。
# bad
def some_method
data = initialize(options)
data.manipulate!
data.result
end
def some_other_method
result
end
# good
def some_method
data = initialize(options)
data.manipulate!
data.result
end
def some_other_method
result
end
两个或多个空行
不要连续使用多个空行。
# bad - It has two empty lines.
some_method
some_method
# good
some_method
some_method
属性访问器周围的空行
在属性访问器周围使用空行。
# bad
class Foo
attr_reader :foo
def foo
# do something...
end
end
# good
class Foo
attr_reader :foo
def foo
# do something...
end
end
访问修饰符周围的空行
在访问修饰符周围使用空行。
# bad
class Foo
def bar; end
private
def baz; end
end
# good
class Foo
def bar; end
private
def baz; end
end
主体周围的空行
不要在方法、类、模块、块主体周围使用空行。
# bad
class Foo
def foo
begin
do_something do
something
end
rescue
something
end
true
end
end
# good
class Foo
def foo
begin
do_something do
something
end
rescue
something
end
end
end
方法参数中的尾随逗号
避免在方法调用中最后一个参数后使用逗号,尤其是在参数不在单独的行上时。
# bad - easier to move/add/remove parameters, but still not preferred
some_method(
size,
count,
color,
)
# bad
some_method(size, count, color, )
# good
some_method(size, count, color)
等号周围的空格
在为方法参数分配默认值时,在 =
运算符周围使用空格
# bad
def some_method(arg1=:default, arg2=nil, arg3=[])
# do something...
end
# good
def some_method(arg1 = :default, arg2 = nil, arg3 = [])
# do something...
end
虽然一些 Ruby 书籍建议使用第一种风格,但第二种风格在实践中更为突出(并且可以说更易读)。
表达式中的行延续
避免在不需要的情况下使用\
进行行延续。在实践中,避免使用行延续来进行除了字符串连接之外的任何操作。
# bad (\ is not needed here)
result = 1 - \
2
# bad (\ is required, but still ugly as hell)
result = 1 \
- 2
# good
result = 1 -
2
long_string = 'First part of the long string' \
' and second part of the long string'
多行方法链
采用一致的多行方法链风格。Ruby 社区中有两种流行的风格,它们都被认为是好的 - 前导.
和尾随.
。
前导.
当在另一行继续链式方法调用时,将.
保留在第二行。
# bad - need to consult first line to understand second line
one.two.three.
four
# good - it's immediately clear what's going on the second line
one.two.three
.four
方法参数对齐
如果方法调用的参数跨越多行,则对齐这些参数。当由于行长限制而无法对齐参数时,对第一行之后的行进行单行缩进也是可以接受的。
# starting point (line is too long)
def send_mail(source)
Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text)
end
# bad (double indent)
def send_mail(source)
Mailer.deliver(
to: '[email protected]',
from: '[email protected]',
subject: 'Important message',
body: source.text)
end
# good
def send_mail(source)
Mailer.deliver(to: '[email protected]',
from: '[email protected]',
subject: 'Important message',
body: source.text)
end
# good (normal indent)
def send_mail(source)
Mailer.deliver(
to: '[email protected]',
from: '[email protected]',
subject: 'Important message',
body: source.text
)
end
隐式选项哈希
重要
|
从 Ruby 2.7 开始,选项哈希周围的花括号不再是可选的。 |
省略隐式选项哈希周围的外部花括号。
# bad
user.set({ name: 'John', age: 45, permissions: { read: true } })
# good
user.set(name: 'John', age: 45, permissions: { read: true })
DSL 方法调用
省略属于内部 DSL(例如 Rake、Rails、RSpec)的方法的外部花括号和圆括号。
class Person < ActiveRecord::Base
# bad
attr_reader(:name, :age)
# good
attr_reader :name, :age
# bad
validates(:name, { presence: true, length: { within: 1..10 } })
# good
validates :name, presence: true, length: { within: 1..10 }
end
多行数组对齐
对齐跨越多行的数组字面量的元素。
# bad - single indent
menu_item = %w[Spam Spam Spam Spam Spam Spam Spam Spam
Baked beans Spam Spam Spam Spam Spam]
# good
menu_item = %w[
Spam Spam Spam Spam Spam Spam Spam Spam
Baked beans Spam Spam Spam Spam Spam
]
# good
menu_item =
%w[Spam Spam Spam Spam Spam Spam Spam Spam
Baked beans Spam Spam Spam Spam Spam]
命名约定
编程中唯一真正的困难是缓存失效和命名事物。
标识符使用英文
标识符使用英文命名。
# bad - identifier is a Bulgarian word, using non-ascii (Cyrillic) characters
заплата = 1_000
# bad - identifier is a Bulgarian word, written with Latin letters (instead of Cyrillic)
zaplata = 1_000
# good
salary = 1_000
符号、方法和变量使用蛇形命名法
符号、方法和变量使用 snake_case
命名。
# bad
:'some symbol'
:SomeSymbol
:someSymbol
someVar = 5
def someMethod
# some code
end
def SomeMethod
# some code
end
# good
:some_symbol
some_var = 5
def some_method
# some code
end
包含数字后缀的标识符
符号、方法和变量中的数字不要与字母分开。
# bad
:some_sym_1
some_var_1 = 1
var_10 = 10
def some_method_1
# some code
end
# good
:some_sym1
some_var1 = 1
var10 = 10
def some_method1
# some code
end
类和模块使用驼峰命名法
注意
|
CapitalCase 也称为 UpperCamelCase 、CapitalWords 和 PascalCase 。
|
类和模块使用 CapitalCase
命名。(保持缩写词如 HTTP、RFC、XML 大写)。
# bad
class Someclass
# some code
end
class Some_Class
# some code
end
class SomeXml
# some code
end
class XmlSomething
# some code
end
# good
class SomeClass
# some code
end
class SomeXML
# some code
end
class XMLSomething
# some code
end
文件使用蛇形命名法
文件命名使用 snake_case
,例如 hello_world.rb
。
目录使用蛇形命名法
目录命名使用 snake_case
,例如 lib/hello_world/hello_world.rb
。
每个文件一个类
每个源文件只包含一个类/模块。文件命名与类/模块名称一致,将 CapitalCase
替换为 snake_case
。
谓词方法后缀
谓词方法(返回布尔值的函数)的名称应该以问号结尾(例如 Array#empty?
)。不返回布尔值的函数不应该以问号结尾。
# bad
def even(value)
end
# good
def even?(value)
end
谓词方法前缀
避免在谓词方法中使用辅助动词,例如 is
、does
或 can
。这些词是多余的,并且与 Ruby 核心库中布尔方法的风格不一致,例如 empty?
和 include?
。
# bad
class Person
def is_tall?
true
end
def can_play_basketball?
false
end
def does_like_candy?
true
end
end
# good
class Person
def tall?
true
end
def basketball_player?
false
end
def likes_candy?
true
end
end
危险方法后缀
如果存在安全版本的危险方法,则潜在危险方法(例如修改self
或参数的方法、exit!
(不像exit
那样运行最终化器)等)的名称应以感叹号结尾。
# bad - there is no matching 'safe' method
class Person
def update!
end
end
# good
class Person
def update
end
end
# good
class Person
def update!
end
def update
end
end
安全方法与危险方法之间的关系
如果可能,请根据带感叹号(危险)方法定义不带感叹号(安全)方法。
class Array
def flatten_once!
res = []
each do |e|
[*e].each { |f| res << f }
end
replace(res)
end
def flatten_once
dup.flatten_once!
end
end
未使用的变量前缀
使用_
作为未使用的块参数和局部变量的前缀。也可以只使用_
(尽管它不太描述性)。此约定被 Ruby 解释器和 RuboCop 等工具识别,它们会抑制未使用的变量警告。
# bad
result = hash.map { |k, v| v + 1 }
def something(x)
unused_var, used_var = something_else(x)
# some code
end
# good
result = hash.map { |_k, v| v + 1 }
def something(x)
_unused_var, used_var = something_else(x)
# some code
end
# good
result = hash.map { |_, v| v + 1 }
def something(x)
_, used_var = something_else(x)
# some code
end
other
参数
在定义二元运算符和类似运算符的方法时,对于操作数具有“对称”语义的运算符,将参数命名为other
。对称语义意味着运算符的两侧通常是相同类型或可强制转换类型。
具有对称语义的运算符和类似运算符方法(参数应命名为other
):`, `-`, `+
, /
, %
, *
, ==
, >
, <
, |
, &
, ^
, eql?
, equal?
。
具有非对称语义的运算符(参数不应命名为other
):<<
, []
(操作数之间的集合/项目关系),===
(模式/可匹配关系)。
请注意,只有当运算符的两侧具有相同的语义时,才应遵循此规则。Ruby 核心中的一个突出例外是,例如,Array#*(int)
。
# good
def +(other)
# body omitted
end
# bad
def <<(other)
@internal << other
end
# good
def <<(item)
@internal << item
end
# bad
# Returns some string multiplied `other` times
def *(other)
# body omitted
end
# good
# Returns some string multiplied `num` times
def *(num)
# body omitted
end
控制流
for
循环
不要使用for
,除非你确切地知道为什么。大多数情况下,应该使用迭代器。for
是用each
实现的(所以你添加了一层间接性),但有一个区别 - for
不会引入新的作用域(不像each
),并且在其块中定义的变量将在其外部可见。
arr = [1, 2, 3]
# bad
for elem in arr do
puts elem
end
# note that elem is accessible outside of the for loop
elem # => 3
# good
arr.each { |elem| puts elem }
# elem is not accessible outside each block
elem # => NameError: undefined local variable or method `elem'
多行表达式中的then
不要在多行if
/unless
/when
/in
中使用then
。
# bad
if some_condition then
# body omitted
end
# bad
case foo
when bar then
# body omitted
end
# bad
case expression
in pattern then
# body omitted
end
# good
if some_condition
# body omitted
end
# good
case foo
when bar
# body omitted
end
# good
case expression
in pattern
# body omitted
end
条件放置
在多行条件语句中,始终将条件放在与if
/unless
相同的行上。
# bad
if
some_condition
do_something
do_something_else
end
# good
if some_condition
do_something
do_something_else
end
三元运算符与if
优先使用三元运算符 (?:
) 而不是 if/then/else/end
结构。它更常见,显然也更简洁。
# bad
result = if some_condition then something else something_else end
# good
result = some_condition ? something : something_else
嵌套的三元运算符
在三元运算符中,每个分支使用一个表达式。这也意味着三元运算符不能嵌套。在这种情况下,优先使用 if/else
结构。
# bad
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
# good
if some_condition
nested_condition ? nested_something : nested_something_else
else
something_else
end
if
中的分号
不要使用 if x; …
。使用三元运算符代替。
# bad
result = if some_condition; something else something_else end
# good
result = some_condition ? something : something_else
case
vs if-else
当比较值在每个子句中都相同时,优先使用 case
而不是 if-elsif
。
# bad
if status == :active
perform_action
elsif status == :inactive || status == :hibernating
check_timeout
else
final_action
end
# good
case status
when :active
perform_action
when :inactive, :hibernating
check_timeout
else
final_action
end
从 if
/case
返回结果
利用 if
和 case
是表达式这一事实,它们会返回一个结果。
# bad
if condition
result = x
else
result = y
end
# good
result =
if condition
x
else
y
end
when
中的分号
不要使用 when x; …
。请参阅上一条规则。
in
中的分号
不要使用 in pattern; …
。对于单行 in
模式分支,使用 in pattern then …
。
# bad
case expression
in pattern; do_something
end
# good
case expression
in pattern then do_something
end
!
vs not
使用 !
而不是 not
。
# bad - parentheses are required because of op precedence
x = (not something)
# good
x = !something
双重否定
避免不必要的 !!
使用。
!!
将值转换为布尔值,但在控制表达式的条件中,您不需要这种显式转换;使用它只会掩盖您的意图。
仅当有充分理由限制结果为 true
或 false
时,才考虑使用它。例如,输出到特定格式或 API(如 JSON),或作为 predicate?
方法的返回值。在这些情况下,也考虑进行空值检查:!something.nil?
。
# bad
x = 'test'
# obscure nil check
if !!x
# body omitted
end
# good
x = 'test'
if x
# body omitted
end
# good
def named?
!name.nil?
end
# good
def banned?
!!banned_until&.future?
end
and
/or
在布尔上下文中不要使用 and
和 or
- and
和 or
是控制流运算符,应该按此使用。它们的优先级非常低,可以用作指定流程序列的简写形式,例如“评估表达式 1,并且只有当它不成功(返回 nil
)时,才评估表达式 2”。这对于在不破坏阅读流程的情况下引发错误或提前返回特别有用。
# good: and/or for control flow
x = extract_arguments or raise ArgumentError, "Not enough arguments!"
user.suspended? and return :denied
# bad
# and/or in conditions (their precedence is low, might produce unexpected result)
if got_needed_arguments and arguments_valid
# ...body omitted
end
# in logical expression calculation
ok = got_needed_arguments and arguments_valid
# good
# &&/|| in conditions
if got_needed_arguments && arguments_valid
# ...body omitted
end
# in logical expression calculation
ok = got_needed_arguments && arguments_valid
# bad
# &&/|| for control flow (can lead to very surprising results)
x = extract_arguments || raise(ArgumentError, "Not enough arguments!")
避免在一个表达式中使用多个控制流运算符,因为这很快就会变得令人困惑。
# bad
# Did author mean conditional return because `#log` could result in `nil`?
# ...or was it just to have a smart one-liner?
x = extract_arguments and log("extracted") and return
# good
# If the intention was conditional return
x = extract_arguments
if x
return if log("extracted")
end
# If the intention was just "log, then return"
x = extract_arguments
if x
log("extracted")
return
end
注意
|
在社区中,使用and 和or 来组织控制流是否是一个好主意一直是一个有争议的话题。但如果你确实要使用它们,请优先使用这些运算符而不是&& /|| 。因为不同的运算符具有不同的语义,这使得更容易判断你是在处理逻辑表达式(将被简化为布尔值)还是控制流。
|
多行三元运算符
避免使用多行?:
(三元运算符);使用if
/unless
代替。
if
作为修饰符
当你有单行主体时,优先使用修饰符if
/unless
。另一个好的选择是使用控制流and
/or
。
# bad
if some_condition
do_something
end
# good
do_something if some_condition
# another good option
some_condition and do_something
多行if
修饰符
避免在非平凡的多行块末尾使用修饰符if
/unless
。
# bad
10.times do
# multi-line body omitted
end if some_condition
# good
if some_condition
10.times do
# multi-line body omitted
end
end
嵌套修饰符
避免嵌套修饰符if
/unless
/while
/until
的使用。如果合适,优先使用&&
/||
。
# bad
do_something if other_condition if some_condition
# good
do_something if some_condition && other_condition
if
vs unless
对于否定条件(或控制流||
),优先使用unless
而不是if
。
# bad
do_something if !some_condition
# bad
do_something if not some_condition
# good
do_something unless some_condition
# another good option
some_condition || do_something
使用else
与unless
不要将unless
与else
一起使用。将这些重写为正向情况优先。
# bad
unless success?
puts 'failure'
else
puts 'success'
end
# good
if success?
puts 'success'
else
puts 'failure'
end
条件周围的括号
不要在控制表达式的条件周围使用括号。
# bad
if (x > 10)
# body omitted
end
# good
if x > 10
# body omitted
end
注意
|
此规则有一个例外,即条件中的安全赋值。 |
多行 while do
不要将 while/until condition do
用于多行 while/until
。
# bad
while x > 5 do
# body omitted
end
until x > 5 do
# body omitted
end
# good
while x > 5
# body omitted
end
until x > 5
# body omitted
end
while
作为修饰符
当您有一个单行主体时,请优先使用修饰符 while/until
。
# bad
while some_condition
do_something
end
# good
do_something while some_condition
while
与 until
对于负条件,请优先使用 until
而不是 while
。
# bad
do_something while !some_condition
# good
do_something until some_condition
无限循环
当您需要无限循环时,请使用 Kernel#loop
而不是 while
/until
。
# bad
while true
do_something
end
until false
do_something
end
# good
loop do
do_something
end
带 break
的 loop
对于循环后测试,请使用带 break
的 Kernel#loop
而不是 begin/end/until
或 begin/end/while
。
# bad
begin
puts val
val += 1
end while val < 0
# good
loop do
puts val
val += 1
break unless val < 0
end
显式 return
在控制流不需要的情况下,避免使用 return
。
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
显式 self
在不需要的情况下,避免使用 self
。(仅在调用 self
写访问器、以保留字命名的方法或可重载运算符时才需要。)
# bad
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# good
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress
end
status == :verified
end
遮蔽方法
作为推论,避免用局部变量遮蔽方法,除非它们都等效。
class Foo
attr_accessor :options
# ok
def initialize(options)
self.options = options
# both options and self.options are equivalent here
end
# bad
def do_something(options = {})
unless options[:when] == :later
output(self.options[:message])
end
end
# good
def do_something(params = {})
unless params[:when] == :later
output(options[:message])
end
end
end
条件中的安全赋值
不要在条件表达式中使用 =
(赋值)的返回值,除非赋值被括号包围。这在 Ruby 程序员中是一个相当流行的习惯用法,有时被称为条件中的安全赋值。
# bad (+ a warning)
if v = array.grep(/foo/)
do_something(v)
# some code
end
# good (MRI would still complain, but RuboCop won't)
if (v = array.grep(/foo/))
do_something(v)
# some code
end
# good
v = array.grep(/foo/)
if v
do_something(v)
# some code
end
BEGIN
块
避免使用 BEGIN
块。
嵌套条件语句
避免使用嵌套条件语句来控制流程。
当可以断言数据无效时,请优先使用保护子句。保护子句是在函数开头的一个条件语句,它可以尽快地退出函数。
# bad
def compute_thing(thing)
if thing[:foo]
update_with_bar(thing[:foo])
if thing[:foo][:bar]
partial_compute(thing)
else
re_compute(thing)
end
end
end
# good
def compute_thing(thing)
return unless thing[:foo]
update_with_bar(thing[:foo])
return re_compute(thing) unless thing[:foo][:bar]
partial_compute(thing)
end
在循环中优先使用next
,而不是条件块。
# bad
[0, 1, 2, 3].each do |item|
if item > 1
puts item
end
end
# good
[0, 1, 2, 3].each do |item|
next unless item > 1
puts item
end
异常
raise
vs fail
对于异常,请优先使用raise
而不是fail
。
# bad
fail SomeException, 'message'
# good
raise SomeException, 'message'
显式抛出RuntimeError
在raise
的两个参数版本中,不要显式指定RuntimeError
。
# bad
raise RuntimeError, 'message'
# good - signals a RuntimeError by default
raise 'message'
异常类消息
请优先向raise
提供异常类和消息作为两个独立的参数,而不是异常实例。
# bad
raise SomeException.new('message')
# Note that there is no way to do `raise SomeException.new('message'), backtrace`.
# good
raise SomeException, 'message'
# Consistent with `raise SomeException, 'message', backtrace`.
ensure
中的return
不要从ensure
块中返回。如果在ensure
块中显式地从方法中返回,则返回将优先于任何抛出的异常,方法将返回,就好像没有抛出任何异常一样。实际上,异常将被静默地抛弃。
# bad
def foo
raise
ensure
return 'very bad idea'
end
隐式begin
尽可能使用隐式begin块。
# bad
def foo
begin
# main logic goes here
rescue
# failure handling goes here
end
end
# good
def foo
# main logic goes here
rescue
# failure handling goes here
end
应急方法
通过使用应急方法(由Avdi Grimm创造的术语)来减少begin
块的激增。
# bad
begin
something_that_might_fail
rescue IOError
# handle IOError
end
begin
something_else_that_might_fail
rescue IOError
# handle IOError
end
# good
def with_io_error_handling
yield
rescue IOError
# handle IOError
end
with_io_error_handling { something_that_might_fail }
with_io_error_handling { something_else_that_might_fail }
抑制异常
不要抑制异常。
# bad
begin
do_something # an exception occurs here
rescue SomeError
end
# good
begin
do_something # an exception occurs here
rescue SomeError
handle_exception
end
# good
begin
do_something # an exception occurs here
rescue SomeError
# Notes on why exception handling is not performed
end
# good
do_something rescue nil
使用rescue
作为修饰符
避免使用rescue
的修饰符形式。
# bad - this catches exceptions of StandardError class and its descendant classes
read_file rescue handle_error($!)
# good - this catches only the exceptions of Errno::ENOENT class and its descendant classes
def foo
read_file
rescue Errno::ENOENT => e
handle_error(e)
end
使用异常来控制流程
不要使用异常来控制流程。
# bad
begin
n / d
rescue ZeroDivisionError
puts 'Cannot divide by 0!'
end
# good
if d.zero?
puts 'Cannot divide by 0!'
else
n / d
end
盲目救援
避免救援Exception
类。这将捕获信号和对exit
的调用,需要您kill -9
进程。
# bad
begin
# calls to exit and kill signals will be caught (except kill -9)
exit
rescue Exception
puts "you didn't really want to exit, right?"
# exception handling
end
# good
begin
# a blind rescue rescues from StandardError, not Exception as many
# programmers assume.
rescue => e
# exception handling
end
# also good
begin
# an exception occurs here
rescue StandardError => e
# exception handling
end
异常救援顺序
将更具体的异常放在救援链的更高位置,否则它们将永远不会被救援。
# bad
begin
# some code
rescue StandardError => e
# some handling
rescue IOError => e
# some handling that will never be executed
end
# good
begin
# some code
rescue IOError => e
# some handling
rescue StandardError => e
# some handling
end
从文件读取
当仅在单个操作中从头到尾读取文件时,使用便利方法File.read
或File.binread
。
## text mode
# bad (only when reading from beginning to end - modes: 'r', 'rt', 'r+', 'r+t')
File.open(filename).read
File.open(filename, &:read)
File.open(filename) { |f| f.read }
File.open(filename) do |f|
f.read
end
File.open(filename, 'r').read
File.open(filename, 'r', &:read)
File.open(filename, 'r') { |f| f.read }
File.open(filename, 'r') do |f|
f.read
end
# good
File.read(filename)
## binary mode
# bad (only when reading from beginning to end - modes: 'rb', 'r+b')
File.open(filename, 'rb').read
File.open(filename, 'rb', &:read)
File.open(filename, 'rb') { |f| f.read }
File.open(filename, 'rb') do |f|
f.read
end
# good
File.binread(filename)
写入文件
当仅打开文件以在单个操作中创建/替换其内容时,使用便利方法File.write
或File.binwrite
。
## text mode
# bad (only truncating modes: 'w', 'wt', 'w+', 'w+t')
File.open(filename, 'w').write(content)
File.open(filename, 'w') { |f| f.write(content) }
File.open(filename, 'w') do |f|
f.write(content)
end
# good
File.write(filename, content)
## binary mode
# bad (only truncating modes: 'wb', 'w+b')
File.open(filename, 'wb').write(content)
File.open(filename, 'wb') { |f| f.write(content) }
File.open(filename, 'wb') do |f|
f.write(content)
end
# good
File.binwrite(filename, content)
释放外部资源
在ensure
块中释放程序获得的外部资源。
f = File.open('testfile')
begin
# .. process
rescue
# .. handle error
ensure
f.close if f
end
自动释放外部资源
尽可能使用资源获取方法的版本,这些版本在可能的情况下进行自动资源清理。
# bad - you need to close the file descriptor explicitly
f = File.open('testfile')
# some action on the file
f.close
# good - the file descriptor is closed automatically
File.open('testfile') do |f|
# some action on the file
end
原子文件操作
在确认文件存在检查后进行文件操作时,频繁的并行文件操作可能会导致难以重现的问题。因此,最好使用原子文件操作。
# bad - race condition with another process may result in an error in `mkdir`
unless Dir.exist?(path)
FileUtils.mkdir(path)
end
# good - atomic and idempotent creation
FileUtils.mkdir_p(path)
# bad - race condition with another process may result in an error in `remove`
if File.exist?(path)
FileUtils.remove(path)
end
# good - atomic and idempotent removal
FileUtils.rm_f(path)
标准异常
优先使用标准库中的异常,而不是引入新的异常类。
赋值与比较
并行赋值
避免使用并行赋值来定义变量。并行赋值在它是方法调用(例如Hash#values_at
)的返回值时、与 splat 运算符一起使用时或用于交换变量赋值时是允许的。并行赋值不如单独赋值易读。
# bad
a, b, c, d = 'foo', 'bar', 'baz', 'foobar'
# good
a = 'foo'
b = 'bar'
c = 'baz'
d = 'foobar'
# good - swapping variable assignment
# Swapping variable assignment is a special case because it will allow you to
# swap the values that are assigned to each variable.
a = 'foo'
b = 'bar'
a, b = b, a
puts a # => 'bar'
puts b # => 'foo'
# good - method return
def multi_return
[1, 2]
end
first, second = multi_return
# good - use with splat
first, *list = [1, 2, 3, 4] # first => 1, list => [2, 3, 4]
hello_array = *'Hello' # => ["Hello"]
a = *(1..3) # => [1, 2, 3]
处理解构赋值中的尾部下划线变量
在并行赋值期间,避免使用不必要的尾部下划线变量。命名下划线变量比下划线变量更可取,因为它们提供了上下文。当赋值左侧定义了 splat 变量且 splat 变量不是下划线时,需要使用尾部下划线变量。
# bad
foo = 'one,two,three,four,five'
# Unnecessary assignment that does not provide useful information
first, second, _ = foo.split(',')
first, _, _ = foo.split(',')
first, *_ = foo.split(',')
# good
foo = 'one,two,three,four,five'
# The underscores are needed to show that you want all elements
# except for the last number of underscore elements
*beginning, _ = foo.split(',')
*beginning, something, _ = foo.split(',')
a, = foo.split(',')
a, b, = foo.split(',')
# Unnecessary assignment to an unused variable, but the assignment
# provides us with useful information.
first, _second = foo.split(',')
first, _second, = foo.split(',')
first, *_ending = foo.split(',')
自赋值
尽可能使用简写自赋值运算符。
# bad
x = x + y
x = x * y
x = x**y
x = x / y
x = x || y
x = x && y
# good
x += y
x *= y
x **= y
x /= y
x ||= y
x &&= y
条件变量初始化简写
使用 ||=
仅在变量未初始化时初始化它们。
# bad
name = name ? name : 'Bozhidar'
# bad
name = 'Bozhidar' unless name
# good - set name to 'Bozhidar', only if it's nil or false
name ||= 'Bozhidar'
警告
|
不要使用
|
存在性检查简写
使用 &&=
预处理可能存在也可能不存在的变量。使用 &&=
仅在变量存在时才会更改其值,从而无需使用 if
检查其存在性。
# bad
if something
something = something.downcase
end
# bad
something = something ? something.downcase : nil
# ok
something = something.downcase if something
# good
something = something && something.downcase
# better
something &&= something.downcase
身份比较
在比较 object_id
时,优先使用 equal?
而不是 ==
。Object#equal?
用于比较对象的标识,而 Object#==
用于进行值比较。
# bad
foo.object_id == bar.object_id
# good
foo.equal?(bar)
类似地,优先使用 Hash#compare_by_identity
而不是使用 object_id
作为键。
# bad
hash = {}
hash[foo.object_id] = :bar
if hash.key?(baz.object_id) # ...
# good
hash = {}.compare_by_identity
hash[foo] = :bar
if hash.key?(baz) # ...
请注意,Set
也提供了 Set#compare_by_identity
。
显式使用大小写相等运算符
避免显式使用大小写相等运算符 ===
。顾名思义,它旨在被 case
表达式隐式使用,在 case
表达式之外使用它会导致一些非常混乱的代码。
# bad
Array === something
(1..100) === 7
/something/ === some_string
# good
something.is_a?(Array)
(1..100).include?(7)
some_string.match?(/something/)
注意
|
对于 BasicObject 的直接子类,使用 is_a? 不是一种选择,因为 BasicObject 不提供该方法(它是在 Object 中定义的)。在这些罕见的情况下,可以使用 === 。
|
is_a?
与 kind_of?
优先使用 is_a?
而不是 kind_of?
。这两个方法是同义词,但 is_a?
是在实际应用中更常用的名称。
# bad
something.kind_of?(Array)
# good
something.is_a?(Array)
is_a?
与 instance_of?
优先使用 is_a?
而不是 instance_of?
。
虽然这两种方法很相似,但is_a?
会考虑整个继承链(超类和包含的模块),这通常是您想要做的事情。另一方面,instance_of?
仅当对象是您正在检查的特定类的实例(而不是子类)时才返回true
。
# bad
something.instance_of?(Array)
# good
something.is_a?(Array)
instance_of?
与类比较
使用Object#instance_of?
代替类比较来判断相等性。
# bad
var.class == Date
var.class.equal?(Date)
var.class.eql?(Date)
var.class.name == 'Date'
# good
var.instance_of?(Date)
==
与 eql?
当==
可以满足需求时,不要使用eql?
。eql?
提供的更严格的比较语义在实践中很少需要。
# bad - eql? is the same as == for strings
'ruby'.eql? some_str
# good
'ruby' == some_str
1.0.eql? x # eql? makes sense here if want to differentiate between Integer and Float 1
块、Proc 和 Lambda
Proc 调用简写
当调用的方法是块的唯一操作时,使用 Proc 调用简写。
# bad
names.map { |name| name.upcase }
# good
names.map(&:upcase)
单行块分隔符
对于单行块,优先使用{…}
而不是do…end
。避免在多行块中使用{…}
(多行链式调用总是很丑陋)。对于“控制流”和“方法定义”(例如在 Rakefiles 和某些 DSL 中),始终使用do…end
。在链式调用时避免使用do…end
。
names = %w[Bozhidar Filipp Sarah]
# bad
names.each do |name|
puts name
end
# good
names.each { |name| puts name }
# bad
names.select do |name|
name.start_with?('S')
end.map { |name| name.upcase }
# good
names.select { |name| name.start_with?('S') }.map(&:upcase)
有些人会争论说,使用{…}
的多行链式调用看起来还可以,但他们应该问问自己 - 这段代码真的可读吗?块的内容可以提取到简洁的方法中吗?
单行do
…end
块
使用多行do
…end
块而不是单行do
…end
块。
# bad
foo do |arg| bar(arg) end
# good
foo do |arg|
bar(arg)
end
# bad
->(arg) do bar(arg) end
# good
->(arg) { bar(arg) }
显式块参数
考虑使用显式块参数来避免编写只将参数传递给另一个块的块字面量。
require 'tempfile'
# bad
def with_tmp_dir
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments
end
end
# good
def with_tmp_dir(&block)
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir, &block)
end
end
with_tmp_dir do |dir|
puts "dir is accessible as a parameter and pwd is set: #{dir}"
end
块参数中的尾部逗号
避免在块参数的最后一个参数后使用逗号,除非只有一个参数存在,并且删除它会影响功能(例如,数组解构)。
# bad - easier to move/add/remove parameters, but still not preferred
[[1, 2, 3], [4, 5, 6]].each do |a, b, c,|
a + b + c
end
# good
[[1, 2, 3], [4, 5, 6]].each do |a, b, c|
a + b + c
end
# bad
[[1, 2, 3], [4, 5, 6]].each { |a, b, c,| a + b + c }
# good
[[1, 2, 3], [4, 5, 6]].each { |a, b, c| a + b + c }
# good - this comma is meaningful for array destructuring
[[1, 2, 3], [4, 5, 6]].map { |a,| a }
嵌套方法定义
不要使用嵌套方法定义,使用 lambda 代替。嵌套方法定义实际上会在与外部方法相同的范围内(例如类)生成方法。此外,每次调用包含其定义的方法时,都会重新定义“嵌套方法”。
# bad
def foo(x)
def bar(y)
# body omitted
end
bar(x)
end
# good - the same as the previous, but no bar redefinition on every foo call
def bar(y)
# body omitted
end
def foo(x)
bar(x)
end
# also good
def foo(x)
bar = ->(y) { ... }
bar.call(x)
end
多行 Lambda 定义
对于单行代码块,使用新的 lambda 字面量语法。对于多行代码块,使用 lambda
方法。
# bad
l = lambda { |a, b| a + b }
l.call(1, 2)
# correct, but looks extremely awkward
l = ->(a, b) do
tmp = a * 7
tmp * b / 50
end
# good
l = ->(a, b) { a + b }
l.call(1, 2)
l = lambda do |a, b|
tmp = a * 7
tmp * b / 50
end
带参数的 Stabby Lambda 定义
定义带参数的 stabby lambda 时,不要省略参数括号。
# bad
l = ->x, y { something(x, y) }
# good
l = ->(x, y) { something(x, y) }
无参数的 Stabby Lambda 定义
定义无参数的 stabby lambda 时,省略参数括号。
# bad
l = ->() { something }
# good
l = -> { something }
proc
vs Proc.new
优先使用 proc
而不是 Proc.new
。
# bad
p = Proc.new { |n| puts n }
# good
p = proc { |n| puts n }
Proc 调用
对于 lambda 和 proc,优先使用 proc.call()
而不是 proc[]
或 proc.()
。
# bad - looks similar to Enumeration access
l = ->(v) { puts v }
l[1]
# bad - most compact form, but might be confusing for newcomers to Ruby
l = ->(v) { puts v }
l.(1)
# good - a bit verbose, but crystal clear
l = ->(v) { puts v }
l.call(1)
方法
简短方法
避免方法长度超过 10 行代码 (LOC)。理想情况下,大多数方法的长度应小于 5 行代码。空行不计入相关 LOC。
顶层方法
避免顶层方法定义。将它们组织到模块、类或结构体中。
注意
|
在脚本中使用顶层方法定义是可以的。 |
# bad
def some_method; end
# good
class SomeClass
def some_method; end
end
无单行方法
避免使用单行方法。虽然它们在现实中比较流行,但它们定义语法的几个特殊之处使得它们的应用不可取。无论如何,单行方法中不应该超过一个表达式。
注意
|
Ruby 3 引入了单行方法定义的替代语法,将在本指南的下一节中讨论。 |
# bad
def too_much; something; something_else; end
# okish - notice that the first ; is required
def no_braces_method; body end
# okish - notice that the second ; is optional
def no_braces_method; body; end
# okish - valid syntax, but no ; makes it kind of hard to read
def some_method() body end
# good
def some_method
body
end
规则的一个例外是空主体方法。
# good
def no_op; end
无限方法
仅对具有单行代码体的 Ruby 3.0 的无限方法定义使用。理想情况下,此类方法定义应该既简单(单个表达式)又没有副作用。
注意
|
重要的是要理解,本指南并不与上一条指南相矛盾。我们仍然建议不要使用单行方法定义,但如果要使用此类方法,则优先使用无限方法。 |
# bad
def fib(x) = if x < 2
x
else
fib(x - 1) + fib(x - 2)
end
# good
def the_answer = 42
def get_x = @x
def square(x) = x * x
# Not (so) good: has side effect
def set_x(x) = (@x = x)
def print_foo = puts("foo")
双冒号
仅使用 ::
来引用常量(包括类和模块)和构造函数(如 Array()
或 Nokogiri::HTML()
)。不要将 ::
用于常规方法调用。
# bad
SomeClass::some_method
some_object::some_method
# good
SomeClass.some_method
some_object.some_method
SomeModule::SomeClass::SOME_CONST
SomeModule::SomeClass()
冒号方法定义
不要使用 ::
来定义类方法。
# bad
class Foo
def self::some_method
end
end
# good
class Foo
def self.some_method
end
end
方法定义括号
当有参数时,使用带括号的 def
。当方法不接受任何参数时,省略括号。
# bad
def some_method()
# body omitted
end
# good
def some_method
# body omitted
end
# bad
def some_method_with_parameters param1, param2
# body omitted
end
# good
def some_method_with_parameters(param1, param2)
# body omitted
end
方法调用括号
在方法调用参数周围使用括号,尤其是在第一个参数以左括号 (
开头时,例如 f((3 + 2) + 1)
。
# bad
x = Math.sin y
# good
x = Math.sin(y)
# bad
array.delete e
# good
array.delete(e)
# bad
temperance = Person.new 'Temperance', 30
# good
temperance = Person.new('Temperance', 30)
无参数方法调用
对于没有参数的方法调用,始终省略括号。
# bad
Kernel.exit!()
2.even?()
fork()
'test'.upcase()
# good
Kernel.exit!
2.even?
fork
'test'.upcase
在 Ruby 中具有“关键字”状态的方法
对于在 Ruby 中具有“关键字”状态的方法,始终省略括号。
注意
|
不幸的是,并不完全清楚哪些方法具有“关键字”状态。人们普遍认为声明性方法具有“关键字”状态。然而,对于哪些非声明性方法(如果有)具有“关键字”状态,人们意见不一。 |
在 Ruby 中具有“关键字”状态的非声明性方法
对于具有“关键字”状态的非声明性方法(例如,各种 Kernel
实例方法),两种风格都被认为是可以接受的。最流行的风格是省略括号。理由:代码更易读,方法调用看起来更像关键字。另一种不太流行但仍然可以接受的风格是包含括号。理由:这些方法具有普通语义,那么为什么要区别对待它们呢?而且,通过不担心哪些方法具有“关键字”状态,更容易实现统一的风格。无论你选择哪一种,都要始终如一地应用它。
# good (most popular)
puts temperance.age
system 'ls'
exit 1
# also good (less popular)
puts(temperance.age)
system('ls')
exit(1)
使用带参数的 super
在使用带参数的 super
时,始终使用括号。
# bad
super name, age
# good
super(name, age)
重要
|
在不带参数的情况下调用 super 时,super 和 super() 的含义不同。请根据您的使用情况选择合适的选项。
|
参数过多
避免参数列表超过三个或四个参数。
可选参数
将可选参数定义在参数列表的末尾。在 Ruby 中,在参数列表开头定义可选参数会导致一些意想不到的结果。
# bad
def some_method(a = 1, b = 2, c, d)
puts "#{a}, #{b}, #{c}, #{d}"
end
some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'w, 2, x, y'
some_method('w', 'x', 'y', 'z') # => 'w, x, y, z'
# good
def some_method(c, d, a = 1, b = 2)
puts "#{a}, #{b}, #{c}, #{d}"
end
some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'y, 2, w, x'
some_method('w', 'x', 'y', 'z') # => 'y, z, w, x'
关键字参数顺序
将必需的关键字参数放在可选关键字参数之前。否则,如果可选关键字参数隐藏在中间,就很难发现它们。
# bad
def some_method(foo: false, bar:, baz: 10)
# body omitted
end
# good
def some_method(bar:, foo: false, baz: 10)
# body omitted
end
布尔型关键字参数
在将布尔型参数传递给方法时,使用关键字参数。
# bad
def some_method(bar = false)
puts bar
end
# bad - common hack before keyword args were introduced
def some_method(options = {})
bar = options.fetch(:bar, false)
puts bar
end
# good
def some_method(bar: false)
puts bar
end
some_method # => false
some_method(bar: true) # => true
关键字参数与可选参数
优先使用关键字参数而不是可选参数。
# bad
def some_method(a, b = 5, c = 1)
# body omitted
end
# good
def some_method(a, b: 5, c: 1)
# body omitted
end
关键字参数与选项哈希
使用关键字参数代替选项哈希。
# bad
def some_method(options = {})
bar = options.fetch(:bar, false)
puts bar
end
# good
def some_method(bar: false)
puts bar
end
参数转发
使用 Ruby 2.7 的参数转发功能。
# bad
def some_method(*args, &block)
other_method(*args, &block)
end
# bad
def some_method(*args, **kwargs, &block)
other_method(*args, **kwargs, &block)
end
# bad
# Please note that it can cause unexpected incompatible behavior
# because `...` forwards block also.
# https://github.com/rubocop/rubocop/issues/7549
def some_method(*args)
other_method(*args)
end
# good
def some_method(...)
other_method(...)
end
块转发
使用 Ruby 3.1 的匿名块转发功能。
在大多数情况下,块参数的名称类似于 &block
或 &proc
。它们的名称没有信息,而 &
对于语法意义来说已经足够了。
# bad
def some_method(&block)
other_method(&block)
end
# good
def some_method(&)
other_method(&)
end
私有全局方法
如果您确实需要“全局”方法,请将它们添加到 Kernel 中并将其设为私有。
类与模块
一致的类
在类定义中使用一致的结构。
class Person
# extend/include/prepend go first
extend SomeModule
include AnotherModule
prepend YetAnotherModule
# inner classes
class CustomError < StandardError
end
# constants are next
SOME_CONSTANT = 20
# afterwards we have attribute macros
attr_reader :name
# followed by other macros (if any)
validates :name
# public class methods are next in line
def self.some_method
end
# initialization goes between class methods and other instance methods
def initialize
end
# followed by other public instance methods
def some_method
end
# protected and private methods are grouped near the end
protected
def some_protected_method
end
private
def some_private_method
end
end
Mixin 分组
将多个 Mixin 分成单独的语句。
# bad
class Person
include Foo, Bar
end
# good
class Person
# multiple mixins go in separate statements
include Foo
include Bar
end
单行类
对于没有主体类的类定义,建议使用两行格式。这样更容易阅读、理解和修改。
# bad
FooError = Class.new(StandardError)
# okish
class FooError < StandardError; end
# ok
class FooError < StandardError
end
注意
|
许多编辑器/工具无法正确理解 Class.new 的用法。尝试查找类定义的人可能会尝试使用 grep "class FooError"。最后一点区别是,使用 Class.new 形式时,类的名称在基类的 inherited 回调中不可用。总的来说,最好坚持使用基本的双行风格。
|
文件类
不要将多行类嵌套在类中。尝试将这些嵌套类分别放在各自的文件中,并将文件命名为与包含类相同的名称。
# bad
# foo.rb
class Foo
class Bar
# 30 methods inside
end
class Car
# 20 methods inside
end
# 30 methods inside
end
# good
# foo.rb
class Foo
# 30 methods inside
end
# foo/bar.rb
class Foo
class Bar
# 30 methods inside
end
end
# foo/car.rb
class Foo
class Car
# 20 methods inside
end
end
命名空间定义
使用显式嵌套来定义(和重新打开)命名空间类和模块。使用作用域解析运算符可能会导致意外的常量查找,因为 Ruby 的 词法作用域 依赖于定义时的模块嵌套。
module Utilities
class Queue
end
end
# bad
class Utilities::Store
Module.nesting # => [Utilities::Store]
def initialize
# Refers to the top level ::Queue class because Utilities isn't in the
# current nesting chain.
@queue = Queue.new
end
end
# good
module Utilities
class WaitingList
Module.nesting # => [Utilities::WaitingList, Utilities]
def initialize
@queue = Queue.new # Refers to Utilities::Queue
end
end
end
模块与类
对于只有类方法的类,建议使用模块。只有在需要创建实例时才使用类。
# bad
class SomeClass
def self.some_method
# body omitted
end
def self.some_other_method
# body omitted
end
end
# good
module SomeModule
module_function
def some_method
# body omitted
end
def some_other_method
# body omitted
end
end
module_function
当您想要将模块的实例方法转换为类方法时,建议使用 module_function
而不是 extend self
。
# bad
module Utilities
extend self
def parse_something(string)
# do stuff here
end
def other_utility_method(number, string)
# do some more stuff
end
end
# good
module Utilities
module_function
def parse_something(string)
# do stuff here
end
def other_utility_method(number, string)
# do some more stuff
end
end
Liskov
在设计类层次结构时,请确保它们符合 Liskov 替换原则。
定义 to_s
始终为表示域对象的类提供适当的 to_s
方法。
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def to_s
"#{first_name} #{last_name}"
end
end
attr
家族
使用 attr
函数族来定义简单的访问器或修改器。
# bad
class Person
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def first_name
@first_name
end
def last_name
@last_name
end
end
# good
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
访问器/修改器方法命名
对于访问器和修改器,避免在方法名前缀使用 get_
和 set_
。在 Ruby 中,使用属性名作为访问器(读取器)的命名约定,而 attr_name=
则用于修改器(写入器)。
# bad
class Person
def get_name
"#{@first_name} #{@last_name}"
end
def set_name(name)
@first_name, @last_name = name.split(' ')
end
end
# good
class Person
def name
"#{@first_name} #{@last_name}"
end
def name=(name)
@first_name, @last_name = name.split(' ')
end
end
attr
避免使用 attr
。使用 attr_reader
和 attr_accessor
代替。
# bad - creates a single attribute accessor (deprecated in Ruby 1.9)
attr :something, true
attr :one, :two, :three # behaves as attr_reader
# good
attr_accessor :something
attr_reader :one, :two, :three
Struct.new
考虑使用 Struct.new
,它可以为您定义简单的访问器、构造函数和比较运算符。
# good
class Person
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
# better
Person = Struct.new(:first_name, :last_name) do
end
不要扩展 Struct.new
不要扩展由 Struct.new
初始化的实例。扩展它会引入一个多余的类级别,并且如果文件被多次加载,也可能会导致奇怪的错误。
# bad
class Person < Struct.new(:first_name, :last_name)
end
# good
Person = Struct.new(:first_name, :last_name)
不要扩展 Data.define
不要扩展由 Data.define
初始化的实例。扩展它会引入一个多余的类级别。
# bad
class Person < Data.define(:first_name, :last_name)
end
Person.ancestors
# => [Person, #<Class:0x0000000105abed88>, Data, Object, (...)]
# good
Person = Data.define(:first_name, :last_name)
Person.ancestors
# => [Person, Data, Object, (...)]
鸭子类型
优先使用 鸭子类型 而不是继承。
# bad
class Animal
# abstract method
def speak
end
end
# extend superclass
class Duck < Animal
def speak
puts 'Quack! Quack'
end
end
# extend superclass
class Dog < Animal
def speak
puts 'Bau! Bau!'
end
end
# good
class Duck
def speak
puts 'Quack! Quack'
end
end
class Dog
def speak
puts 'Bau! Bau!'
end
end
不要使用类变量
避免使用类 (@@
) 变量,因为它们在继承中的行为“很糟糕”。
class Parent
@@class_var = 'parent'
def self.print_class_var
puts @@class_var
end
end
class Child < Parent
@@class_var = 'child'
end
Parent.print_class_var # => will print 'child'
正如您所见,类层次结构中的所有类实际上共享一个类变量。通常应该优先使用类实例变量而不是类变量。
利用访问修饰符(例如 private
和 protected
)
根据方法的预期用途,为方法分配适当的可见性级别 (private
、protected
)。不要将所有内容都设置为 public
(默认值)。
访问修饰符缩进
将 public
、protected
和 private
方法缩进与它们所应用的方法定义相同的程度。在可见性修饰符上方留一个空行,在下方留一个空行,以强调它适用于其下方的所有方法。
# good
class SomeClass
def public_method
# some code
end
private
def private_method
# some code
end
def another_private_method
# some code
end
end
定义类方法
使用 def self.method
定义类方法。这使得代码更容易重构,因为类名不会重复。
class TestClass
# bad
def TestClass.some_method
# body omitted
end
# good
def self.some_other_method
# body omitted
end
# Also possible and convenient when you
# have to define many class methods.
class << self
def first_method
# body omitted
end
def second_method_etc
# body omitted
end
end
end
词法别名方法
在词法类范围内为方法创建别名时,优先使用 alias
,因为 self
在此上下文中的解析也是词法的,它清楚地向用户传达了别名间接关系在运行时或任何子类中不会被更改,除非明确说明。
class Westerner
def first_name
@names.first
end
alias given_name first_name
end
由于 alias
与 def
一样是关键字,因此优先使用裸字参数而不是符号或字符串。换句话说,使用 alias foo bar
,而不是 alias :foo :bar
。
还要注意 Ruby 如何处理别名和继承:别名引用在定义别名时解析的方法;它不是动态分派的。
class Fugitive < Westerner
def first_name
'Nobody'
end
end
在这个例子中,Fugitive#given_name
仍然会调用原始的 Westerner#first_name
方法,而不是 Fugitive#first_name
。要覆盖 Fugitive#given_name
的行为,您需要在派生类中重新定义它。
class Fugitive < Westerner
def first_name
'Nobody'
end
alias given_name first_name
end
alias_method
在运行时为模块、类或单例类的方法创建别名时,始终使用 alias_method
,因为 alias
的词法范围会导致这些情况下的不可预测性。
module Mononymous
def self.included(other)
other.class_eval { alias_method :full_name, :given_name }
end
end
class Sting < Westerner
include Mononymous
end
类和 self
当类(或模块)方法调用其他此类方法时,在调用其他此类方法时,省略使用前导 self
或自己的名称后跟一个 .
。这在“服务类”或其他类似概念中经常看到,其中类被视为函数。这种约定往往会减少此类类中的重复样板代码。
class TestClass
# bad - more work when class renamed/method moved
def self.call(param1, param2)
TestClass.new(param1).call(param2)
end
# bad - more verbose than necessary
def self.call(param1, param2)
self.new(param1).call(param2)
end
# good
def self.call(param1, param2)
new(param1).call(param2)
end
# ...other methods...
end
在块中定义常量
不要在块中定义常量,因为块的范围不会以任何方式隔离或命名空间常量。
相反,在块之外定义常量,或者如果在外部范围内定义常量有问题,则使用变量或方法。
# bad - FILES_TO_LINT is now defined globally
task :lint do
FILES_TO_LINT = Dir['lib/*.rb']
# ...
end
# good - files_to_lint is only defined inside the block
task :lint do
files_to_lint = Dir['lib/*.rb']
# ...
end
类:构造函数
构造函数中的析取赋值
在构造函数中,避免对实例变量进行不必要的析取赋值 (||=
)。优先使用普通赋值。在 Ruby 中,实例变量(以 @
开头)在赋值之前为 nil,因此在大多数情况下,析取是不必要的。
# bad
def initialize
@x ||= 1
end
# good
def initialize
@x = 1
end
注释
好的代码本身就是最好的文档。在你准备添加注释时,问问自己:“如何改进代码,使这个注释不再需要?”。改进代码,然后对其进行文档化,使其更加清晰。
无注释
编写自文档化代码,忽略本节的其余内容。说真的!
原理注释
如果如何可以自文档化,但为什么不行(例如,代码绕过了不明显的库行为,或实现了来自学术论文的算法),请添加一个注释来解释代码背后的原理。
# bad
x = BuggyClass.something.dup
def compute_dependency_graph
...30 lines of recursive graph merging...
end
# good
# BuggyClass returns an internal object, so we have to dup it to modify it.
x = BuggyClass.something.dup
# This is algorithm 6.4(a) from Worf & Yar's _Amazing Graph Algorithms_ (2243).
def compute_dependency_graph
...30 lines of recursive graph merging...
end
英文注释
用英文编写注释。
哈希空间
在注释的开头 #
字符和注释文本之间使用一个空格。
注释维护
保持现有注释的最新状态。过时的注释比没有注释更糟糕。
注释注解
注解位置
注解通常应该写在与相关代码紧邻的上一行。
# bad
def bar
baz(:quux) # FIXME: This has crashed occasionally since v3.2.1.
end
# good
def bar
# FIXME: This has crashed occasionally since v3.2.1.
baz(:quux)
end
注解关键字格式
注解关键字后面跟着一个冒号和一个空格,然后是一个描述问题的注释。
# bad
def bar
# FIXME This has crashed occasionally since v3.2.1.
baz(:quux)
end
# good
def bar
# FIXME: This has crashed occasionally since v3.2.1.
baz(:quux)
end
多行注解缩进
如果需要多行来描述问题,后续行应该在 #
后缩进三个空格(一个通用缩进加两个用于缩进目的)。
def bar
# FIXME: This has crashed occasionally since v3.2.1. It may
# be related to the BarBazUtil upgrade.
baz(:quux)
end
TODO
使用 TODO
来标记将来应该添加的缺失功能或功能。
FIXME
使用 FIXME
来标记需要修复的损坏代码。
OPTIMIZE
使用 OPTIMIZE
来标记可能导致性能问题的缓慢或低效代码。
HACK
使用 HACK
来标记代码异味,其中使用了有问题的编码实践,应该重构掉。
REVIEW
使用 REVIEW
来标记任何需要检查以确认其按预期工作的内容。例如:REVIEW: 我们确定这是客户当前执行 X 的方式吗?
文档注释
如果觉得合适,可以使用其他自定义注释关键字,但请务必在项目的 README
或类似文件中记录它们。
魔法注释
魔法注释优先
将魔法注释放在文件中的所有代码和文档之上(除了 shebang,将在下面讨论)。
# bad
# Some documentation about Person
# frozen_string_literal: true
class Person
end
# good
# frozen_string_literal: true
# Some documentation about Person
class Person
end
在 shebang 下面
当文件中存在 shebang 时,将魔法注释放在 shebang 下面。
# bad
# frozen_string_literal: true
#!/usr/bin/env ruby
App.parse(ARGV)
# good
#!/usr/bin/env ruby
# frozen_string_literal: true
App.parse(ARGV)
每行一个魔法注释
如果需要多个魔法注释,请每行使用一个。
# bad
# -*- frozen_string_literal: true; encoding: ascii-8bit -*-
# good
# frozen_string_literal: true
# encoding: ascii-8bit
将魔法注释与代码分开
用空行将魔法注释与代码和文档分开。
# bad
# frozen_string_literal: true
# Some documentation for Person
class Person
# Some code
end
# good
# frozen_string_literal: true
# Some documentation for Person
class Person
# Some code
end
集合
字面数组和哈希
优先使用字面数组和哈希创建表示法(除非你需要向它们的构造函数传递参数)。
# bad
arr = Array.new
hash = Hash.new
# good
arr = []
arr = Array.new(10)
hash = {}
hash = Hash.new(0)
%w
当你需要一个单词数组(没有空格和特殊字符的非空字符串)时,优先使用 %w
而不是字面数组语法。仅对包含两个或更多元素的数组应用此规则。
# bad
STATES = ['draft', 'open', 'closed']
# good
STATES = %w[draft open closed]
%i
当你需要一个符号数组(并且不需要维护 Ruby 1.9 兼容性)时,优先使用 %i
而不是字面数组语法。仅对包含两个或更多元素的数组应用此规则。
# bad
STATES = [:draft, :open, :closed]
# good
STATES = %i[draft open closed]
没有尾随数组逗号
避免在 Array
或 Hash
字面量的最后一个项目之后使用逗号,尤其是在项目不在单独的行上时。
# bad - easier to move/add/remove items, but still not preferred
VALUES = [
1001,
2020,
3333,
]
# bad
VALUES = [1001, 2020, 3333, ]
# good
VALUES = [1001, 2020, 3333]
first
和 last
当从数组中访问第一个或最后一个元素时,优先使用 first
或 last
而不是 [0]
或 [-1]
。first
和 last
更容易理解,特别是对于经验不足的 Ruby 程序员或来自具有不同索引语义的语言的人来说。
arr = [1, 2, 3]
# ok
arr[0] # => 1
arr[-1] # => 3
# (arguably) better
arr.first # => 1
arr.last # => 3
# good - assignments can only be done via []=
arr[0] = 2
arr[-1] = 5
Set 与 Array
在处理唯一元素时,使用 Set
而不是 Array
。Set
实现了一个无序值的集合,没有重复项。这结合了 Array
的直观交互操作功能和 Hash
的快速查找功能。
符号作为键
优先使用符号而不是字符串作为哈希键。
# bad
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
# good
hash = { one: 1, two: 2, three: 3 }
不可变键
避免使用可变对象作为哈希键。
哈希字面量
当你的哈希键是符号时,使用 Ruby 1.9 哈希字面量语法。
# bad
hash = { :one => 1, :two => 2, :three => 3 }
# good
hash = { one: 1, two: 2, three: 3 }
哈希字面量值
当你的哈希键和值相同时,使用 Ruby 3.1 哈希字面量值语法。
# bad
hash = { one: one, two: two, three: three }
# good
hash = { one:, two:, three: }
哈希字面量作为数组的最后一个元素
如果哈希字面量是数组的最后一个元素,请将其用大括号括起来。
# bad
[1, 2, one: 1, two: 2]
# good
[1, 2, { one: 1, two: 2 }]
不要混合哈希语法
不要在同一个哈希字面量中混合使用 Ruby 1.9 哈希语法和哈希火箭。当你的键不是符号时,请坚持使用哈希火箭语法。
# bad
{ a: 1, 'b' => 2 }
# good
{ :a => 1, 'b' => 2 }
避免使用 Hash[] 构造函数
Hash::[]
是 Ruby 2.1 之前用于从键值对数组或扁平键值列表中构造哈希的一种方法。它具有模糊的语义,并且在代码中看起来很神秘。从 Ruby 2.1 开始,可以使用 Enumerable#to_h
从键值对列表中构造哈希,并且应该优先使用它。对于使用字面量键值对的 Hash[]
,应该优先使用哈希字面量。
# bad
Hash[ary]
Hash[a, b, c, d]
# good
ary.to_h
{a => b, c => d}
Hash#key?
使用 Hash#key?
代替 Hash#has_key?
,使用 Hash#value?
代替 Hash#has_value?
。
# bad
hash.has_key?(:test)
hash.has_value?(value)
# good
hash.key?(:test)
hash.value?(value)
Hash#each
使用 Hash#each_key
代替 Hash#keys.each
,使用 Hash#each_value
代替 Hash#values.each
。
# bad
hash.keys.each { |k| p k }
hash.values.each { |v| p v }
hash.each { |k, _v| p k }
hash.each { |_k, v| p v }
# good
hash.each_key { |k| p k }
hash.each_value { |v| p v }
Hash#fetch
在处理应该存在的哈希键时,使用 Hash#fetch
。
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
# bad - if we make a mistake we might not spot it right away
heroes[:batman] # => 'Bruce Wayne'
heroes[:supermann] # => nil
# good - fetch raises a KeyError making the problem obvious
heroes.fetch(:supermann)
Hash#fetch
默认值
通过 Hash#fetch
为哈希键引入默认值,而不是使用自定义逻辑。
batman = { name: 'Bruce Wayne', is_evil: false }
# bad - if we just use || operator with falsey value we won't get the expected result
batman[:is_evil] || true # => true
# good - fetch works correctly with falsey values
batman.fetch(:is_evil, true) # => false
使用哈希块
如果需要评估的代码可能具有副作用或成本高昂,请优先使用块而不是 Hash#fetch
中的默认值。
batman = { name: 'Bruce Wayne' }
# bad - if we use the default value, we eager evaluate it
# so it can slow the program down if done multiple times
batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers is an expensive call
# good - blocks are lazy evaluated, so only triggered in case of KeyError exception
batman.fetch(:powers) { obtain_batman_powers }
Hash#values_at
和 Hash#fetch_values
当需要连续从哈希中检索多个值时,使用 Hash#values_at
或 Hash#fetch_values
。
# bad
email = data['email']
username = data['nickname']
# bad
keys = %w[email nickname].freeze
email, username = keys.map { |key| data[key] }
# good
email, username = data.values_at('email', 'nickname')
# also good
email, username = data.fetch_values('email', 'nickname')
Hash#transform_keys
和 Hash#transform_values
在转换哈希的键或值时,优先使用 `transform_keys` 或 `transform_values`,而不是 `each_with_object` 或 `map`。
# bad
{a: 1, b: 2}.each_with_object({}) { |(k, v), h| h[k] = v * v }
{a: 1, b: 2}.map { |k, v| [k.to_s, v] }.to_h
# good
{a: 1, b: 2}.transform_values { |v| v * v }
{a: 1, b: 2}.transform_keys { |k| k.to_s }
有序哈希
依赖于 Ruby 1.9 及更高版本中哈希是有序的事实。
不要修改集合
遍历集合时不要修改它。
直接访问元素
访问集合元素时,如果提供了替代形式的读取器方法,请避免通过 `[n]` 进行直接访问。这可以防止你在 `nil` 上调用 `[]`。
# bad
Regexp.last_match[1]
# good
Regexp.last_match(1)
提供集合的替代访问器
提供集合的访问器时,请提供替代形式,以防止用户在访问集合中的元素之前检查 `nil`。
# bad
def awesome_things
@awesome_things
end
# good
def awesome_things(index = nil)
if index && @awesome_things
@awesome_things[index]
else
@awesome_things
end
end
map
/find
/select
/reduce
/include?
/size
优先使用 `map` 而不是 `collect`,`find` 而不是 `detect`,`select` 而不是 `find_all`,`reduce` 而不是 `inject`,`include?` 而不是 `member?`,以及 `size` 而不是 `length`。这不是硬性要求;如果使用别名可以提高可读性,则可以使用它。这些押韵的方法继承自 Smalltalk,在其他编程语言中并不常见。鼓励使用 `select` 而不是 `find_all` 的原因是,它与 `reject` 配合得很好,并且它的名称非常直观。
count
与 size
不要使用 `count` 来代替 `size`。对于除 `Array` 之外的 `Enumerable` 对象,它将遍历整个集合以确定其大小。
# bad
some_hash.count
# good
some_hash.size
flat_map
使用 `flat_map` 而不是 `map` + `flatten`。这并不适用于深度大于 2 的数组,例如,如果 `users.first.songs == ['a', ['b','c']]`,则使用 `map + flatten` 而不是 `flat_map`。`flat_map` 将数组扁平化 1 层,而 `flatten` 将其完全扁平化。
# bad
all_songs = users.map(&:songs).flatten.uniq
# good
all_songs = users.flat_map(&:songs).uniq
reverse_each
优先使用 reverse_each
而不是 reverse.each
,因为一些包含 Enumerable
模块的类会提供更高效的实现。即使在最坏的情况下,某个类没有提供专门的实现,从 Enumerable
继承的通用实现至少与使用 reverse.each
一样高效。
# bad
array.reverse.each { ... }
# good
array.reverse_each { ... }
Object#yield_self
vs Object#then
方法 Object#then
比 Object#yield_self
更受欢迎,因为名称 then
表明了意图,而不是行为。这使得生成的代码更容易阅读。
# bad
obj.yield_self { |x| x.do_something }
# good
obj.then { |x| x.do_something }
注意
|
您可以在 这里 阅读有关此指南背后的原因的更多信息。 |
使用范围切片
使用范围切片数组以提取一些元素(例如 ary[2..5]
)是一种流行的技术。下面您将找到一些在使用它时需要注意的小事项。
-
[0..-1]
在ary[0..-1]
中是多余的,并且只是ary
的同义词。
# bad - you're selecting all the elements of the array
ary[0..-1]
ary[0..nil]
ary[0...nil]
# good
ary
-
Ruby 2.6 引入了无限范围,这提供了一种更简单的方法来描述一个一直到数组末尾的切片。
# bad - hard to process mentally
ary[1..-1]
ary[1..nil]
# good - easier to read and more concise
ary[1..]
-
Ruby 2.7 引入了无头范围,这在切片中也很方便。但是,与
ary[1..-1]
中有些模糊的-1
不同,ary[0..42]
中的0
作为起点很清楚。事实上,将其更改为ary[..42]
可能会降低可读性。因此,使用类似ary[0..42]
的代码是可以的。另一方面,ary[nil..42]
应该替换为ary[..42]
或arr[0..42]
。
# bad - hard to process mentally
ary[nil..42]
# good - easier to read
ary[..42]
ary[0..42]
数字
数字中的下划线
在大型数字字面量中添加下划线以提高可读性。
# bad - how many 0s are there?
num = 1000000
# good - much easier to parse for the human brain
num = 1_000_000
数字字面量前缀
对于数字字面量前缀,优先使用小写字母。0o
用于八进制,0x
用于十六进制,0b
用于二进制。不要使用 0d
前缀表示十进制字面量。
# bad
num = 01234
num = 0O1234
num = 0X12AB
num = 0B10101
num = 0D1234
num = 0d1234
# good - easier to separate digits from the prefix
num = 0o1234
num = 0x12AB
num = 0b10101
num = 1234
整数类型检查
使用 Integer
检查整数的类型。由于 Fixnum
是平台相关的,在 32 位和 64 位机器上检查它将返回不同的结果。
timestamp = Time.now.to_i
# bad
timestamp.is_a?(Fixnum)
timestamp.is_a?(Bignum)
# good
timestamp.is_a?(Integer)
浮点数除法
当对两个整数执行浮点数除法时,可以使用 fdiv
或将其中一个整数转换为浮点数。
# bad
a.to_f / b.to_f
# good
a.to_f / b
a / b.to_f
a.fdiv(b)
浮点数比较
避免对浮点数进行(不)相等比较,因为它们不可靠。
浮点数本身就存在精度问题,而对它们进行精确相等比较几乎永远不是想要的语义。通过 ==/!=
运算符进行比较会检查浮点数的表示形式是否完全相同,如果您执行了任何涉及精度损失的算术运算,这将非常不可能。
# bad
x == 0.1
x != 0.1
# good - using BigDecimal
x.to_d == 0.1.to_d
# good - not an actual float comparison
x == Float::INFINITY
# good
(x - 0.1).abs < Float::EPSILON
# good
tolerance = 0.0001
(x - 0.1).abs < tolerance
# Or some other epsilon based type of comparison:
# https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
指数表示法
使用指数表示法表示数字时,最好使用规范的科学计数法,即使用介于 1(包含)和 10(不包含)之间的尾数。如果指数为零,则完全省略指数。
目标是避免十的幂和指数表示法之间的混淆,因为快速阅读 10e7
的人可能会认为它是 10 的 7 次方(一个然后是 7 个零),而实际上它是 10 的 8 次方(一个然后是 8 个零)。如果您想要 10 的 7 次方,您应该使用 1e7
。
幂表示法 | 指数表示法 | 输出 |
---|---|---|
10 ** 7 |
1e7 |
10000000 |
10 ** 6 |
1e6 |
1000000 |
10 ** 7 |
10e6 |
10000000 |
人们可能会倾向于使用另一种工程表示法,其中指数必须始终是 3 的倍数,以便于转换为千 / 百万 / … 系统。
# bad
10e6
0.3e4
11.7e5
3.14e0
# good
1e7
3e3
1.17e6
3.14
另一种:工程表示法
# bad
3.2e7
0.1e5
12e4
# good
1e6
17e6
0.98e9
字符串
字符串插值
优先使用字符串插值和字符串格式化而不是字符串连接
# bad
email_with_name = user.name + ' <' + user.email + '>'
# good
email_with_name = "#{user.name} <#{user.email}>"
# good
email_with_name = format('%s <%s>', user.name, user.email)
一致的字符串字面量
采用一致的字符串字面量引用风格。在 Ruby 社区中,有两种流行的风格,这两种风格都被认为是好的 - 默认情况下使用单引号和默认情况下使用双引号。
注意
|
本指南中的字符串字面量默认使用单引号。 |
单引号
如果您不需要字符串插值或特殊符号,例如 \t
、\n
、'
等,请优先使用单引号字符串。
# bad
name = "Bozhidar"
name = 'De\'Andre'
# good
name = 'Bozhidar'
name = "De'Andre"
双引号
除非您的字符串字面量包含 " 或您想要抑制的转义字符,否则优先使用双引号。
# bad
name = 'Bozhidar'
sarcasm = "I \"like\" it."
# good
name = "Bozhidar"
sarcasm = 'I "like" it.'
没有字符字面量
不要使用字符字面量语法 ?x
。自 Ruby 1.9 以来,它基本上是多余的 - ?x
将被解释为 'x'
(包含单个字符的字符串)。
# bad
char = ?c
# good
char = 'c'
花括号插值
在将实例变量和全局变量插值到字符串中时,不要省略{}
。
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
# bad - valid, but awkward
def to_s
"#@first_name #@last_name"
end
# good
def to_s
"#{@first_name} #{@last_name}"
end
end
$global = 0
# bad
puts "$global = #$global"
# good
puts "$global = #{$global}"
不要使用to_s
不要对插值的物体使用Object#to_s
。它会自动调用。
# bad
message = "This is the #{result.to_s}."
# good
message = "This is the #{result}."
字符串连接
当需要构建大型数据块时,避免使用String#`。相反,使用`String#<<`。连接会就地修改字符串实例,并且始终比
String#
更快,后者会创建一堆新的字符串对象。
# bad
html = ''
html += '<h1>Page title</h1>'
paragraphs.each do |paragraph|
html += "<p>#{paragraph}</p>"
end
# good and also fast
html = ''
html << '<h1>Page title</h1>'
paragraphs.each do |paragraph|
html << "<p>#{paragraph}</p>"
end
不要滥用gsub
在可以使用更快、更专业的替代方案的情况下,不要使用String#gsub
。
url = 'http://example.com'
str = 'lisp-case-rules'
# bad
url.gsub('http://', 'https://')
str.gsub('-', '_')
# good
url.sub('http://', 'https://')
str.tr('-', '_')
String#chars
优先使用String#chars
而不是使用空字符串或正则表达式字面量参数的String#split
。
注意
|
从 Ruby 2.0 开始,这些情况的行为相同。 |
# bad
string.split(//)
string.split('')
# good
string.chars
sprintf
优先使用sprintf
及其别名format
,而不是相当神秘的String#%
方法。
# bad
'%d %d' % [20, 10]
# => '20 10'
# good
sprintf('%d %d', 20, 10)
# => '20 10'
# good
sprintf('%<first>d %<second>d', first: 20, second: 10)
# => '20 10'
format('%d %d', 20, 10)
# => '20 10'
# good
format('%<first>d %<second>d', first: 20, second: 10)
# => '20 10'
命名格式标记
当使用命名格式字符串标记时,优先使用%<name>s
而不是%{name}
,因为它编码了有关值类型的信息。
# bad
format('Hello, %{name}', name: 'John')
# good
format('Hello, %<name>s', name: 'John')
长字符串
将长字符串分成多行,但不要使用+
连接它们。如果要添加换行符,请使用 heredoc。否则使用\
# bad
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. " +
"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, " +
"when an unknown printer took a galley of type and scrambled it to make a type specimen book."
# good
<<~LOREM
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type specimen book.
LOREM
# good
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. "\
"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, "\
"when an unknown printer took a galley of type and scrambled it to make a type specimen book."
Heredocs
波浪形 Heredoc
对于格式良好的多行字符串,使用 Ruby 2.3 的波浪形 heredocs。
# bad - using Powerpack String#strip_margin
code = <<-RUBY.strip_margin('|')
|def test
| some_method
| other_method
|end
RUBY
# also bad
code = <<-RUBY
def test
some_method
other_method
end
RUBY
# good
code = <<~RUBY
def test
some_method
other_method
end
RUBY
Heredoc 分隔符
为 heredocs 使用描述性的分隔符。分隔符为 heredoc 内容添加了有价值的信息,作为额外的好处,如果使用正确的分隔符,一些编辑器可以突出显示 heredoc 中的代码。
# bad
code = <<~END
def foo
bar
end
END
# good
code = <<~RUBY
def foo
bar
end
RUBY
# good
code = <<~SUMMARY
An imposing black structure provides a connection between the past and
the future in this enigmatic adaptation of a short story by revered
sci-fi author Arthur C. Clarke.
SUMMARY
Heredoc 方法调用
将带有 heredoc 接收者的方法调用放在 heredoc 定义的第一行。如果添加或删除新行,不良形式具有很大的错误可能性。
# bad
query = <<~SQL
select foo from bar
SQL
.strip_indent
# good
query = <<~SQL.strip_indent
select foo from bar
SQL
Heredoc 参数闭合括号
将带有 heredoc 参数的函数调用中的闭合括号放在 heredoc 定义的第一行。如果删除闭合括号之前的换行符,则不良格式可能会导致错误。
# bad
foo(<<~SQL
select foo from bar
SQL
)
# good
foo(<<~SQL)
select foo from bar
SQL
日期和时间
Time.now
在获取当前系统时间时,优先使用 Time.now
而不是 Time.new
。
不使用 DateTime
除非您需要考虑历史上的日历改革,否则不要使用 DateTime
- 如果需要,请明确指定 start
参数以清楚地说明您的意图。
# bad - uses DateTime for current time
DateTime.now
# good - uses Time for current time
Time.now
# bad - uses DateTime for modern date
DateTime.iso8601('2016-06-29')
# good - uses Date for modern date
Date.iso8601('2016-06-29')
# good - uses DateTime with start argument for historical date
DateTime.iso8601('1751-04-23', Date::ENGLAND)
正则表达式
有些人遇到问题时会想:“我知道了,我会使用正则表达式。” 现在他们有两个问题了。
纯文本搜索
如果您只需要在字符串中进行纯文本搜索,请不要使用正则表达式。
foo = 'I am an example string'
# bad - using a regular expression is an overkill here
foo =~ /example/
# good
foo['example']
使用正则表达式作为字符串索引
对于简单的构造,您可以直接通过字符串索引使用正则表达式。
match = string[/regexp/] # get content of matched regexp
first_group = string[/text(grp)/, 1] # get content of captured group
string[/text (grp)/, 1] = 'replace' # string => 'text replace'
不要混合命名捕获和编号捕获
不要在正则表达式字面量中混合命名捕获和编号捕获。因为如果混合使用,编号捕获将被忽略。
# bad - There is no way to access `(BAR)` capturing.
m = /(?<foo>FOO)(BAR)/.match('FOOBAR')
p m[:foo] # => "FOO"
p m[1] # => "FOO"
p m[2] # => nil - not "BAR"
# good - Both captures are accessible with names.
m = /(?<foo>FOO)(?<bar>BAR)/.match('FOOBAR')
p m[:foo] # => "FOO"
p m[:bar] # => "BAR"
# good - `(?:BAR)` is non-capturing grouping.
m = /(?<foo>FOO)(?:BAR)/.match('FOOBAR')
p m[:foo] # => "FOO"
# good - Both captures are accessible with numbers.
m = /(FOO)(BAR)/.match('FOOBAR')
p m[1] # => "FOO"
p m[2] # => "BAR"
按名称引用命名正则表达式捕获
优先使用名称来引用命名正则表达式捕获,而不是使用数字。
# bad
m = /(?<foo>FOO)(?<bar>BAR)/.match('FOOBAR')
p m[1] # => "FOO"
p m[2] # => "BAR"
# good
m = /(?<foo>FOO)(?<bar>BAR)/.match('FOOBAR')
p m[:foo] # => "FOO"
p m[:bar] # => "BAR"
避免使用 Perl 风格的最后一个正则表达式组匹配器
不要使用表示最后一个正则表达式组匹配的 cryptic Perl 遗留变量($1
、$2
等)。请改用 Regexp.last_match(n)
。
/(regexp)/ =~ string
...
# bad
process $1
# good
process Regexp.last_match(1)
避免使用编号组
避免使用编号组,因为很难跟踪它们包含的内容。可以使用命名组代替。
# bad
/(regexp)/ =~ string
# some code
process Regexp.last_match(1)
# good
/(?<meaningful_var>regexp)/ =~ string
# some code
process meaningful_var
限制转义
字符类只有几个特殊字符需要关注:^
、-
、\
、]
,所以不要转义.
或[]
中的方括号。
脱字符和美元符号正则表达式
小心使用^
和$
,因为它们匹配的是行首/行尾,而不是字符串结尾。如果你想匹配整个字符串,请使用:\A
和\z
(不要与\Z
混淆,它等效于/\n?\z/
)。
string = "some injection\nusername"
string[/^username$/] # matches
string[/\Ausername\z/] # doesn't match
多行正则表达式
对于多行正则表达式,使用x
(自由间距)修饰符。
注意
|
这被称为自由间距模式。在这种模式下,前导和尾随空格将被忽略。 |
# bad
regex = /start\
\s\
(group)\
(?:alt1|alt2)\
end/
# good
regexp = /
start
\s
(group)
(?:alt1|alt2)
end
/x
注释复杂正则表达式
对于复杂的正则表达式,使用x
修饰符。这使得它们更易读,你可以添加一些有用的注释。
regexp = /
start # some text
\s # white space char
(group) # first group
(?:alt1|alt2) # some alternation
end
/x
使用带块或哈希的gsub
进行复杂替换
对于复杂的替换,sub
/gsub
可以与块或哈希一起使用。
words = 'foo bar'
words.sub(/f/, 'f' => 'F') # => 'Foo bar'
words.gsub(/\w+/) { |word| word.capitalize } # => 'Foo Bar'
百分号字面量
%q
简写
对于需要插值和嵌入双引号的单行字符串,使用%()
(它是%Q
的简写)。对于多行字符串,建议使用heredoc。
# bad (no interpolation needed)
%(<div class="text">Some text</div>)
# should be '<div class="text">Some text</div>'
# bad (no double-quotes)
%(This is #{quality} style)
# should be "This is #{quality} style"
# bad (multiple lines)
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# should be a heredoc.
# good (requires interpolation, has quotes, single line)
%(<tr><td class="name">#{name}</td>)
%q
除非你的字符串中同时包含'
和"
,否则避免使用%()
或等效的%q()
。常规字符串字面量更易读,除非需要转义大量字符,否则应该优先使用它们。
# bad
name = %q(Bruce Wayne)
time = %q(8 o'clock)
question = %q("What did you say?")
# good
name = 'Bruce Wayne'
time = "8 o'clock"
question = '"What did you say?"'
quote = %q(<p class='quote'>"What did you say?"</p>)
%x
除非你要使用反引号执行命令(这种情况不太可能),否则避免使用%x
。
# bad
date = %x(date)
# good
date = `date`
echo = %x(echo `date`)
%s
避免使用%s
。社区似乎已经决定:"some string"
是创建带空格符号的首选方式。
百分比字面量大括号
使用最适合各种百分比字面量的括号。
-
()
用于字符串字面量 (%q
,%Q
)。 -
[]
用于数组字面量 (%w
,%i
,%W
,%I
),因为它与标准数组字面量保持一致。 -
{}
用于正则表达式字面量 (%r
),因为括号经常出现在正则表达式中。这就是为什么使用{
的不太常见的字符通常是%r
字面量的最佳分隔符。 -
()
用于所有其他字面量(例如%s
,%x
)
# bad
%q{"Test's king!", John said.}
# good
%q("Test's king!", John said.)
# bad
%w(one two three)
%i(one two three)
# good
%w[one two three]
%i[one two three]
# bad
%r((\w+)-(\d+))
%r{\w{1,2}\d{2,5}}
# good
%r{(\w+)-(\d+)}
%r|\w{1,2}\d{2,5}|
元编程
避免不必要的元编程
避免不必要的元编程。
不要猴子补丁
编写库时不要在核心类中乱搞(不要猴子补丁)。
块 class_eval
class_eval
的块形式优于字符串插值形式。
提供位置
当您使用字符串插值形式时,始终提供 __FILE__
和 __LINE__
,以便您的回溯有意义
class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
define_method
define_method
优于 class_eval { def … }
eval
注释文档
当使用 class_eval
(或其他 eval
)进行字符串插值时,添加一个注释块,显示其插值后的外观(Rails 代码中使用的做法)
# from activesupport/lib/active_support/core_ext/string/output_safety.rb
UNSAFE_STRING_METHODS.each do |unsafe_method|
if 'String'.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{unsafe_method}(*params, &block) # def capitalize(*params, &block)
to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block)
end # end
def #{unsafe_method}!(*params) # def capitalize!(*params)
@dirty = true # @dirty = true
super # super
end # end
EOT
end
end
不要使用 method_missing
避免使用 method_missing
进行元编程,因为回溯会变得混乱,行为不会列在 #methods
中,并且拼写错误的方法调用可能会静默地工作,例如 nukes.luanch_state = false
。考虑使用委托、代理或 define_method
代替。如果您必须使用 method_missing
-
只捕获具有明确定义前缀的方法,例如
find_by_*
- 使您的代码尽可能地断言。 -
在您的语句末尾调用
super
-
委托给断言的、非魔术方法
# bad
def method_missing(meth, *params, &block)
if /^find_by_(?<prop>.*)/ =~ meth
# ... lots of code to do a find_by
else
super
end
end
# good
def method_missing(meth, *params, &block)
if /^find_by_(?<prop>.*)/ =~ meth
find_by(prop, *params, &block)
else
super
end
end
# best of all, though, would to define_method as each findable attribute is declared
优先使用 public_send
优先使用 public_send
而不是 send
,以避免绕过 private
/protected
可见性。
# We have an ActiveModel Organization that includes concern Activatable
module Activatable
extend ActiveSupport::Concern
included do
before_create :create_token
end
private
def reset_token
# some code
end
def create_token
# some code
end
def activate!
# some code
end
end
class Organization < ActiveRecord::Base
include Activatable
end
linux_organization = Organization.find(...)
# bad - violates privacy
linux_organization.send(:reset_token)
# good - should throw an exception
linux_organization.public_send(:reset_token)
优先使用 __send__
优先使用 __send__
而不是 send
,因为 send
可能与现有方法重叠。
require 'socket'
u1 = UDPSocket.new
u1.bind('127.0.0.1', 4913)
u2 = UDPSocket.new
u2.connect('127.0.0.1', 4913)
# bad - Won't send a message to the receiver object. Instead it will send a message via UDP socket.
u2.send :sleep, 0
# good - Will actually send a message to the receiver object.
u2.__send__ ...
Gemfile 和 Gemspec
Gemspec 中不包含 RUBY_VERSION
Gemspec 不应包含 RUBY_VERSION
作为切换依赖项的条件。RUBY_VERSION
由 rake release
确定,因此用户最终可能会使用错误的依赖项。
# bad
Gem::Specification.new do |s|
if RUBY_VERSION >= '2.5'
s.add_runtime_dependency 'gem_a'
else
s.add_runtime_dependency 'gem_b'
end
end
通过以下方式修复:
-
安装后消息。
-
将两个 gem 都添加为依赖项(如果允许)。
-
如果是开发依赖项,则将其移至 Gemfile。
其他
不要检查非 nil
除非处理布尔值,否则不要进行显式的非nil
检查。
# bad
do_something if !something.nil?
do_something if something != nil
# good
do_something if something
# good - dealing with a boolean
def value_set?
!@some_boolean.nil?
end
全局输入/输出流
使用$stdout/$stderr/$stdin
而不是STDOUT/STDERR/STDIN
。STDOUT/STDERR/STDIN
是常量,虽然你实际上可以在 Ruby 中重新赋值(可能重定向一些流)常量,但如果你这样做,你会得到解释器的警告。
# bad
STDOUT.puts('hello')
hash = { out: STDOUT, key: value }
def m(out = STDOUT)
out.puts('hello')
end
# good
$stdout.puts('hello')
hash = { out: $stdout, key: value }
def m(out = $stdout)
out.puts('hello')
end
注意
|
流常量的唯一有效用例是获取对原始流的引用(假设你已经重定向了一些全局变量)。 |
警告
使用warn
而不是$stderr.puts
。除了更简洁和清晰之外,warn
允许你在需要时抑制警告(通过将警告级别设置为 0,方法是使用-W0
)。
# bad
$stderr.puts 'This is a warning!'
# good
warn 'This is a warning!'
Array#join
优先使用Array#join
,而不是使用带有字符串参数的相当神秘的Array#*
。
# bad
%w[one two three] * ', '
# => 'one, two, three'
# good
%w[one two three].join(', ')
# => 'one, two, three'
数组强制转换
当处理你想作为数组处理的变量时,但你不确定它是否是数组,使用Array()
而不是显式的Array
检查或[*var]
。
# bad
paths = [paths] unless paths.is_a?(Array)
paths.each { |path| do_something(path) }
# bad (always creates a new Array instance)
[*paths].each { |path| do_something(path) }
# good (and a bit more readable)
Array(paths).each { |path| do_something(path) }
范围或between
尽可能使用范围或Comparable#between?
,而不是复杂的比较逻辑。
# bad
do_something if x >= 1000 && x <= 2000
# good
do_something if (1000..2000).include?(x)
# good
do_something if x.between?(1000, 2000)
谓词方法
优先使用谓词方法,而不是使用==
进行显式比较。数字比较是可以的。
# bad
if x % 2 == 0
end
if x % 2 == 1
end
if x == nil
end
# good
if x.even?
end
if x.odd?
end
if x.nil?
end
if x.zero?
end
if x == 0
end
没有神秘的 Perl 式
避免使用 Perl 风格的特殊变量(如$:
、$;
等)。它们非常神秘,除了单行脚本之外,不建议在任何地方使用它们。
# bad
$:.unshift File.dirname(__FILE__)
# good
$LOAD_PATH.unshift File.dirname(__FILE__)
如果需要,使用English
库提供的用户友好的别名。
# bad
print $', $$
# good
require 'English'
print $POSTMATCH, $PID
尽可能使用require_relative
对于所有内部依赖项,你应该使用require_relative
。require
的使用应该保留给外部依赖项。
# bad
require 'set'
require 'my_gem/spec/helper'
require 'my_gem/lib/something'
# good
require 'set'
require_relative 'helper'
require_relative '../lib/something'
这种方式更具表达性(明确哪些依赖项是内部的,哪些不是),并且更高效(因为require_relative
不需要尝试所有$LOAD_PATH
,与require
相反)。
始终警告
编写ruby -w
安全代码。
无可选哈希参数
避免使用哈希作为可选参数。方法是否做得太多?(对象初始化器是此规则的例外)。
实例变量
使用模块实例变量而不是全局变量。
# bad
$foo_bar = 1
# good
module Foo
class << self
attr_accessor :bar
end
end
Foo.bar = 1
OptionParser
使用OptionParser
解析复杂的命令行选项,使用ruby -s
解析简单的命令行选项。
无参数变异
不要修改参数,除非这是方法的目的。
三是你要数的数字
避免超过三层的块嵌套。
函数式代码
以函数式方式编写代码,在有意义的情况下避免变异。
a = []; [1, 2, 3].each { |i| a << i * 2 } # bad
a = [1, 2, 3].map { |i| i * 2 } # good
a = {}; [1, 2, 3].each { |i| a[i] = i * 17 } # bad
a = [1, 2, 3].reduce({}) { |h, i| h[i] = i * 17; h } # good
a = [1, 2, 3].each_with_object({}) { |i, h| h[i] = i * 17 } # good
不要显式使用.rb
到require
省略传递给require
和require_relative
的文件名的.rb
扩展名。
注意
|
如果省略了扩展名,Ruby 会尝试在名称中添加 '.rb'、'.so' 等,直到找到为止。如果找不到名为的文件,将引发LoadError 。存在一个边缘情况,如果foo.so 文件存在,则会加载foo.so 文件而不是LoadError ,如果require 'foo.rb' 将更改为require 'foo' ,但这似乎无害。
|
# bad
require 'foo.rb'
require_relative '../foo.rb'
# good
require 'foo'
require 'foo.so'
require_relative '../foo'
require_relative '../foo.so'
避免使用tap
tap
方法可以帮助调试,但不要将其保留在生产代码中。
# bad
Config.new(hash, path).tap do |config|
config.check if check
end
# good
config = Config.new(hash, path)
config.check if check
config
这更简单、更高效。
历史
此指南最初于 2011 年作为内部公司 Ruby 编码指南(由 Bozhidar Batsov 编写)开始。Bozhidar 作为一名 Ruby 开发人员,一直对一件事感到困扰 - Python 开发人员有一个很棒的编程风格参考(PEP-8),而 Ruby 开发人员从未获得过官方指南,记录 Ruby 编码风格和最佳实践。Bozhidar 坚信风格很重要。他还相信,像 Ruby 这样优秀的黑客社区,应该能够制作出这份梦寐以求的文档。其余的都是历史了……
在某个时候,Bozhidar 决定他正在做的工作可能对 Ruby 社区的成员来说很有趣,而且世界并不需要另一个内部公司指南。但世界肯定可以从社区驱动的、社区认可的 Ruby 编程实践、习语和风格规范中受益。
Bozhidar 在项目过渡到 RuboCop 总部之前,担任了该指南的唯一编辑几年,之后组建了一个编辑团队。
自从指南问世以来,我们收到了来自世界各地优秀的 Ruby 社区成员的大量反馈。感谢所有建议和支持!我们可以共同创建一个对每个 Ruby 开发人员都有益的资源。
灵感来源
许多人、书籍、演示文稿、文章和其他风格指南影响了社区 Ruby 风格指南。以下是一些例子:
贡献
本指南仍在不断完善中 - 一些指南缺乏示例,一些指南的示例不足以清晰地说明它们。改进这些指南是帮助 Ruby 社区的一个很棒(且简单)的方式!
这些问题将在适当的时候(希望)得到解决 - 现在请记住它们。
本指南中没有内容是不可更改的。我们希望与所有对 Ruby 编码风格感兴趣的人一起合作,以便最终创建一个对整个 Ruby 社区都有益的资源。
欢迎您随时提交问题或发送包含改进内容的拉取请求。感谢您的帮助!
您也可以通过以下平台之一为项目(和 RuboCop)提供财务支持
如何贡献?
很简单,只需按照以下贡献指南操作即可
-
在 GitHub 上 Fork rubocop/ruby-style-guide
-
在功能分支中进行功能添加或错误修复。
-
包含对更改的 良好描述
-
将您的功能分支推送到 GitHub
-
发送 拉取请求
题辞
本指南使用 AsciiDoc 编写,并使用 AsciiDoctor 发布为 HTML。指南的 HTML 版本托管在 GitHub Pages 上。
指南最初使用 Markdown 编写,但在 2019 年转换为 AsciiDoc。
许可证
本作品根据 知识共享署名 3.0 通用许可协议 授权
传播
社区驱动的风格指南对于不知道其存在的社区来说毫无用处。在 Twitter 上发布指南,与您的朋友和同事分享。我们收到的每条评论、建议或意见都会让指南变得更好一点。我们想要拥有最好的指南,不是吗?