はじめに
今年の8月からプログラミングスクールの講師として初学者にRailsを教えてきました。またMENTAでも100名弱の方にプログラミングを教えてきました。それくらい教えると初学者がどんなところでハマりがちでどんな知識が不足しがちなのかが大体わかってきます。要は初心者あるあるですね。
それを今回Advent Calendar一発目に書くことにしました。
ではさっそくいってみましょう!
開発全般編
1. 問題がおきた場合に一気に色々と試しがち
一気に色々と試すと原因の切り分けができなくなります。
例えば「ログインができない」という問題があった時、初学者がやりがちなのはこのようなログイン処理のコードとずーーーっとにらめっこすることです。
def create
@user = login(params[:email], params[:password])
if @user
redirect_to root_path, success: '成功'
else
flash.now[:danger] = '失敗'
render :new
end
end
色々な要素が入り混じっているのでこのコードだけを見てもどこに問題があるのか切り分けができません。
なので例えばですがこのようなアプローチで進めていけば良いのでしょう。
①そもそもこのコントローラのこのアクションが動いているか
これを確認するためにはこのように書けば良いですね。
def create
p '通ってる?'
end
標準出力に何も出力されていなかったらルーティングまたはフォームの書き方に問題がある可能性が高いです。
いくらこのアクションの中の実装が正しくてもそもそもこのアクションが動いていなければ話になりませんよね。
そこが問題なければ次に進みます。
②クライアントから正しくパラメータが送られてきてるか
②-1params
を使っているところをハードコーディングしてみる
def create
@user = login('example@example.com', '12345678') # ハードコーディング
if @user
redirect_to root_path, success: '成功'
else
flash.now[:danger] = '失敗'
render :new
end
end
これでもしログインできたら『クライアントから正しくパラメータが送られていなかった』もしくは『送られてきたパラメータをコントローラ側でうまく扱えていなかった』ということになります。
②-2params
の中身を確認する
def create
p params
end
# 説明の便宜上色々省略してます
<ActionController::Parameters {"user_sessions"=>{"email"=>"example@example.com", "password"=>"12345678"}>
フォームに入力した値が格納されていれば一旦問題ないと言えます。それでもログインできない場合はコントローラ側でのパラメータの扱いが怪しそうです。
②-3params[:email]
という書き方があっているかを確認する
def create
p params[:email]
end
=> nil
もしこれでnilが出力されたらparams[:email]
という書き方がおかしいということになります。
つまり今回のケースではクライアントからサーバへのデータの送り方を変えるか(name=user_sessions[:email]
からname=email
に変えるか)、コントローラ側でparams
の扱い方を変えるか(params.dig(:user_sessions, :email)
のように変えるか)で解決できそうです。
繰り返しになりますが一気に色々とやろうとするとどこに原因があるかがわからなくなるので、必ず問題を細分化して考えるべきです。
###2. 入力と出力を意識していない
プログラムは全てインプットとアウトプットの組み合わせといっても過言ではありません。何を入力したら何が出力されるのかを意識することは非常に重要です。
例えば『自分が投稿したコメントかどうかを判定したい』というメソッドを作るとします。
その際いきなりロジックを考え始める人が初学者には非常に多いと感じました。
ロジック云々の前に考えるべきなのは
- 何を入力して
- どんな出力を期待するのか
という入出力です。
やり方は色々あるとは思いますが一例としてはこうでしょう。
- 何を入力して => 判定したいコメントのオブジェクトを入力する
- どんな出力を期待するのか => trueかfalse
メソッドとしてはこのようになるはずです。
def mine?(comment)
# ロジックは一旦置いといて
# trueかfalseを返せれば良い
end
**「コメントのオブジェクトを引数として受け取り、boolean型の値を返す関数」**ですね。
それができたらようやくロジックを考えられるようになります。
完成形の一例です。
def mine?(comment)
id == comment.user_id
end
このロジック自体はここでは重要ではありません。重要なのは入出力が決まって初めて実際のロジックを考えられるようになるということです。その逆は有り得ません。
余談ですがスペックを書くとこの辺りの力が鍛えられると思ってるので、初学者こそスペックを書いて欲しいです。
###3. ブラウザの開発者ツールを使わない
サーバログを見る人はちらほらいますがブラウザの開発者ツールは見ないという人がほとんどでした。
不具合が起きた際のヒントが何かしらあることが多いので開発者ツールは使うことをおすすめします。
特にJSが絡んでくるとサーバとクライアントのどちらに問題があるのかの見極めが重要になってくるので開発者ツールのコンソールは個人的にはよく見ます。500番台のエラーならすぐにサーバサイドに問題があることがわかります。
その他、ネットワークタブも個人的にはよく見ますね。
httpプロトコルの勉強にもなって一石二鳥です。
###4. ログを見てググらない/見ない
ログを見て断念する。もしくはそもそも見ない人が一定数います。エラーログは解決のためのヒントが書いてあるので必ず見てググってもらいたいです。
ただこれは難しい問題で、周辺知識がないとエラーログはどうしても読めないと自分は考えてます。
このエラーを見た時にある程度経験のある人は(実装上どこに問題があるかはどうあれ)根本の原因はわかると思います。
一方で知識がまだ少ない初学者にとっては undefined method user
は undefined method user
でしかありません。そこから何か得ることはできないでしょう。
『テレビが映らない』という問題に対して『テレビは電気で動いている』という知識がなければ『コンセントはちゃんと入っているかな?』という発想にはそもそも至らないんですね。
プログラミングはそういった周辺知識の積み重ねが重要で、先の例でいうと
- undefinedというのは変数や関数が定義されていないことを意味する
- methodというのは関数である
- 定義されていないというのはオブジェクトに当該変数や関数が書かれていない
という知識がないとどこに問題があるのか察しがつかないでしょう。
そういった知識は一朝一夕で身に付くものではないので地道に積み上げていくことが重要です。
モデル編
5. rails consoleを使って試そうとしない
『ユーザーが作成できない!助けてください!』と言われた時には『まずコンソールで作れるか確認してください』と伝えてます。
これも原因の切り分けの一環です。
ブラウザでぽちぽち操作してユーザーが作れないという場合、モデル・ビュー・コントローラ・ルーティングその他諸々が複合的に絡み合ってるので原因の切り分けがしづらくなります。
なので一旦rails consoleを使ってとりあえずDBに保存できるか、ということを試してもらってます。
User.create(email="example@example.com", password="12345678")
こんな風に書いてたとしたらそれは当然ユーザーも作れないですね。
Railsには色々なクエリインターフェースがあるので色々と試してみると良いと思います。
6. クラスとインスタンスのイメージを持てていない
User.name
や
user.find(params[:id])
と書く人がちらほらいましたが、クラスとインスタンスの違いをイメージだけでも持っていればこんなミスはしないはずです。
ざっくりとしたイメージはこうです。
クラスは設計図
インスタンスは実体
「人間」という設計図には具体的なメールアドレスなんて存在しえないですよね。なので『「たろう」さんのメールアドレス』や『「はなこ」さんのメールアドレス』とするのは正しいですが、『「人間」のメールアドレス』とするのは誤りということが感覚でおかしいなと気づくはずです。
イメージを持ち、違和感を感じる感覚を養うことが大事です。
ルーティング・コントローラ編
7. リクエストからレスポンスの流れの理解が曖昧
リンクを踏んでからブラウザに表示されるまでに一体何が行われているのかのイメージがついていない人ほど課題に行き詰まる傾向にあるような気がしています。
このフローを理解していないと実装してても腹落ちしないはずです。
8. RESTの理解が曖昧
カンペを見なくても答えられるくらいこの表が頭の中に入っていないとRailsでの開発は厳しいです。
初学者はこの表をベースに「この機能を実現するにはどういったURLにすれば良いのだろうか?」を考えて試行錯誤してそのURLを出力させることに注力すると良いと思います。
Webアプリは原則ユーザーの操作を起点に動くものなので何はともあれまずはビューです。詰まった時はlink_to
だろうがform_with
だろうが自分が期待するURLをなんとかして出力することを意識すると良いと思います。言っちゃえば最初の動作確認の段階ではlink_to
もform_with
も使わなくてもいいんです。コメントを作りたいのであれば先の表で言うと/comments
に対してPOST
すれば良いのでフォームのアクション属性に直接/comments
を書いても良いんですし、同様に、ログイン機能を作りたいのであれば/sessions
に対してPOST
すれば良いのでフォームに直接/sessions
を指定しちゃっていいんです。comments_path
やuser_sessions_path
など難しいことは一旦置いといていいんです。あとから書き直せばいいんです。
余談ですが、癖をつけると言う意味で初心者のうちはRESTにとことん忠実になった方が良いと考えてます。デフォルトで生成される7つのアクション以外を自前で作り始めるとカオスになりがちなので。
この辺りを参考にすると良いです。
DHHはどのようにRailsのコントローラを書くのか
DHH流のルーティングで得られるメリットと、取り入れる上でのポイント
9. Comment.find(params[:id])とやりがち
コメントの編集機能を実装するときにこのように書く人が多いです。
Comment.find(params[:id])
これでも動きますが、/comments/:id/edit
のid部分をユーザーに書き換えられたら他人のコメントも更新できてしまうという問題点があります。
なのでこちらの書き方の方がベターです。
current_user.comments.find(params[:id]
RailsBestPracticeにも書いてあります。
Use scope access
他にもRailsBestPracticeには為になることが書いてあるので読んでみると良いです。
ビュー編
10. コレクションをeachで回してパーシャルをレンダーしがち
@users.each do |user|
render 'user', user: user
end
ユーザーの一覧を表示する際にこのような書き方をする人が多いです。間違いではないですがもっとスッキリする書き方があるのでそちらを使おうという話です。
render @users
自分では検証していませんが、レンダリングコストも低くなっているそうです。
RailsBestPracticeにも書いてあります。
Simplify render in views
まとめ
開発全般編の話なんかはRailsに限らず他の言語でもいえる話ですね。というか原因の切り分けの話はプログラミング以外でも通ずる話ですよね。
iPhoneが充電できなかった時ってiPhoneが悪いのか充電器が悪いのかをまず調べるために、違うiPhoneを繋いでみたりしませんか?
プログラミングも結局はそういうことです。
とりあえず今回は前半ということで10個書きましたが、近いうち後半編であと10個書きます...!!