駆け出し新卒エンジニアによるOSSコードリーディングのすすめ(Ruby編)

[Increments × cyma (Ateam Inc.) Advent Calendar 2020](https://qiita.com/advent-calendar/2020/increments-cyma) の2日目は、株式会社エイチーム EC事業本部の島袋（@shimabee）が担当します。

# はじめに
駆け出し新卒エンジニアがRubyで書かれたOSSコードを読んで行く中で得られたことをまとめました。
OSSコードリーディングの導入としてOpenStructというRubyの標準ライブラリの一部を読むパートもありますので、是非とも最後までお読みください。

# なぜ、OSSコードリーディングを始めたのか
OSSコードリーディングを始めた目的は、コードリーディングスキルの向上です。
コードリーディングスキルはプロのエンジニアには重要なスキルです。なぜなら、自分だけで好き勝手に開発していた学生の頃とは異なり、エンジニアとして働き始めてからは他人の書いたコードを読み解き、その上で実装を考えることが殆どだからです。
このコードリーディングスキルを上達させる方法をを上司や先輩に相談したところ、勧められたのがOSSのコードリーディングでした。

特に有名なOSSのソースコードは多くの優秀なエンジニアが関わっているのでコードの質も担保されていますし、何より無料で見放題です。同じようにコードリーディングスキル課題を抱えている方も是非試していただきたいです。

# OSSコードリーディングで得たもの
OSSコードリーディングで得たものは大きく二つあります。
1. コードリーディングスキル
2. 仕組みを知りに行く習慣

コードリーディングによって、数多くのRubyのメソッドとその使用ケースを見て知ることができ、徐々にではありますが、今までよりも正確に速くコードを読むことができるようになっています。
ただ、コードリーディングのスキルはいきなり上がるものではないので、地道に続ける必要があるということも実感済みです。

仕組みを知りに行く習慣というのは、ライブラリやフレームワークを使用する際に使い方だけを知りに行くのではなく、その実装を確認しにいく習慣のことです。
仕組みを知るとそのライブラリやフレームワークをより適切に使用することができます。
それだけでなく、頻繁に実装を確認しに行くキッカケにもなるので知識のインプットの習慣化にも繋がっています。


# 実際に読んでみよう！
実際にやってみて体験した方が分かると思うので、ここからは実際にコードを読んでいこうと思います。
今回読んでいくのは、Rubyの標準ライブラリであるOpenStructです。

## なぜ、OpenStructなのか
私がOSSコードリーディングで、2番目に読んだのがこのOpenStructです。
その際にOpenStructを選んだ理由は二つで、実装がシンプルなこととRubyの標準ライブラリであることです。
シンプルなものを選んだ理由としては、駆け出しの私でも構えることなく、気軽にコードリーディングができると考えたからです。
標準ライブラリである理由は、コードの質が良いからです。Rubyの標準ライブラリと言うことはRubyを作っている方々が書いたコードと言うことになるので、参考にするにはもってこいかと思います。

## OpenStructとは
さあ、では早速読んでいきましょうと言いたいところですが、
コードリーディングをする前に、OpenStructがどのようなものかについて見ていきます。
機能や使い方としっかりと抑えてから、仕組みを知りに行くことが大切です。

## 概要
> 要素を動的に追加・削除できる手軽な構造体を提供するクラスです。
> OpenStruct のインスタンスに対して未定義なメソッド x= を呼ぶと、 OpenStruct クラスの BasicObject#method_missing で捕捉され、そのインスタンスにインスタンスメソッド x, x= が定義されます。この挙動によって要素を動的に変更できる構造体として働きます。

[Ruby 2.7.0 リファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/class/OpenStruct.html)より

## 使い方
インスタンス作成後に、任意のインスタンスメソッドを動的に定義できます。

```ruby
person = OpenStruct.new
person.name = "John Smith"
person.age  = 70

person.name      # => "John Smith"
person.age       # => 70
person.address   # => nil
```

初期値にハッシュを渡すこともできます。


```ruby

person = OpenStruct.new({ name: "John Smith", age: 70 })

person.name      # => "John Smith"
person.age       # => 70

```


## コードリーディング
使い方が分かったところで、コードリーディングに移っていきます。

リファレンスマニュアルに大まかな仕組みが書いてありますね！
> OpenStruct のインスタンスに対して未定義なメソッド x= を呼ぶと、 OpenStruct クラスの BasicObject#method_missing で捕捉され、そのインスタンスにインスタンスメソッド x, x= が定義されます。

上記をもとに実装を追っていきましょう！
今回見ていくソースは、[コチラ](https://github.com/ruby/ostruct/blob/master/lib/ostruct.rb) です。

### 未定義なメソッドの捕捉
未定義なメソッドの捕捉には、[BasicObject#method_missing](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html)を利用します。
`method_missing`は、呼びだされたメソッドが定義されていなかった時、Rubyインタプリタに呼び出されるメソッドであるため、これをオーバーライドすることにより、未定義なメソッドが呼び出された際に捕捉することができます。
OpenStructではこの仕組みを利用し、`method_missing`をオーバーライドする中で`set_ostruct_member_value!`を呼び、未定義なメソッドを定義していくわけです。

```ruby
private def method_missing(mid, *args) # :nodoc:
  len = args.length
  if mname = mid[/.*(?==\z)/m]
    if len != 1
      raise! ArgumentError, "wrong number of arguments (given #{len}, expected 1)", caller(1)
    end
    set_ostruct_member_value!(mname, args[0])
  elsif len == 0
  else
    begin
      super
    rescue NoMethodError => err
      err.backtrace.shift
      raise!
    end
  end
end
```


### 捕捉した未定義メソッドを定義する。
では、`set_ostruct_member_value!`の中身も見ていきましょう。
`set_ostruct_member_value!`内では、`method_missing`で受け取ったメソッド名をシンボルに変換し、`new_ostruct_member`へ渡しています。

```ruby
def []=(name, value)
  name = name.to_sym
  new_ostruct_member!(name)
  @table[name] = value
end
alias_method :set_ostruct_member_value!, :[]=
private :set_ostruct_member_value!
``` 

`new_ostruct_member!`内で読み込み、書き込みのメソッドを`define_singleton_method`を使い、特異メソッドとして定義します。

```ruby
def new_ostruct_member!(name) # :nodoc:
  unless @table.key?(name) || is_method_protected!(name)
    define_singleton_method!(name) { @table[name] }
    define_singleton_method!("#{name}=") {|x| @table[name] = x}
  end
end
```

未定義なメソッドを動的に定義する仕組みが見えましたね！
思ったよりも読む部分が少なくて、分かり易かったのではないでしょうか？


# まとめ
OpenStructの実装の一部を見ていきましたが、いかがでしたでしょうか？
全然難しいことはなかったと思います。それに`method_missing`をオーバーライドする辺りはRubyの面白さが見えるところではないかとも思います。
この記事をキッカケに様々なOSSを読んで、仕組みを知りに行く面白さやRubyという言語の面白さを体感してみてください。

# 次のステップ
次のステップとしては、gemやフレームワークの一部の機能について見ていくと良いかもしれません。
個人的なおすすめとしては、[settingslogic](https://github.com/binarylogic/settingslogic/blob/master/lib/settingslogic.rb)やRailsの[strong_parameters](https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb)です。
どちらもコード量は少ないですし、仕組みも分かりやすい部類に入るかと思います。是非お試しください。

最後まで読んでいただきありがとうございました。
ご意見、ご感想があればコメントいただきたいです。

以上、駆け出し新卒エンジニアによるOSSコードリーディングのすすめ(Ruby編)でした。

[Increments × cyma (Ateam Inc.) Advent Calendar 2020](https://qiita.com/advent-calendar/2020/increments-cyma) の3日目は、Increments株式会社の @degudegu2510 がお送りします！お楽しみに！