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/EmptyLinesAroundMethodBodyCop/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を知る必要がなくなります)。
  • 呼び出し順序の明確化。
  • 変更可能: プロジェクト用にリポジトリを作成したくないシナリオがあると判断した場合、ProjectRepository クラスをリファクタリングする必要がなく、新しいサービスクラスを作成できます。
  • Project ファクトリーの各インスタンスは、2 番目の (Repository) オブジェクトを作成しません。

スタイル

RuboCopのルールが提案され、私たちがそれを追加しないことを選択した場合、私たちはその決定をこのガイドに文書化し、より発見しやすくし、関連する議論を参照としてリンクすべきです。

文字列リテラルの引用

修正作業が膨大になるため、文字列リテラルがシングルクォートかダブルクォートかは気にしません。

これまでの議論は以下の通りです:

タイプの安全性

Ruby 3にアップグレードしたことで、型安全性を強制するためのオプションが増えました。

これらのオプションのいくつかはRuby構文の一部としてサポートされており、Sorbetや RBSのような特定の型安全ツールを使用する必要はありません。しかし、将来的にはこれらのツールも検討するかもしれません。

詳細はremote_development ドメイン README のType safetyを参照してください。

機能パターン

Ruby、特にRailsは主にオブジェクト指向プログラミングパターンに基づいていますが、Rubyは非常に柔軟な言語であり、関数型プログラミングパターンもサポートしています。

関数型プログラミングパターン、特にドメインロジックでは、慣用的で使い慣れたRubyのパターンを使用しながら、より読みやすく、メンテナーで、バグに強いコードを作成できることがよくあります。しかし、関数型プログラミング・パターンの中には、Rubyで直接サポートされていても、混乱を引き起こす可能性があり、避けるべきものもあるので、使用には注意が必要です。curry メソッド がその例です。

詳細については