スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Rubyのコードの書き方

こんにちは、ujihisa といいます。現在スタディサプリのProduct Platform の Software engineerとしての仕事を行っています。先月から社内留学で、開発支援チームからコーチングチームに一時的に移籍して、そちらの仕事をやっています。

Rubyのコードの書き方

まずは全体的なコーディングスタイルなどについて。

現在社内で統一的に使用しているコーディングスタイルの標準化などはとくに行われておりません。各チームごとに、それぞれのチームが開発運用している (= ownershipをもっている) コンポーネントそれぞれに対して個別のRuboCop設定などはあります。また、複数のチームの境界にある、歴史的な共有サービスに関しては (そう、まだあります、そしてこれは今後もかなり長いあいだ付き合っていくことになるでしょう) testdouble/standard というTest Double社が独自に設けたRuboCop設定を採用しているぐらいで、それに加えて独自のものは設けていません。基本的にみんな極端に読みにくいコードを自発的に書くことはないだろうし、いまのところコードレビューでとくにコードのスタイルについての指摘で議論が白熱し続けてしまうなどということはないので、なくても別にいいだろうという感じです。

余談ながら、前述の共有コンポーネントすべてに対して、RubocopでRubyコードのスタイルを強制せず、バグだけ検出 するRuboCopの設定 (quipper-rubocop-config-for-monolith) があります。こちらの方はStyleやLayoutに関する設定は一切行っていません。

ここまでが全般的な内容でした。以下、個別のトピックについて雑多に記していきます。なお、チームによって多様性があり、以下の内容はすべてのチームで全面的に採用しているものではなく、少なくともいくつかのチームではこのようにやっているという例の紹介のようなものです。

publicである必要がないものはprivateにする

「このメソッドは他所から使われることを想定していない」「この定数は他所から直接参照されることを想定していない」ということが明確になるというメリットがあります。また大胆なリファクタリングが比較的安心して行えるようになります。

一般に、メソッドをprivateにするのは普及している気がしますが、定数をprivateにするのはあまり普及していない気がします。

class C
  X = ...
  private_constant :X

  ...
end
  • クラス内定数のうち、外部から参照する必要がないものはその場で private_constant をかけます
    • これ、 private_constant X = ... みたいに記述できたら良いんですが、残念ながら現在のRubyの仕様上できないのでちょっぴり不便です。
    • nested classでも同様です。こちらはendのあとにくるので正直とても読みにくくなってしまいますが... ruby module M class C ... end private_constant :C end
  • 基本的にprivateなものにはunit testは不要なんですが、様々な諸事情でどうしてもunit testが欲しく実用性があるみたいな気持ちになるシーンがあります。そのときは以下のような感じで内側に書いてます ruby module M RSpec.describe(C, '...') do ... end end

コードを読むときに記憶しなければいけない「状態」を減らす (目が飛ばないようにする)

以下、社内でよくみかけるパターンのクラス定義です。(スタイルを見せたいだけなのでCやfといった名前や実装に意味はありません。)

class C
  def f
    ...
  end

  def self.g
    ...
  end

  private def h
    ...
  end

  private_class_method def self.i
    ...
  end
end

一方、以前はこのような記述が多かったです。

class C
  def f
    ...
  end

  class << self
    def g
      ...
    end

    private

    def i
      ...
    end
  end

  private

  def h
    ...
  end
end
  • クラスメソッド定義は、def self.fのような形の方が、その行を見るだけで「クラスメソッドの定義だ」ということがわかりやすいです。よって、クラスのインスタンスメソッドを定義するための class << self のイディオムを避けます
    • とくにnested classのとき、インデントの深さでいま自分で読んでいる箇所がどっちなのか判断するのはかなり難しいので、この記述法が有用です
  • private はstatefulな private 単体の記法ではなく、statelessな private :symbol の記法を好みます
    • その行を見るだけでそのメソッドがpublicかprivateなのか判断できることにより、人間の目に優しいです
    • なおprotectedは現状ユースケースがなく存在していません 1

一般に、GitHubのpull requestのコードレビューをするときに、diffの前後の文脈を押し広げなくともスコープなどがわかる記法を好みます。 もっともこの記法の採用はまだ比較的新しいので、既存のコードの多くはいまも後者の書き方のままです。

RSpecまわり

RSpecでUnit testing2をするときに心がけている3つのこと:

  • 対象となる機能の想定している正常系の振る舞いと、とくに関心のある異常系の振る舞いがテストされている
  • 対象となる部位以外の内部構造の変更に影響を受けない
  • テストが落ちたとき、その原因を特定するための作業が邪魔にならない

逆に、避けている3つのこと:

  • subjectis_expected.toなどを用いて、自然な英語による仕様書(spec)のような見た目にすることを他よりも優先
    • とくにsubjectは有効なケースはほとんどなく、積極的に避けるべきです
  • ほんの少しでもコードが重複しそうならletとshared_examplesを用いて共通化する
  • letsubjectのスコープをなるべく広くし、同じletがさまざまなテストから呼ばれるようにする

RSpecはテストコードを書くためのDSLであって英語ではないので無理に英語に寄せる必要はないと考えています。

Myron MarstonさんとIan Deesさんの文書jnchitoさんの日本語翻訳記事 に強く影響を受けています。これに付け加えるならば、自明ならば it の引数すら省略します。

RSpec.describe C, '#f' do
  it do
    c = C.new
    expect(c.f).to eq(123)
  end
end

補足

コードの書き方で大事なのは統一感や一貫性自体ではなく人間にとっての読みやすさです。読みやすさを実現するために統一感や一貫性が要求される場合があるという主従関係です。そして読みやすさは具体的には既存のバグの見つけやすさや、新しいバグが生まれるのを抑制するのにどのくらい助けになるかという点です。いろいろ試してみて、やっぱりこっちの方が読みやすいやとなったら手のひらをくるりと変えて前述の方針をがらりと変える可能性はめいっぱいありそうです。

image

画像がないとちょっぴり寂しいので、アイキャッチ画像としていまさっき食べた夕食のベーコンエッグと自作パンの撮りたての写真を掲載します。ベーコン、いい感じのカリカリ具合に仕上げることに成功しました。以前はベーコン専用のフライパンで油を落としつつ焼いていたのですが、最近はオーブンでじっくりと焼いたあと、キッチンペーパーに脂を吸わせてカリカリに仕上げるのがお気に入りです。この場合Ractorのように同時並行的に別の作業を容易に進めることができ、大変便利です。


文責: ujihisa


  1. と思ってgrepしてみたらprivateのつもりでprotectedを使っているコードを少し発見したので、このあとひっそりと一部修正しておきました
  2. RSpecがTest Driven DevelopmentではなくBehaviour Driven Developmentのためのツールであることはわかった上での1つのコンポーネント単位ごとのテストをやりたいときのケースをゆるく指しています