サーバサイド開発未経験者がRuby on Railsで躓いたところ

全国の新卒エンジニアの皆さん、元気してますか。私はというと学生の頃クライアントしか触ってなかったのに気づいたら半年ほどガッツリとRailsで書かれたゲームのバックエンドのWebAPIと格闘していました…。

で、サーバサイドの開発経験が無い状態でいきなり取り掛かったものですから、色々前提として必要な知識が穴ぼこになっていて結構苦労したみたいなところがありまして。どこらへんで引っかかったかなってのを記録しておくと後々便利だと思ったので、久しぶりにエントリを書いてみます。

もし気づいた点などあればコメント欄や@yashiheiまでマサカリをお待ちしております。

Rails触る前の技術スタック

  • Ruby分からん…、というか静的じゃない言語自体あまり経験無い(ある程度触った言語はC/C++, C#程度)
  • DBやSQLに関しても無知に等しい、ORMも知らない
  • テストとかまともに書いたことない

Rubyで躓いたところ

戻り値を明示しなくていい

メソッドの戻り値は return に渡した値です。return が呼び出されなかった場合は、 body の最後の式の値を返します。 body の最後の式が値を返さない式の場合は nil を返します。

クラス/メソッドの定義 (Ruby 2.7.0 リファレンスマニュアル)

今ではもう慣れたけど最初のうちはかなり戸惑いましたね…。returnを書くことも出来るけど基本的には省略するのが一般的みたいです。

def hoge
  'hogehoge'
end

hoge
=> "hogehoge"

シンボル

Rubyの内部実装では、メソッド名や変数名、定数名、クラス名など の`名前'を整数で管理しています。これは名前を直接文字列として処理するよりも 速度面で有利だからです。そしてその整数をRubyのコード上で表現したものがシンボルです。

class Symbol (Ruby 2.7.0 リファレンスマニュアル)

シンボルは識別子として記述するのに適しています。 シンボルは、どんな物かではなく、何者であるかがすべてです。 irbを立ち上げて、次の違いを見てみてください。

irb(main):001:0> :george.object_id == :george.object_id
=> true
irb(main):002:0> "george".object_id == "george".object_id
=> false
irb(main):003:0>

他言語からのRuby入門

無駄なオブジェクトが生成されないシンボルは、速度の点で文字列オブジェクトよりも有利であり、またその記号の可読性を高めたい場合、整数オブジェクトよりも有利である。

Rubyのシンボルは文字列の皮を被った整数だ!

今まで触ってきた言語の中には無い概念だったので結構戸惑いました。初めのうちは文字列との違いが良くわからなかった。一意な識別子がRubyでは必要なのですね。

あとハッシュのキーにシンボルを使う場合の拡張記述方法が若干初学者には紛らわしかった気が。慣れれば何とも無いですが。

> { :key => "value" } # Ruby1.9以前のシンボルをキーとして記述する例
=> {:key=>"value"}
> { key: "value" } # 今は殆どこう書かれてる
=> {:key => "value"}

block/yield/Proc/lambda

Rubyのブロックは、メソッド同様、ファーストクラスではありません。従ってメソッドに付する以外にそれが独立してRuby空間内に存在することはできないのです。しかし、メソッドがMethodオブジェクトによってファーストクラス化できるのと同様、Rubyではブロックもファーストクラス化することができます。それがProcオブジェクト(手続きオブジェクト)です。

Procを制する者がRubyを制す(嘘)

ブロックは作りかけのオブジェクト
ブロック(実際にはクロージャ)は標準ライブラリでもすごく使われています。 ブロックを呼び出すには、yieldを使うか、引数リストに特別な引数を追加して それをProcオブジェクトにします。

他言語からのRuby入門

block/yield/Procの関連が良くわかった記事
Ruby block/proc/lambdaの使いどころ - Qiita

Rubyで最初にぶち当たる壁はここにあると思います。初学者がいきなりyieldの事理解しようとすると詰みがちかと。

上記の記事のように

  • blockはオブジェクト化しないと独立して存在出来ない
  • 引数に&を前置することでProcオブジェクトに変換する
  • オブジェクト化後はProc#callで呼び出せる
  • yieldと記述することでProc#callやblockを引数で受け取る記述を省略出来るよ!!

と、段階を踏まないと理解が難しかったです。*1

それとRubyで頻繁に出てくるイディオムとしてシンボルに&を付けるものがあると思います。

> ['syaro', 'cocoa', 'chino', 'rize', 'chiya'].map(&:camelize)
=> ["Syaro", "Cocoa", "Chino", "Rize", "Chiya"]

これも意味分からなかったのですが以下の説明で尻尾が掴めました。

1. &にSymbolオブジェクトが渡されたので、RubyはこれをProcオブジェクトに変換することを試みる。そのためにSymbol#to_procを呼ぶ。
2. そしてSymbol#to_procは、ブロック引数に順次渡されるオブジェクト(ここでは’charlie’などのStringオブジェクト)に対し、&に渡されたSymbolオブジェクト(ここでは:capitalize)をメソッドとして呼ぶような実装がなされている。

Procを制する者がRubyを制す(嘘)

あとは、所属したプロジェクトでlambda_driverというgemが追加されてまして。このあたりのコードもProc周りの挙動を理解してようやくつかめてきた感じ。lambda_driver周りを勉強すると関数型言語の触りとして良いかもですね。

lambda_driver作者のyuroyoroさんのスライド。lambda_driverの関数合成について自然に入ってきます。

speakerdeck.com

Enumerableモジュールを使いこなせて無かった

module Enumerable (Ruby 2.7.0 リファレンスマニュアル)

よくレビューでRubyらしく書くならこうみたいに指摘されることがあったのですが、その原因の大半がEnumerableを使いこなせてなかったなあってところが大きいです。手続き的に書くことに慣れきってしまってるですよね…。Enumerable使いこなせるとRuby楽しくなってくるよーってこの前先輩と話してました。

動的言語への抵抗感

実行時に型解決がされるって感覚が暫く身につかなかったです。最初は実行前に型が解決されないのつらいとしか思ってなかったけど、ダックタイピングやメタプロの片鱗に触れてからはちょっと感動してこういった言語も有りだな~(謎に上から目線)って思えてきました。

QuoraにMatzの興味深い回答があったので紹介。

したくない理由は複数あるのですが、最大のものは、型アノテーションを加えることで言語の性質が変化してしまいそうだということです。Rubyのプログラムを書いていて一番魅力的なのは簡潔にすばやく記述できることだと思うのですが、型アノテーションはそれを阻害しそうです。もちろん、オプショナルなので書かなければよいというのもひとつの主張ですが、言語に型アノテーションが加わると、コミュニティとして「型を書くのは必須」のような動きが出てくるのが容易に予想できます。それはRubyの一番のこだわりである「書き味」を変えてしまいそうです。

TypeScriptのような型付けをRubyに実装したら最高の言語になると思いますか?

メタプロ

Rubyは君を信頼する。Rubyは君を分別のあるプログラマとして扱う。Rubyメタプログラミングのような強力な力を与える。ただし、大いなる力には、大いなる責任が伴うことを忘れてはいけない」

〇〇というメソッド何処で定義されてるんだろう?って検索しても見つからずにん~?って怪しいところ探したらメタプロで定義されてたりしたのは最初はかなり戸惑いました。メタプロはプロジェクトごとに使われ方が全然違うのかなとは思うのですが弊社のプロジェクトではバリバリ使っていた印象。

今ならメタプログラミングRuby読めるかな…。

Railsで躓いたところ

ActiveRecord

ActiveRecordの挙動が良く分からないまま前線でコード書き続けてた状態が続いてて結構不味かったっていう認識があります。

最初はActiveRecordは自分のようなSQLに関する知識が未熟な人でもDBを扱うアプリケーションが作れる!なんて素敵なんだって思ったりもしたけど、むしろActiveRecordのようなORMはSQLに習熟した人がSQLベタ書きするのつれえから使うと楽だよねみたいな道具であって、SQLを理解せずに使おうとすると死ぬ。と思いました。初学者はちゃんとクエリログを読もう!!

ActiveRecordの挙動を理解出来なかったってところをもう少し噛み砕くと

  • モデルのインスタンスを返すメソッドとActiveRecord::Relationを返すメソッドの区別が曖昧だった
    • firstとかfindなどは即時クエリを実行して対象モデルのインスタンスを返してくれるけど、whereやselectなどActiveRecord::Relationを返されるメソッドでは続けてクエリに関して操作出来るメソッドをチェーン出来るって認識が抜け落ちてた
  • 値が必要になるまでクエリの実行が遅延されるってことを理解してなかった
    • 必要ないのにArray化してしまうメソッドを呼んでしまったり…
  • ActiveRecordとEnumerableのメソッドを一部混同していた
    • firstとかselectとか両方に存在するメソッドあるので…。おそらく上記の誤解がごちゃごちゃになって区別がついてなかった

ActiveRecordの挙動が分かってないと簡単にクエリが爆発するようなコード書いちゃうのでそこは早いところ矯正してあげないと大変なことになります。具体的には下のスライドで示されているのようなアンチパターンをガンガン踏み抜いてしまう。クエリログを読もう!!(大事なことなので二度

speakerdeck.com

(※追記)Railsの文脈を知らなかった

Clean Architectureを読んだあとに、Railsに対して幾つかの疑問が出てきました。Modelに責務を集約しすぎてるんですよね。

それで「Rails 密結合」とやら検索して巡り合ったスライドがこちらです。

speakerdeck.com

上記のスライドを読んだ後に理解したこととして、道具としてRailsがどういう特性を持っているのか理解しました。

  • 少人数スタートアップ向けの開発に特化
  • Railsは早くて綺麗を実現する為に密結合であることを妥協している
  • ModelへのCRUD操作を中心に整理されてる

振り返ってみると、初めて触れたフルスタックWebフレームワークがRailsだったので比較対象がない状態でした。Railsが何を犠牲にして何を得てるのかよく分からないまま触れていたのだと思います。

Railsというフレームワークが取った戦略を理解し、またHanamiというClean Architectureを採用したフレームワークに触れることで、初めてRailsのことを客観的に眺められるようになった気がします。

例えばサービス層を採用したRailsアプリは沢山あると思われるのですが、何故サービス層が必要かRailsしか知らない状態で把握するのは難しかったです。単一のModelに対して複数のユースケースが絡むとどうしても辛くなる、のでユースケース固有の処理を書く層が必要。それってClean ArchitectureでいうInteractorだよねといった解釈が出来るようになってようやくサービス層が何なのか少しは掴めました。

このあたりはまだまだ勉強不足なので邁進したいところ…。

参照していた文献など

昨今のグーグル先生は、かなりアレな情報を上位検索結果に出してくるので信頼出来る参照先を確保しておくことが大切だなと思います。

Ruby

Rails

※加筆修正 (1/4)

*1:厳密にはProc#callとyieldの場合だとyieldの方が早いとか何とか