新規プロジェクトがいつまでも良いコードであり続けるための、僕なりの5つのルール
はじめまして。FiNCでWeb Application Engineerをしている清水です。サーバーサイドからフロントエンドまで、アプリケーション全般の設計と実装を担当しています。
ウェルネスサーベイというプロジェクトをここ一年半やってきました。過去の新規プロジェクトをゼロからやることは何度かありましたが、やはりやる度にこうしておけばよかったという反省・学びがあるので一度まとめようと思います。ちなみに言語はRubyでRailsを使って開発しています。
新規プロジェクトの初期の一年で起こること
新規プロジェクトの初期の一年は、エンジニアの視点で見れば度々来る 仕様変更 との戦いでした。 実際、今回の我々のプロジェクトにかぎらず、一定の仮説の上で始まるどんな新規サービスも、一年後にまったく同じ仕様というとは逆に稀で、うまくいってもいかなくても、さまざまな仕様追加・変更がともなうことになるでしょう。その中から今後や、次のプロジェクトでは気をつけようと思ったことを5つ書きます。
- モデルの役割をシンプルに保つ
- 影響範囲の小さい選択肢を選ぶ
- すべてに正しい名前を付けて、そして、正しい名前であることを維持する
- CSSこそはじめに明確なルールを
- そのアプリケーションの目的、背景、仕組み、将来を深く理解する
1.モデルの役割をシンプルに保つ
一年を通して、一番多かった失敗がこれでした。 モデルの役割を曖昧にしたり、いくつもあとから追加してしまうことで依存関係が増え、考えることが複雑になり、modelもfatになっていくケース。
たとえば既存のテーブルにちょっと情報を追加したい、というときに
- 既存テーブルにカラムを追加する
- 新しくテーブルを作って既存のテーブルと関係付ける
という選択肢がでてくると思いますが、スケジュールや、手軽さを理由についつい1を選んでしまうことが多いのではないでしょうか。 もちろんYAGNIの考え方の元に、先を考えすぎることもよくないとしてとりあえず追加&あとでリファクタという考えも一つですが、経験上よく考えれば2.を選ぶのがベターな場合も意外と多いです。
たとえばB向けサービスで、Userテーブルがあり、Userがどの企業(Company)に所属するか保持したいとなったとします。その場合
# 1.既存テーブルにカラムを追加する
Users
- id
- email
- password
- company_id # add!
Companies
- id
- name
とする方法が1の選択肢で、
# 2.新しくテーブルを作って既存のテーブルと関係付ける
Users # Userのアカウント情報
- id
- email
- password
UsersCompannies # UserとCompanyの中間テーブル
- user_id
- company_id
Companyies # 登録されている企業
- id
- name
と企業とUserの関係性は別テーブルに持たせるのが2の選択肢になります。
どちらでもやりたいことは実現できますし、なら手軽にできる1.でやろうよ、と思ってしまいがちなんです。
ですがたとえばここで
「上手くいってるので、一般向けにもシステムを開放したい」
となったらどうでしょう?企業情報に紐付かないUserが存在しうるとなるので、前者だと User.company_id
がnilの場合が発生し出します。今までcompany_idがある前提で動いていたメソッドやvalidationを修正するひつようが出てきたり、なにより「Userアカウントの認証に関するデータ」と「ユーザーがどの企業に属するかのデータ」の両方をあつかうことが役割になってしまうため、Userモデルの関心範囲が曖昧に&複雑になってしまいます。
またもっと困るのは
「複数企業に所属するケースが出てきた」
などの場合でしょう。それだともはや1.の選択肢では対応できないため抜本的に設計を変更する必要が出てきます。2.であれば、変更はあれど「ユーザーがどの企業に属するかのデータ」を扱ってるテーブルがその変更に対応し、User側はhas_oneをhas_manyに変える程度ですみます。
最初はなんとかなるのですが、プロジェクトが成長していくにつれ、徐々にその依存関係がきつくなり、技術的負債になりがちです。
実際のモデルの役割というのは、プロジェクトごとケースバイケースで、なかなか普遍的にこうしろということは言い難いのですが、「Userの基本情報をあつかう」「Userの所属情報をあつかう」「Userのプラン情報をあつかう」など、アプリケーションの成長につれて増えていく情報とその振る舞いをいかに切り分け、model一つの関心事を小さく保つかという視点が、長期にわたってサービスコードを書いていく上では重要に思います。
見方を変えればこれは「愚直に正規化をする」とも言えるかもしれません。特にアプリケーションで要なモデルを非正規化した部分はその後影響がでることが多いので注意が必要です。
2.影響範囲の小さい方を選ぶ
Railsをはじめ、アプリケーションフレームワークには便利な機能が沢山あります。 それをどんどん使って効率よく開発していきたいのですが、1年もやると白黒はっきりつけにくい、 トレードオフな選択を迫られることもあると思います。その場合、それぞれの依存関係、影響範囲を最小化する選択肢を選んでみよう、という話です。2つの例を紹介していきます。
Callback(依存関係)を簡単に増やさない
Active Recordのコールバックはなにかと便利で、インスタンスの生成、バリデーション、insert/updateなど、モデルの変化をフックに手軽に処理を追加出来ます。
Callbackはまさにmodelの依存関係を追加する行為で、初期はいいのですが、気をつけないとどんどん保守が大変になりりがちです。絶対必要な処理だと思ってCallbackに書いても、それが外れることは本当によくあります。
最初は、「しょうがない、このケースはこのcallbackをはずそう」と思って、 if:
で特定の条件だけ実装、なんてことでも乗り切れますが、徐々にそれも増えていって思わぬバグを生みやすくなります。
対応策としては、本当にそのcallbackの内容がそのモデルにとって常に必要か考え、yesならCallbackを使うが、特定の業務ロジックのときに一緒に処理したいだけ、といった処理はまずCallbackを使わずcotrollerやServiceなどユースケースごとに並列で処理させるようにしています。
安易に基底クラスに処理を追加しない
これもcallbackと同様のあるあるなケースで、railsでいえば継承元の基底クラスにbefore/after_actionなどで処理を追加してしまうケースです。
DRYに書くためにやるべきことなのですが、最初は全てのケースで必要だったが、一部例外が出た、という場合に困ることも多いです。 影響範囲の全てのテストを修正してQAもすれば修正はできますが、なるべく影響範囲は最小限にして必要な場所に絞ってまずは処理を書いたほうが無難でしょう。 つまるところDRYにいつすべきかというよくある話になりますが、だいたい3箇所以上で利用され、DRYにする必要がでたときに、はじめて基底クラスを修正して共通化する、というほうが変更コストは小さくなります。
要するに
これらに共通していることは、簡単にいえば 「いかに依存関係を少なくして、考えるべきことを減らすか」 ということだと思います。迷ったら常に影響範囲の少ない方法で。そうすることで「人が読んで素早く理解できる」「一部の変更が全体を壊さない」コードになり、結果的に変化に強いコードが書ける、ともいえます。
3.すべてに正しい名前を付けて、そして、正しい名前であることを維持する
これはどちらかというと教訓というよりやってよかったという話で、この言葉自体は きれいなソースコードを書くために必要な、たったひとつの単純な事 この記事からお借りしている言葉で、数年前によんで以来、自分の大事な指針になっています。
Rubyの父であるMatzもこの記事で
私の設計上の座右の銘は「名前重要」です。あらゆる機能をデザインする時に、私はその名前にもっともこだわります。プログラマとしてのキャリアの中で、適切な名前をつけることができた機能は成功し、そうでない機能については後で後悔することが多かったように思うからです。
と言っています。
ほぼこれらの記事でその理由は網羅されていますが、なにか要件が変わるごとにも「正しい名前であることを維持する」ことが、変化に強く、保守性の高さを保つ秘訣であると思います。特に一度崩れた命名はずっと悪影響を与え続けるので、最初の一年でいかに命名を大事にする文化を作るか大事なポイントになります。
4.CSSこそはじめに明確なルールを
CSSは、言語自体の制約がすくないためだれでも手軽に書けますが、制約がない分保守がわりと大変です。 フロントエンドコーダーがいるっていう場合はいいですが、小さなチームではサーバーサイドの人が片手間でってケースも 多いのではないでしょうか。今回のプロジェクトでも
ルールなし(開始当初)
↓
バラバラな命名で読めない&迷う(1ヶ月後)
↓
BEMを導入
↓
命名は整ってきたが再利用生がいまいち上がらない&保守は相変わらず大変(4ヶ月後)
↓
SMACSS+BEMのElementを抜いてよりシンプルな命名ルールで運用
↓
コーディングルール定着←今ココ
という流れできました。今ではCSSは社内共通のCoding Ruleの上で書かれていて、誰でも読みやすくキャッチアップもしやすい形になってきています。
BEMは BEMによるフロントエンドの設計 基本概念とルール | CodeGridや公式のドキュメントにあるように
- 長期的なメンテナンスが出来る設計&開発スピードをあげる
- チームのスケーラビリティ
- コードの再利用生
という目的で作られていて、実際にかなりワークしました。
しかしどれが再利用性のあるコンポーネントとして書かれているのかわかりにくく、また命名も
<div class="user">
<div class="user__name">
<div>
<img class="user__img"><div>
と定石通り収まる場合はよいですが、たいていはもう少しネストが増えて
<div class="user">
<div class="user__name">
<img class="user__name__icon"><div>
<div>
と孫要素が入ってきたり、しまいには
<div class="user">
<div class="user__name">
<img class="user__name__icon user__name__icon--selected user__nam__icon--man"><div>
<div>
のようにどんどん冗長になっていきました。、実例はもっと複雑でリニューアルや大幅な変更をしたいときにかなり困りました。もちろんこれはBEM自体が悪いというより、BEMのルールだけではチームでうまくCSSを管理しきれなかった、というのが原因でした。
そこでもうすこししっかりとしたコーディング規約が必要となり、OOCSS, MCSS, FLOCSS, SMACSSなどを複数検討した結果、SMACSSベースのコーディング規約を使って書くようになりました。
(このあたりは Amazon.co.jp: Web制作者のためのCSS設計の教科書 モダンWeb開発に欠かせない「修正しやすいCSS」の設計手法: 谷 拓樹 がとても良くまとまっていて、勉強になりました。サーバーサイドの方でもCSSが多少かければすぐにキャッチアップできるのでおすすめです。)
コーディング規約の詳細はまた別途公開・解説できればいいと思うので割愛しますが、明確なルールができたことで、読みやすく、命名に迷う時間が減り、なによりみながキャッチアップしやすくなり、結果的に実装速度もあがり、変更にも素早く対応できるようになったのは確かです。
5.そのアプリケーションの目的、背景、仕組み、将来を深く理解する
これが個人的にとても大事だなと思う点で、よい設計をして、よいコードを書き続けるためにはコード以外のことを深く知る必要があるということです。
これを知っているかどうかでかなりアプリケーションの設計の柔軟度が変わってきます。 たとえばECサイトをつくるとき、ソフトウェアを設計する人も「どういう流れで商品が購入され、売上として計上されるのか」「在庫はどう管理されるのか」「会計はどういう仕組みなのか」を理解しないと、適切に抽象化しモデルに落とすことは難しいでしょう。
逆にいえば、今のビジネスがどういうものかを抽象度高く正しく捉えられれば、どういった変更の可能性があり、どういう制約があるのか事前に理解して設計を進められます。 例えばアンケートのシステムで考えると、最初は一択問題だけかもしれませんが、そのうち回答方式が変わるかもしれません。ですがアンケートのパターンというのはある程度決まっていて、一択、複数選択、自由記入(数値、テキスト)くらいで、あとはその延長でUIが違う程度です。そうなれば予め変更を予測したうえでの設計が可能になるわけです。
開発メンバー全員が全てを知っておく必要はないかもしれませんが、全体を設計する立場にある場合は重要になってくるでしょう。概念を正しくつかむことで、システムの面からサービスの歪みや分かりにくさが見つかり、それを仕様やインターフェースに反映することで、プロダクトをよりシンプルにシャープにすることも多々有ります。
まとめ
よりよいサービスを作るためには、作る側もスピーディーで、柔軟な体制が求められます。そんな中でもコードの品質も保ちながら、開発者自体も健全に開発を続けるための教訓を幾つかまとめてみました。これからプロジェクトを始める方、今サービスを運用してる方のなにか参考になれば幸いです。
最後になりましたが、FiNCでは、人々をより健康に、本当に価値あるサービスを一緒につくっていけるエンジニアを募集しています。誰もが直面する健康課題を一緒に解決したい、マイクロサービスに興味がある、とにかくruby/railsが好きだ!など、ご興味ある方はぜひ一度オフィスに遊びに来てください。