どうも。今は保守をメインの業務にしている muryoimpl です。
今回は、私がこうなっていると読みやすくて嬉しいなぁというコードについて、頑張って言語化していきたいと思います。
明確な指針を出しにくいものなので、リーダブルコードやリファクタリング Ruby エディションには載っていないかもしれないですが、今から書いていくことが意識されているコードを私は読みやすいと思うので、こういう観点もあるのね、くらいの気持ちでお付き合いください。
メソッドを見れば流れがわかる、短期記憶にやさしいコード
私は局所を見るよりも、まず全体の流れがわかったうえで詳細を読むほうが理解が進むタイプの人です。 メソッドの全体の流れがわかると、流れからどの部分で何が行われるかだいたい想像することができるので、詳細を見に行ったときに予めイメージを持っておけるし、イメージがわけば考える範囲を限定できるため、100%手探りよりも効率がよいと感じるからです。
流れがわかると、「考える範囲を限定できる」というのが重要です。そのメソッドのもつ役割と振る舞いにだけ集中してそのメソッドを読めばよいからです。読み進めていくうちに記憶しておくべきことを少なくできると、頭の中がクリアな状態を保てるため、理解も進みます。
ただし、読むべきメソッドが限定できたとしても、そのメソッドが極端に長かったり、複数ファイルにまたがって深いところまで追っていかないと最終的な結果が何をしているかわからなくなっていたりする場合、脳の短期記憶がオーバーフローしてしまい、よくわからなくなってしまいます。こういう場合、戻ることも難しくなって読みなおし、ってなることがあるんですよね。
例えば、他のファイルにあるメソッドへの参照がない10行のメソッドと5,000行のメソッドでは読み進める中で記憶しておくことは 5,000行のメソッドのほうがきっと多くなります。同じ 10行のメソッドでも、同一ファイル内で完結しているメソッドと複数のファイルにまたがって定義されているメソッドをあれこれ組み合わせた10行のメソッドでは後者のほうが記憶しておくことは多いでしょう。
短期記憶を酷使しない脳が疲れないコードが読みやすいやさしいコードだと思うのです。
流れがわかるコード
原則として、コードは上から下に順に処理されるようになっています。上から下に読み進めていくというのは、時系列に処理を追うことができるので読みやすい、というのは同意いただけるのではないかと思います。まず、上から下に読んでいけるようなコードの構成にしましょう。
メソッドや変数が流れを理解できる程度にわかる名前がついていると、更に読みやすくなります。変数やメソッド名は処理の目次に等しいと私は思っているので、これは大事です。概要が名前からわかると、その部分については読む/読まないの判断をつけやすいため無駄に調べる必要がなくなり、読む人にとっては非常やさしくなります。名前についてはリーダブルコードの「第Ⅰ部 表面上の改善」でいろいろと触れられているのでそちらを参照してください。
以下のコードは bin/rails g scaffold User
した controller の create メソッドのコードを一部加工、抽出したものです。user_params メソッドを読むのにちょっと下のほうにジャンプして戻ってこなければいけませんが、基本的にファイルの上から下に読み進めていけます。
class UsersController < ApplicationController、 def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end private def user_params params.fetch(:user, {:first_name, :last_name}) end end
私は複数のオブジェクトが相互作用するようなメソッドを作成するときは、いつも controller のメソッドを思い浮かべます。このように流れがわかるようになっているか?それぞれの変数、メソッドは目次的な役割を果たしているか?と自問しながら実装しています。
主となるオブジェクトに対する操作がわかるように書かれている
定義しようとしているメソッドの中で、主となるオブジェクトに対する操作が見えるように記述レベルを揃えて書くことを意識すると、処理の流れが見えるコードになります。
先ほどの UsersController#create
の例では、User モデルのインスタンスが主とするオブジェクトと設定されています。このメソッドの中で扱おうとしている User モデルのインスタンスに対する操作の様子が見えるように処理が書かれているため、流れがわかりやすくなっています。
「記述レベルを合わせる」とは、オブジェクトの生成から操作終了までを一貫してここのメソッド内に書くということです。open や close のような対になっている処理については、必ず同じメソッド内に記述します。open はあるのにclose がないと、本来セットで現れるべきものが現れないため不安を煽りますし、close を探す必要が出てきてしまいます。
場合によっては別のオブジェクト(例: Service クラスや Form Object など) に渡して処理をお願いすることもあるでしょうが、その場合は主とするオブジェクトに対して何をしているのかが明確にわかる名前にする必要があります。先に書いたように、変数やメソッドは目次に相当するので名前は重要です。必ず、主とするオブジェクトに対する操作がわかるような名前をつけましょう。
これらを3分クッキングに例えるならば、料理をする際はまな板のある作業台(今定義しようとしているメソッド) の上で材料を扱い、手順をみせます。手順のうち時間のかかるものについては「こちらに予め準備しておいたものがあります」とアシスタント(Serviceクラスや Form Object) に依頼して作ったとしても、必ずできあがったそれを作業台の上で扱うことで全体の流れと操作がわかりやすくなるようになっています。これと同じイメージです。 大きな処理を作る場合は、アシスタントの処理もアシスタント側で作業台の上で操作するようにすると、更にわかりやすくなると思います。
まとめ
今回は、私の読みやすいと思うコードということで、全体の流れがわかるコード、短期記憶を酷使しないコードが私にとっては読みやすいコードです、ということを表明し、どうすればそうなるのかについて少しだけ触れてみました。
構成だけでなく読みやすさ/読みにくさを考えてコードを書くのは大変な行為なのですが、ファイルやコードの構成上優れていたとしても読みにくいコードは理解しにくいものです。理解がしにくいと手を入れるモチベーションが低下してしまうので、大変ですが将来修正する自分のためだと思ってやっていきましょう。