Rubyでpackage-private的な可視性を実現する

## TL;DR

`private_constant` というのを使えば定数のスコープを private にできるらしいのでそれを使う。
恥ずかしながら今まで知らなかった。

## Ruby でも静的型検査したい

と思ってる人が世の中にどれくらいいるかわからないけど、そういうライブラリが出てきたり（僕は [sorbet](https://sorbet.org/) を使ってみている）、Ruby 3 からは静的型が使えるだのなんだのっていう情報も飛び交っったりしていて、ダックタイピングを前提にコードの設計を考えないといけなかった今までとは若干趣が変わってきたかもしれないなぁと思っている。

ただ不満なのは、静的に型付けしてくれてもすべての型がグローバルだとどこを疎結合にしたいとかどこは凝集度が高いと考えいているかっていうのをコードレベルで表現できないし、最悪いろいろなものをバイパスされてしまう [^1] ので、Java で言うところの pckage-private 的な可視性を使いたいなと思った。

[^1]: そんなやつをチームに入れるなという主張もあると思うが、それは別の問題なのでここでは扱わない。


```ruby
module UserPackage
  class User
    def initialize(first_name, last_name)
      @first_name = first_name
      @last_name = last_name
    end
  end

  class FirstName
    def initialize(value)
      @value = value
    end
  end

  class LastName
    def initialize(value)
      @value = value
    end
  end
end

first_name = UserPackage::FirstName.new('Akira') #=> UserPackage 外でインスタンス生成できる
last_name = UserPackage::LastName.new('Suenami') #=> UserPackage 外でインスタンス生成できる
user = UserPackage::User.new(first_name, last_name)
```

`UserPackage::User` はいわゆる集約のルートオブジェクトで、その内部に持つ 2 つのバリューオブジェクト `UserPackage::FirstName` と `UserPackage::LastName` はルートオブジェクトを通してのみ参照を渡したいとする。

そういう場合、定数のスコープを private にする `private_constant` というのがあって、それを利用して以下のようにする。

```ruby
# typed: strong

require 'sorbet-runtime'

module UserPackage
  class User
    extend T::Sig

    sig {params(first_name: FirstName, last_name: LastName).void}
    def initialize(first_name, last_name)
      @first_name = T.let(first_name, FirstName)
      @last_name = T.let(last_name, LastName)
    end

    sig {params(first_name: String, last_name: String).returns(User)}
    def self.factory(first_name, last_name)
      first_name = FirstName.new(first_name)
      last_name = LastName.new(last_name)
      new(first_name, last_name)
    end
  end

  class FirstName
    extend T::Sig

    sig {params(value: String).void}
    def initialize(value)
      @value = T.let(value, String)
    end
  end

  class LastName
    extend T::Sig

    sig {params(value: String).void}
    def initialize(value)
      @value = T.let(value, String)
    end
  end

  private_constant :FirstName
  private_constant :LastName
end

# first_name = UserPackage::FirstName.new('Akira') #=> private constant UserPackage::FirstName referenced (NameError)
# last_name = UserPackage::LastName.new('Suenami') #=> private constant UserPackage::LastName referenced (NameError)
# user = UserPackage::User.new(first_name, last_name)
user = UserPackage::User.factory('Akira', 'Suenami')
```

こんな感じになる。ついでに sorbet の型注釈もつけてみたのと、 `UserPackage` の外にはコンストラクタの引数に渡す型（ `FirstName` `Lastname` ）が公開されなくなったのでプリミティブ型からインスタンスを作るための `factory` メソッドを作った。これでバリューオブジェクトは `UserPackage` module の外からは完全に見えなくなった感じ。

定数の定義後じゃないと `private_constant` の宣言は効かないっぽいので、実業務ではファイルのロード順とか気にしとく必要はありそう。ほとんどの場合は AcitveSupport の autoload に任せることになると思うので、該当のバリューオブジェクトを定義したファイルのクラス定義直後に書けば概ね問題ないとは思う。

## まとめ

特にない。僕自身も実際のプロダクトではまだやってないので、もし懸念とかあれば誰か教えて欲しい。
感覚的には結構いいのでは？っていう気がしている。

## 2019/07/24 19:57 追記

なんか ActiveSupport の autoload だとあまりうまくいかなさそうですね…
https://github.com/rails/rails/issues/25813
https://github.com/rails/rails/issues/35898

こちらうまく回避できないですかね…（調べてみる）