こんにちは、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
の記法を好みます
一般に、GitHubのpull requestのコードレビューをするときに、diffの前後の文脈を押し広げなくともスコープなどがわかる記法を好みます。 もっともこの記法の採用はまだ比較的新しいので、既存のコードの多くはいまも後者の書き方のままです。
RSpecまわり
RSpecでUnit testing2をするときに心がけている3つのこと:
- 対象となる機能の想定している正常系の振る舞いと、とくに関心のある異常系の振る舞いがテストされている
- 対象となる部位以外の内部構造の変更に影響を受けない
- テストが落ちたとき、その原因を特定するための作業が邪魔にならない
逆に、避けている3つのこと:
subject
やis_expected.to
などを用いて、自然な英語による仕様書(spec)のような見た目にすることを他よりも優先- とくに
subject
は有効なケースはほとんどなく、積極的に避けるべきです
- とくに
- ほんの少しでもコードが重複しそうならletとshared_examplesを用いて共通化する
let
やsubject
のスコープをなるべく広くし、同じ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
補足
コードの書き方で大事なのは統一感や一貫性自体ではなく人間にとっての読みやすさです。読みやすさを実現するために統一感や一貫性が要求される場合があるという主従関係です。そして読みやすさは具体的には既存のバグの見つけやすさや、新しいバグが生まれるのを抑制するのにどのくらい助けになるかという点です。いろいろ試してみて、やっぱりこっちの方が読みやすいやとなったら手のひらをくるりと変えて前述の方針をがらりと変える可能性はめいっぱいありそうです。
画像がないとちょっぴり寂しいので、アイキャッチ画像としていまさっき食べた夕食のベーコンエッグと自作パンの撮りたての写真を掲載します。ベーコン、いい感じのカリカリ具合に仕上げることに成功しました。以前はベーコン専用のフライパンで油を落としつつ焼いていたのですが、最近はオーブンでじっくりと焼いたあと、キッチンペーパーに脂を吸わせてカリカリに仕上げるのがお気に入りです。この場合Ractorのように同時並行的に別の作業を容易に進めることができ、大変便利です。
文責: ujihisa