こんにちは、YOUTRUSTのやまでぃ(YOUTRUST/X)です。
今回は何書くの?
下記の項目について私見を述べます。
- 条件式の引数の並び順
- 早期リターン
- ネストの深さ
- 後置if
前提
重要なそもそも話ですが、細かいコーディングスタイルについてあれこれ気にするよりも、ちゃんと責務に応じて適切にクラスやメソッドのサイズをコンパクトに収めることを考えることの方が優先度高いと思っています。
コードがコンパクトになっているのであれば、どんなスタイルで書かれていても大した負荷なく読むことができます。
「細かいコーディングルール」 < 「クラスやメソッドのサイズ」
です。
(クラスの責務分割に関しては同意を得やすいが、コーディングルールに関しては主観が入り混じって統一しにくかったりもしますしね、これから細かいこと書きますが、細かいことは気にしない〜)
それでは早速参りましょう!
条件式の引数の並び順
# A if (length >= 10) # B if (10 <= length)
【私見】B派です。
理由は大小の方向が数直線と一致しており、直感的に理解できるからです。
複数の引数が&&
で連結されていても読みやすいです。
if 10 <= length && length <= 100 # →「10〜100ね」(脳負荷なしに即答できる) if length >= 10 && length <= 100 # →「10以上、100以下…10〜100ってことか」(一瞬考えてしまう)
早期リターン
【私見】状況によっては使わない方が良いと思っています。
ifの解像度を少し上げて、「分岐」「選択」「アサート」に分けて考えてみます。
- 分岐…1本の道から二手に分かれる二股道路のイメージ(右?左?)
- 選択…道は1本のまま何かをチョイスするイメージ(金の斧?銀の斧?)
- アサート…前提条件をチェックするイメージ(入場チケット見せて?)
この中で早期リターンは「アサート」のifに対して有効なものだと考えています。
class HogeNotification def initialize(from_user:) @from_user = from_user end def run to_users = fetch_target_users(user: @from_user, type: :friend_of_friend) to_users.each do |to_user| # 「分岐」のif…早期リターンしなくて良い if push_notifiable_to?(to_user) send_push_notification_to(to_user) end # まぁ現時点では↑で早期リターンしても良いが、 # 将来、下記のような通知処理が追加された場合に手直しが必要になってしまう。 # if email_notifiable_to?(to_user) # send_email_notification_to(to_user) # end end end private # 「選択」のif def fetch_target_users(user:, type:) # case文でも可 if type == :friend_candidate user.friend_candidates elsif type == :friend_of_friend user.friend_of_friends else user.friends end # 下記のように無理して早期リターンする必要はない # return user.friend_candidates if type == :friend_candidate # return user.friend_of_friends if type == :friend_of_friend # user.friends end # 「アサート」のif def push_notifiable_to?(user) # 条件が複雑になる時は特に早期リターンが有効 return false if !user.active? return false if user.disabled_push_notification?(:hoge) # return false if ... # return false if ... # return false if ... true end end
ネストを浅くする
【私見】状況によっては使わない方が良いと思っています。
# ネストを浅くした例 def hoge # a if user_result != 'SUCCESS' # b logger.debug('user failure') return end # c if permission_result != 'SUCCESS' # d logger.debug('permission failure') return end # f logger.debug('success') end
確かに早期リターンを用いることによってネストの深さが1に抑えられています。
ですが、私はこのコードに対して以下のように思ってしまいます。
user_result
は成功したがpermission_result
は失敗した場合の処理をどこに書けばよいかが瞬時に分からない(d)user_result
もpermission_result
も失敗した場合の処理を書く場所がない(bに書くの?)- return漏れたらバグる危険性
- ifの位置を逆にしたら挙動が変わる
これらのifは「分岐」と捉えられるので、ここでも敢えて早期リターンを使わずに記述してみます。
# 敢えてネストした例 def hoge if user_result == 'SUCCESS' if permission_result == 'SUCCESS' logger.debug('success') else logger.debug('permission failure') end else logger.debug('user failure') end end
いかがでしょうか。
確かにネストは2に深くなりましたが、先に上げた点が全て解決(迷うことがない、間違えにくい)されてはいないでしょうか。
後置if
【私見】処理部が長くなるような場合には使わない方が良いと思います。
# 後置ifが辛い例 def create result = HogeClass.run(...) return render 'errors', status: :bad_request, formats: :json if !result.success? head :ok end
私がこれを読んだときの脳内「最初にHogeClassを実行して、えっ?errors返すの!?400!?JSON!?あ、失敗時のみか…(びっくりした)」
…となります。
また、視線が左右に揺れて疲れたりもするし、もしかしたらifを見落としてしまうかもしれません。
下記のようにしてみます。
def create result = HogeClass.run(...) if result.success? head :ok else render 'errors', status: :bad_request, formats: :json end end
いかがでしょうか。
私がこれを読んだときの脳内「最初にHogeClassを実行して、成功時には200、失敗時にはエラーを返すのね(シンプル!)」
…となります。
終わりだよ
ここまで読んでいただきありがとうございました。
今回はいつもと色を変えて完全私見でアレコレ語らせていただきました。
繰り返しになりますがあくまで私見です!笑
「このルールに従えこらぁぁぁ」なんて警察業はしていません。(クラスやメソッドの粒度についてはちゃんとしているので)
最後に、弊社ではエンジニアの方を募集しておりますので、ご興味を持たれた方はカジュアル面談しましょう〜!
以上です。
また次回の記事をお楽しみに〜!またね〜🖐️