動的型付け言語として有名なRuby。データ型を考慮しなくてもいいので開発スピードが速く、とても使いやすいと思われます。
ただ、コードの規模が大きければ大きいほど、型エラーのリスクが上がり、
Typescriptのような静的型付け言語がウェブ開発界に流行ってきました。
この記事にて、StripeやShopifyが使用する、Rubyを静的型付け言語にするSorbetというgemを紹介したいと思います。

準備

既存プロジェクトとして、以下のRubyファイルを作成しました。

user.rb

class User
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
  # 去年時点の年齢を出力
  def age_last_year
    age - 1
  end
end
main.rb

require_relative 'user'
user = User.new('ユーザー', '5')

User.age_last_yearから見ますと、@ageは数字を必要としますが、
main.rbで作成されたuserのageは文字列となります。
データ型チェッカーが設置されていないので、
age_last_yearを呼び出さない限りエラー、ウォーニングが特に発生せず、
潜在的なエラーがあると思われます。

Sorbetをインストール

データ型による不具合を避けるために、Sorbetを導入してみました。
そのために、gemfileを作成し、以下のgemを追加しました:

source "https://rubygems.org"
ruby "3.3.0"
gem 'sorbet', :group => :development
gem 'sorbet-runtime'
gem 'tapioca', require: false, :group => [:development, :test]

次は以下のコマンドでgemをインストールし、Sorbetをセットアップしました。

bundle install # gemをインストール
bundle exec tapioca init # sorbetをセットアップ

tapioca initコマンドは成功すると、bin、sorbetというフォルダが自動的に作成されます。

データ型チェックを有効にする

Sorbetが有効になりましたが、データ型が宣言されていない限りエラーが特に発生しません。
変数、関数のデータ型を宣言するには、Tモジュールのlet(変数の場合)やsig(関数)という関数を使えます。
書き方は以下のようになります:

# 関数の場合
sig { params(変数名: データ型, ...).戻り値のデータ型 }
# 変数の場合
変数 = T.let(値, データ型)
# attr_reader、attr_accessorなどの場合
sig { returns(データ型) }

よって、user.rbにあるデータ型を宣言する:

# typed: true
# データ型チェックを有効にします
require 'sorbet-runtime' # Sorbetをインポート
class User
  extend T::Sig
  sig { returns(String) }
  attr_accessor :name
  sig { returns(Integer) }
  attr_accessor :age
  sig { params(name: String, age: Integer).void } # 引数のデータ型を宣言
  def initialize(name, age)
    @name = name
    @age = age
  end
  # 去年時点の年齢を出力
  def age_last_year
    age - 1
  end
end

今、main.rbを実行しますと、次のエラーが表示されます:「Parameter ‘age’: Expected type Integer, got type String with value “5” (TypeError)」
データ型チェックのおかげで、潜在的な不具合は自動的に発見され、解消しない限りスクリプトの実行は不可能です。

データ型チェックの感度

user.rbの頭に、typed: trueを入力しました。
typedはtrueとされていたら、sigなどで宣言されたデータ型が確認されていますが、
データ型の宣言がされない引数や変数などのデータ型が確認されません。
というわけで、typed: trueは柔軟なデータ型チェックになります。
他のtypedの値は以下のようになります:

typed: ignore # ファイルは無視されます。
typed: false # チェックされるものは主に構文です。データ型自体はチェックされません。
typed: true # 型エラーもチェックされます。データ型の宣言がされない関数などは動的型付け変数としてチェックされます。
typed: strict # データ型の宣言は必須となります。データ型の宣言がされない関数があれば、エラーが発生します。
typed: strong # T.untyped(データ型がない)も無効になります。

typedのおかげで、ファイルごとにデータ型チェックの感度を設定することができ、
円滑に既存プロジェクトに導入できます。
例えば、ファイルの多いプロジェクトですと、一つのファイルずつ、typedをtrueに設定し、データ型を宣言することができます。

終わりに

Sorbetのおかげで、Rubyを静的型付け言語になります。
データ型の宣言をファイルごとに設定できますし、新規プロジェクトにも既存プロジェクトにも簡単に適用できます。
そういった理由で、データ型の宣言が必須なウェブアプリやスクリプトでも、Rubyを候補として考えることができます。