「MedBeer -Rails開発での技術的負債との付き合い方」での発表資料です
Rails Good Parts, BadParts@willnet
View Slide
自己紹介» 前島真一 aka @willnet or @netwillnet» ginza.rb から来ました» メドピアさんで技術顧問しています» https://github.com/willnet» https://twitter.com/netwillnet» https://blog.willnet.in
技術顧問として主に負債を減らしたり、負債の増加を防ぐために頑張っています!
技術顧問について詳しく知りたい方はこちらをどうぞ» Rails Developers Meetup で喋った» https://speakerdeck.com/willnet/ji-shu-gu-wen-toiudong-kifang» ブログ書いた» https://blog.willnet.in/entry/2018/04/09/101808
今日のテーマは技術的負債!
負債へのアプローチは2つ
できてしまった負債を減らす⬇
新規開発で負債を増やさない⬆!
今日は負債を増やさない話をします
負債の増加を防ぐにはどうしたらよいか
Railsのレールに乗る
レールに乗る、とは?» みんなよく言ってるけどふわっとした概念» レールについてきちんと説明している文章がないように見える
Railsが提供しているべんりな機能を活用する = レールに乗るここではこういう定義とします
Railsのべんりな機能» たくさんある» こういうメソッドないかな?と思って調べると大抵定義済み
べんりな機能を知らずにスクラッチで実装して微妙な感じになるケースがよくある!
べんりな機能の具体例» あまり知られていない&&知ってると便利なもの» 複数のお手伝い先で何回も言ってるやつ
has_many
みんな知ってる
定義することで使えるようになる機能がたくさんある
has_manyで使えるようになるものたちcollectioncollection<<(object, ...)collection.delete(object, ...)collection.destroy(object, ...)collection=(objects)collection_singular_idscollection_singular_ids=(ids)collection.clear
has_manyで使えるようになるものたちcollection.empty?collection.sizecollection.find(...)collection.where(...)collection.exists?(...)collection.build(attributes = {}, ...)collection.create(attributes = {})collection.create!(attributes = {})collection.reload
全種類知ってますか?
collection=(objects),collection_singular_ids=(ids)» 既存のcollectionをいい感じに置き換える» has_many through の場合は中間テーブルを置き換える
合わせ技でつかえるview helper» collection_check_boxesこれらをスクラッチで書いてしまう人めっちゃ多い(当社比)
コード例ビールの銘柄にタグ付けをするコードを考えてみます
modelはこんな感じclass Beer < ApplicationRecordhas_many :taggingshas_many :tags, through: :taggingsend
素直に書くと<% Tag.all.each do |tag| %><%= form.label tag.name do %><%= check_box_tag 'beer[tag][id][]', tag.id,beer.tags.find { |t| tag.id == t.id } %><%= tag.name %><% end %><% end %>
素直に書くとdef update@beer = Beer.find(params[:id])@beer.attributes = beer_paramsoriginal_taggings = @beer.taggingstaggings_ids = params[:beer][:tag][:id]delete_taggings = original_taggings.reject { |bt| taggings_ids.include?(bt.tag_id.to_s) }delete_taggings.each(&:destroy)add_tags = taggings_ids.reject { |id| original_taggings.map(&:tag_id).include?(id.to_i) }add_tags.each do |tag_id|@beer.taggings.build(tag_id: tag_id)endif @beer.saveredirect_to @beer, notice: 'Beer was successfully updated.'elserender :editendendprivatedef beer_paramsparams.require(:beer).permit(:name)end
一瞬では読めないですね…!
既存のタグと入力値との差分を見て、必要なものだけinsert, deleteしている
読みづらいしバグも入り込みやすそう
便利メソッドたちを使うと<%= form.collection_check_boxes :tag_ids,Tag.all, :id, :name %>
便利メソッドたちを使うとdef update@beer = Beer.find(params[:id])if @beer.update(beer_params)redirect_to @beer, notice: 'Beer was successfully updated.'elserender :editendendprivatedef beer_paramsparams.require(:beer).permit(:name, tag_ids: [])end
普通に書けるbeer.tag_ids = params[:beer][:tag_id]» has_many :tagsでtag_ids=メソッドが生えている» いい感じに差分を見て中間テーブルをinsert,deleteしてくれる
これ、初めて知った人✋
どうやってべんりな機能を知るのか» 公式のドキュメントやRailsガイドを読みましょう» もしくは強い人にレビューやペアプロしてもらいましょう
ここまでの話をまとめると
Railsが提供するべんりな機能を使えば負債を作らずに開発できる!
なんですけど…
Railsが提供している機能が全部無条件に便利というわけではない» 安易に使うとやけどする機能もある» ググるといろいろでてくるはず» callback» default_scope» nested attributes
gemも似たように、安易に採用すべきではないものがある» devise» simple_form» activeadmin» etc
この手のやつ、どう取り扱うべきか» チーム開発のときは特に慎重になったほうが良い» 禁止にしておいたほうが無難なことも多い» 利用シーンやチームの状況によって使えることもあるので、できれば吟味して決めたい
例えばwebpacker» 最近よくdisられている» しかしフロントエンド専任がおらず、片手間でjsを書く&&とりあえずes6使いたい、というケースだと大変便利» フロントエンド専任がいて、webpackの設定をちゃんとしたい、というケースでは必要ない
例えばdevise» 昔からよくdisられている» devise wayから外れるとつらい» 管理画面だけ認証が必要、などdevise wayで問題ないならサッと導入できて便利
要注意なものは» なぜ注意が必要なのか» どう避けるか» どういうときなら使ってもよいのかを把握してまとめておくと良い
例えばhas_manyのcollection<<は要注意class Userhas_many :postsendこれはイマイチな書き方user.posts << Post.new(title: 'hello world!')
なぜかcollection<<は、レシーバのオブジェクトがDBに保存済みか否かで挙動が異なる(クエリが発行されたり発行されなかったりする)
どう避けるかuser.posts.build(title: 'hello world!')user.posts.create(title: 'hello world!')のように書いたほうが可読性が高く、事故りにくい
どういうときなら使ってもよいのかcollection.build や collection.createが適切でない、かつチームメンバー全員が挙動を理解していればギリギリOKかとtag = Tag.create(name: 'Ϗʔϧ')Beer.each { |beer| beer.tags << tag }
(こうも書けるのであんまり例が良くない)tag = Tag.create(name: 'Ϗʔϧ')Beer.each { |beer| beer.taggings.create!(tag: tag) }
このように» なぜ注意が必要なのか» どう避けるか» どういうときなら使ってもよいのかを把握してチーム内で共有したい
負債を作らないためには、負債になりそうな要素の対応法をみんなが理解している必要がある
少なくとも、レビューで負債になりそうな要素を止められるようにしておきたい
負債を作らないイコール社内教育を頑張る
メドピアでは» 社内読書会» ペアプロ» ふりかえり会などしています
ところで、さっきの例は簡単すぎ
現実ではもっと判断に悩むケースが多い
判断に悩む例: コールバックをどう回避するとよいか?» フォームオブジェクト?» サービスクラス?» その他↑方針を決めたとして、具体的にどうやって作るといいの?
なるべく具体的に「こうだ!」と道を示さないと、それぞれ思い思いの実装になってそれが負債につながる
しかし選択するのが難しい
そこで clean-rails.orgですよ
可読性の高いRailsのコードを議論するコミュニティ
どのように実装するとよいか判断に悩むときに相談できる
皆様からの投稿お待ちしています!
まとめ» 負債を作らないためには、べんりな機能、要注意な機能を知ることが大事» 要注意なものについて、チーム全体で理解できるように詳細を詰めて周知する» 勉強と教育をがんばりましょう
判断に悩むものは一緒に勉強していきましょう