Rubyスタイルガイド
これはRubyコードのためのGitLab固有のスタイルガイドです。このページに書かれていることはすべて、議論のために再開することができます。
Rubyスタイルガイドのルールを強制するためにRuboCopを使っています。
RuboCop のルールがない場合は、慣用的な Ruby を書くための一般的なガイドラインとして、以下のスタイルガイドを参照してください:
一般的に、スタイルが既存の RuboCop ルールや上記のスタイルガイドでカバーされていない場合は、ブロッカーになるべきではありません。
一部のスタイルについては、誰も強く意見を言うべきではないと判断しています。
こちらも参照してください。
ルールのないスタイル
これらのスタイルには RuboCop のルールがありません。
このセクションに追加されたすべてのスタイルについて、そのセクションのバージョン履歴ノートから議論をリンクし、文脈を提供し、参考としてください。
インスタンス変数へのアクセスにattr_reader
GitLab 14.1 で導入されました。
インスタンス変数はクラスの中で様々な方法でアクセスすることができます:
# public
class Foo
attr_reader :my_var
def initialize(my_var)
@my_var = my_var
end
def do_stuff
puts my_var
end
end
# private
class Foo
def initialize(my_var)
@my_var = my_var
end
private
attr_reader :my_var
def do_stuff
puts my_var
end
end
# direct
class Foo
def initialize(my_var)
@my_var = my_var
end
private
def do_stuff
puts @my_var
end
end
公開属性は、クラスの外部にアクセスする場合にのみ使用すべきです。属性が内部でのみアクセスされる場合にどのような戦略をとるかについては、関連するコードに一貫性がある限り、強い意見はありません。
改行スタイルガイド
いくつかの改行スタイルを強制する RuboCopsLayout/EmptyLinesAroundMethodBody
とCop/LineBreakAroundConditionalBlock
に加えて、RuboCop がサポートしていないガイドラインを以下に示します。
ルール: 改行でコードを区切るのは、関連するロジックをグループ化するためだけです。
# bad
def method
issue = Issue.new
issue.save
render json: issue
end
# good
def method
issue = Issue.new
issue.save
render json: issue
end
ルール:ブロックの前に改行
# bad
def method
issue = Issue.new
if issue.save
render json: issue
end
end
# good
def method
issue = Issue.new
if issue.save
render json: issue
end
end
例外:コードブロックが別のコードブロックの内部で開始または終了する場合、改行は不要です。
# bad
def method
if issue
if issue.valid?
issue.save
end
end
end
# good
def method
if issue
if issue.valid?
issue.save
end
end
end
ActiveRecordコールバックの回避
ActiveRecordコールバックを使うと、”オブジェクトの状態を変更する前後にロジックを起動させる “ことができます。
コールバックは、代替手段がない場合に使用しますが、その理由を十分に理解した場合にのみ使用してください。
ActiveRecordオブジェクトに新しいライフサイクルイベントを追加する場合は、コールバックではなくサービスクラスにロジックを追加することが望ましいです。
コールバックを避ける理由
一般的に、コールバックは避けるべきです:
- コールバックは、呼び出し順序が明らかでないため、推論が難しく、コードの物語性を壊します。
- コールバックは、通常のメソッド呼び出しではなく、リフレクションに依存して起動するため、場所を特定し、ナビゲートするのが困難です。
- コールバックは、変更が常にコールバックチェーン全体をトリガするため、オブジェクトの状態に選択的に変更を適用することが難しくなります。
- コールバックはActiveRecordクラスにロジックを閉じ込めます。この緊密な結合は、ビジネスロジックが多すぎるファットモデルを助長し、その代わりに、より再利用性が高く、Composerで、テストが容易なサービスオブジェクトに格納することができます。
- オブジェクトの不正な状態遷移は、属性の検証によってよりよく強制することができます。
- コールバックの多用は、ファクトリーの生成速度に影響します。クラスによっては何百ものコールバックを持つものがあり、 自動テストのためにそのオブジェクトのインスタンスを作成するのは非常に遅いオペレーションとなり、 結果的に遅い仕様となってしまいます。
thoughtbotのビデオでは、このような例をいくつか取り上げています。
GitLabのコードベースはコールバックに大きく依存しており、目に見えない依存関係のために一度追加するとリファクタリングが困難です。そのため、このガイドラインでは既存のコールバックをすべて削除することは求めていません。
コールバックを使うタイミング
コールバックは特別な場合に使用することができます。コールバックを追加する意味がある例をいくつか挙げます:
- 依存関係がコールバックを使用していて、コールバックの動作をオーバーライドしたい場合。
- キャッシュ数のインクリメント
- 現在のモデルのデータにのみ関連するデータの正規化。
コールバックからサービスへの移行例
次のような基本データモデルを持つプロジェクトがあります:
class Project
has_one :repository
end
class Repository
belongs_to :project
end
プロジェクトが作成された後にリポジトリを作成し、プロジェクト名をリポジトリ名として使用したいとします。Railsに慣れた開発者なら、ActiveRecordコールバックのジョブだとすぐに思うかもしれません!と思うかもしれません:
class Project
has_one :repository
after_initialize :create_random_name
after_create :create_repository
def create_random_name
SecureRandom.alphanumeric
end
def create_repository
Repository.create!(project: self)
end
end
class Repository
after_initialize :set_name
def set_name
name = project.name
end
end
class ProjectsController
def create
Project.create! # also creates a repository and names it
end
end
まだ生まれたばかりのRailsアプリには無害に思えますが、Railsアプリが大きく複雑になると、コールバックを使ってこの種のロジックを追加することには多くのデメリットがあります(このドキュメントにすべて記載されています)。代わりに、このロジックをサービスクラスに追加することができます:
class Project
has_one :repository
end
class Repository
belongs_to :project
end
class ProjectCreator
def self.execute
ApplicationRecord.transaction do
name = SecureRandom.alphanumeric
project = Project.create!(name: name)
Repository.create!(project: project, name: name)
end
end
end
class ProjectsController
def create
ProjectCreator.execute
end
end
このようにシンプルなアプリケーションでは、2番目のアプローチのメリットを理解するのは難しいかもしれません。しかし、すでにいくつかの利点があります:
-
Project
の作成ロジックとは別に、Repository
の作成ロジックをテストできます。コードがデメテルの法則に違反しなくなります(Repository
クラスがproject.name
を知る必要がなくなります)。 - 呼び出し順序の明確化。
- 変更可能: プロジェクト用にリポジトリを作成したくないシナリオがあると判断した場合、
Project
とRepository
クラスをリファクタリングする必要がなく、新しいサービスクラスを作成できます。 -
Project
ファクトリーの各インスタンスは、2 番目の (Repository
) オブジェクトを作成しません。
スタイル
RuboCopのルールが提案され、私たちがそれを追加しないことを選択した場合、私たちはその決定をこのガイドに文書化し、より発見しやすくし、関連する議論を参照としてリンクすべきです。
文字列リテラルの引用
修正作業が膨大になるため、文字列リテラルがシングルクォートかダブルクォートかは気にしません。
これまでの議論は以下の通りです:
- https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44234
- https://gitlab.com/gitlab-org/gitlab-foss/-/issues/36076
- https://gitlab.com/gitlab-org/gitlab/-/issues/198046
タイプの安全性
Ruby 3にアップグレードしたことで、型安全性を強制するためのオプションが増えました。
これらのオプションのいくつかはRuby構文の一部としてサポートされており、Sorbetや RBSのような特定の型安全ツールを使用する必要はありません。しかし、将来的にはこれらのツールも検討するかもしれません。
詳細はremote_development
ドメイン README のType safetyを参照してください。
機能パターン
Ruby、特にRailsは主にオブジェクト指向プログラミングパターンに基づいていますが、Rubyは非常に柔軟な言語であり、関数型プログラミングパターンもサポートしています。
関数型プログラミングパターン、特にドメインロジックでは、慣用的で使い慣れたRubyのパターンを使用しながら、より読みやすく、メンテナーで、バグに強いコードを作成できることがよくあります。しかし、関数型プログラミング・パターンの中には、Rubyで直接サポートされていても、混乱を引き起こす可能性があり、避けるべきものもあるので、使用には注意が必要です。curry
メソッド がその例です。
詳細については