2020年1月付けで入社した社長室 エンジニアの芝田と申します。 社長室ではkakariという、かかりつけ薬局化を支援するサービスをやっており、そちらでサーバーサイドエンジニアとして働いています。
エンジニアとしてのキャリアはメドピアで2社目で、まだまだ勉強中の身です。 今回はメドピアでの開発を始めて、開発環境のいいところや実装のtipsを一部ご紹介したいと思います。
開発環境のいいところ
CIでRSpecやRubocopをはじめとする複数のLint👮が通っていないと原則マージできない
Rubyは自由な記法ができるメリットの反面で、記法のばらつきが比較的出がちです。そこは、Lintによってある程度カバーすることが可能です。
また、ClassLength
・LineLength
・AbcSize
等によって、ファイルの肥大・コードの複雑度合いを知ることができます。
kakariではClassLength 100行以上はマスターデータではない限り許可していないので、読むのを諦めたくなるファイルは無いです。
Dangerを使い、他のファイルと合わせてこう書いてほしいというレビュー漏れを無くしている
例えば、プルリクエストを出した際に、XXX.created_at.strftime('%Y/%m/%d %H:%M')
をチーム内ではl(XXX.created_at, format: :datetime_with_slash)
と書いてほしい時に、Dangerで設定しておくと自動で警告を出してくれます。
API~フロントエンド間で開発前にOpenAPIを使って、送るパラメータと期待するレスポンスを決定している
yamlを書くだけでSwagger Editor上やChromeの拡張でリッチなUIのAPI仕様書を自動生成してくれたり、committeeを使って、OpenAPIで定義したレスポンスをRspecで自動チェックしてくれたりします💪
kakariのメンバーが以前作成した資料がありますので、ご興味ある方は併せて確認お願いします。
可能な限りパフォーマンスの良い書き方を求められる。
bulletでテスト時にN+1を検知したり、無駄な繰り返し処理をできるだけ減らす書き方を求められます。
例えば、対象となる患者を探すロジックを書きたい時、account.patients.not_deleted.select do |patient|
だと論理削除されていない患者全体を取得し、患者数分繰り返し回してしまい患者数が多いほどパフォーマンスが悪くなります。
この場合、where
やfind
メソッドを使って一気に対象患者を検索するようにすることが望ましいです。
また、関連付けされた値をキャッシュしたかったり、テーブル同士をjoinやキャッシュする必要がある場合は、eager_load
やpreload
メソッドを適切に使うようにしています。
テストをしっかり書いている
入社する前の私はどちらかというとテストは書かず、ブラウザで動作確認をしていました。しかし、テストがない環境では、バージョンアップやリファクタリングが辛かったり、動作が要件通りになっているだけ(行が長かったり引数が多い)のメソッドを書きがちです。
特にレビューや仕様追加によって、コードを変更した際、常に要件通りのチェックを毎回するのか?となり、テストを書かなかった工数分が後で確認工数増加やデグレとして降りかかってきます。
社内全体でテストがしっかり書く習慣となっているため、kakariでは最新のRails6系やRuby 2.7系を使っており、他の依存ライブラリも常にアップデートされています。また、リファクタリング系のissueにはテストが既に書かれているため、書いた本人以外でも着手しやすいようになっています。
実装のtips
プレーンなRubyファイルで書かれたPOROでロジックをこまめに切り出す
私はまとまったロジックが必要になった時、モデル側にインスタンスメソッドやクラスメソッドを書いて実装しがちでした。しかし、ロジックがモデルに集中すると、関心事が入り乱れてテストし辛いコードになりがちです。
kakariではFoo::Updater
のようなクラスを切り、クラスメソッドでcallすることが多いです。-er(~する人)が呼ばれる(call)という名前だと、メソッド名に悩まされにくく、読み手側の頭にも入ってきやすいかと思います。
class Foo::Updater def self.call(params:) new(params).call end def initialize(params) @params = params end def call update end private attr_reader :params def update # 何かの処理 end end
POROに関しては弊社の技術顧問である@willnetさんが書いた記事もありますので、ご興味ある方は併せて確認お願いします。
繰り返し参照されるメソッドは変数に格納する
2度目も参照されるロジックの場合、結果を変数に格納しています。初回はインスタンス変数がnil
になるので、右側の式が実行されます。
2度目以降の実行では変数が使われるので、2度目以降に引数を変えて異なる結果を取得したい場合は意図しない挙動になるので注意してください。
def foo_object @foo_object ||= Foo::Creator.call end
列挙型で使いたいカラムはデフォルト値をDB側で定義するのではなく、モデル側で定義する
enumerizeを使う場合、モデルにデフォルト値を持つことが可能です。メリットとして、項目が増えたときにデフォルトの値を変えたい際、migrateファイルを発行せずに済みます。またtextで指定できるので、何の値をデフォルトにしているかが明確です。
class CreateFooBars < ActiveRecord::Migration[6.0] def change create_table :foo_bars do |t| t.integer :age_code, default: 4 t.timestamps end end end
ではなく、下記のようにモデル側で定義する。
class Foo < ApplicationRecord extend Enumerize enumerize :age_code, in: { all: 0, older_forty: 1, older_fifty: 2, older_sixty: 3, older_sixty_five: 4, older_seventy: 5, older_seventy_five: 6, older_eighty: 7 }, default: :older_sixty_five end
名前の重複のない関連付けの参照をする
例えばモデルとしてはfoo_answer
、foo_question
で示したいが、関連付けする際には、デフォルトでfoo_answer.foo_question
となってfoo
が冗長です。
Railsのデフォルトのinverse_of
から外れてfoo_answer.question
やfoo_question.answers
としたい時はinverse_of
を明示的に設定します。
class FooQuestion < ApplicationRecord has_many :answers, class_name: 'FooAnswer', dependent: :destroy, foreign_key: :question_id, inverse_of: :question end
class FooAnswer < ApplicationRecord belongs_to :question, class_name: 'FooQuestion', inverse_of: :answers end
Viewでしか使わない整形用のメソッドはDecoratorに切り出す
erbやHamlで実装している箇所はDecoratorを使ってviewにロジックを直接書かないようにしています
module FooDecorator def full_name "#{last_name} #{first_name}" end end
おわりに
私は今までの考え方として、スピードを優先するときはある程度汚い状態のコードがリリースされるのは仕方がないと思っていました。しかし、メドピアではビジネスのスピード感を犠牲にせず、そしてマンパワーにも頼らず、便利なライブラリで賢く仕組み化して、コードの品質を落とさない取り組みを実践しているという部分に触れられたことが、入社して良かった点の1つです。なので、このような開発環境で成長したい人にとっては、メドピアへの入社というのは良い選択肢の一つだと感じました。
今回は他の記事と比べて1つの事項を深掘りした内容ではありませんが、「お、使ってみようかな」・「メドピアの開発環境のことをもっと知りたいな」と思っていただける内容が1つでもあれば幸いです!読んでいただき、ありがとうございました。
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら
https://medpeer.co.jp/recruit/workplace/development.html