ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

技術的負債に立ち向かう ①実装編

f:id:ar_tama:20210419101030p:plain

こんにちは! 白銀荘でセルフロウリュが可能になったと聞いて居ても立っても居られない@ar_tamaです。
今回は、開発者なら一度は直面したことのある「コードの技術的負債化」への考察を、実装編・ドキュメント編の2編に分けてお届けします。

我々が産み出しているものは資産? 負債?

みなさん、コード書いてますかー?(はーい!)
エンジニアの職務に就いている我々は、おそらくなんらかのコードを書いて「資産」を作り出し、それにより対価を得て暮らしています。動いたコードが経済活動の一端を担っているのであれば、それは間違いなく「資産」であると言えるでしょう。
一方で、その資産は現在だけでなく未来にも影響を及ぼします。現在は相応の評価がなされている資産でも、その価値の維持に投資以上のコスト*1がかかるのであれば、それは「負債」とも呼び表されます。
せっかく長く価値を発揮する「資産」とするべく作ったものが、あっという間に賞味期限が切れ、あまつさえ「負債」などと揶揄されてしまうなんて、なんとも悲しいことではないでしょうか。

負債になるコードとは

では、負債になりやすい資産とは、いったいどのようなものでしょうか? 代表的な原因を、Railsアプリケーションを例に示します。

「どこから呼ばれているのかわからないコード」がたくさんある

Railsでは特にconcernやModelのscopeなんかの形で目にすることが多いでしょうか。共通処理をまとめるとコードもスッキリしますしダブルメンテも防げますが、クラスの処理の一覧性が落ち可読性が下がります。要はトレードオフなのですが、開発の初期段階ではコードベースが小さいために後者が見落とされるケースが多いと感じています。
またRubyのオープンクラスは小回りがきいて便利な反面、行き過ぎた「黒魔術」的コードにより可読性が下がったり、「code grepをしたがヒットしなかったので消したらリリース後に思わぬ箇所で実行時エラーになってしまった」という事故を誘発したりもします。

Fat Model, Fat Controller

ActiveRecordのModel objectはその万能性からほとんどすべてがFat Modelへと成長を遂げます。バリデーションもコールバックもDB操作も、なんなら外部ファイルの操作も一緒くたに定義されうるのですから、Fatになるのは当然の帰結といえるでしょう。
最近はActiveModelのModelやValidationを継承させて別クラスに切り出すテクニックも見られるようになりましたが、依然としてModel classに求められる責務は過剰なままです。

どちらも原因は「責務が適切に分離されていない」と抽象化できます。責務の混在や重複は、(正しい使い方か分からなかったり、使われているかどうかが怪しかったりする)大量のメソッド・アクセサを生み、コードの可読性を大きく下げることとなります。また既存機能に変更を加えるたびにバタフライエフェクト的な壊れ方をしていないかをチェックする必要が生じ、それによりメンテナンスコストも変更への心理的負荷も増加します。ぱっと見ただけでは分からない、巨大な「なんでも屋」クラスが跋扈しているアプリケーションは、我々をまるでジェンガを触っているような気分にさせることでしょう。

上記に該当しないようなシンプルなコードが突然負債として襲いかかってくるようなケースもありますが、これについては次回の「ドキュメント編」にて詳説します。

負債への立ち向かい方・実装編

それでは、上記のような状況に直面してしまった場合、どのようにアプローチしていくのがよいでしょうか。「責務の分離」に焦点を絞り、同じくRailsアプリケーションを例に取って考察します。

負債ができてしまった(と思った)ら

回り道かもしれませんが、はじめに心構えの話をします。
最近あなたが「これは負債だ」と感じたコードはどんなものだったでしょうか?「なんか読みにくいコードだな」「ロジックを理解するのに時間がかかるな」「もっと簡単に書けるのにな」といった感想を抱いたのみでは、残念ながら それは負債ではない ことが多いでしょう*2
先ほど「現存する価値の維持に投資以上のコストがかかるケース」を負債と定義しました。当たり前ですが、この負債の返済自体にもコストはかかります。コストをかける(投資する)ことはビジネス判断の一種ですので、常にトレードオフの対象があるという認識をチームで共有できるとスムーズです。
それでもこれは負債である、返済するべきだ、とあなたが感じている場合、そのポイントをできるだけシャープにしてまとめておくと、計画も立てやすく、また返済後の評価もしやすいのでおすすめです。以下に例を示します。

  • 機能ごとに評価する
    • 機能ごとのチケット消化にかかるポイント数が、ある時点と現在とでN倍になったのか
    • → 個別の事象のみを見た場合に、頻繁に手が入らない箇所であれば無視できる場合がある
  • 影響範囲を調べる
    • 自分のみが観測しているのか、周りもそうなのか
    • → 広く顕在化していない場合、評価を遅延できる場合がある
  • タイムラインごとの投資コストを判断する
    • 未来に起こりうる事業イベントと現在のコードがコンフリクトするのはいつか、解消にどの程度かかるのか
    • → 短期的には無視できる場合がある
  • ステークホルダーの増減を考慮する
    • 1年以内に何人採用する予定か
    • → 採用の計画がないなら力技でなんとかできる場合がある

負債の返済方法

責務の分離には「水平方向(レイヤーの分離)」と「垂直方向(機能ごとの分離)」の2つのアプローチが考えられます。Fat ControllerやFat Modelにお悩みの場合は、MVCが様々な責務を持ちすぎているということですので、水平方向の分離を初手として検討します。一方で、機能同士の絡み合いによりコードが肥大化しているという場合は垂直方向の分離から検討するとよいでしょう。
組織やコードベースのボリュームにもよりますが、どちらもまずはリファクタリングに手を出したい気持ちを抑え、新規実装にかこつけて実践することでコードをガイドライン化し、徐々に既存のコードへと適用していくと、手戻りのボリュームや影響範囲を小さく進めることが可能です。

水平方向の分離を検討する場合、今ならShopifyのUpgrow*3の導入がよいと考えています。ガイドにも記載がありましたが、とにかくまずは「Model(ActiveRecord)層にDB操作以外のことをやらせない」ことが第一歩です。その上で、バリデーションの分離(バリデータ層の新設)、コールバックの分離(ビジネスロジックへ吸収させる)、DB操作の分離(リポジトリ層の新設・CQRSの実現)を順に行っていくのがよいでしょう。

次点で、Controllerからプラットフォーム起因のコードとビジネスロジックとを分離することを検討します。垂直方向への分離に重きを置いている場合はこちらを優先して行うべきでしょう。 View Templateを利用した昔ながらのRailsアプリケーションから、Web Frontendを切り出してマルチプラットフォーム化したい、などといった場合にも有効です。
またビジネスロジックはドメイン層として独立させるなど、エッセンスとしてDDDを援用することでスムーズに分離が行えます。過剰な適用は目的と手段が入れ替わるリスクがあるためご注意ください*4。このあたりの塩梅の見極めには「モノリスからマイクロサービスへ」が参考になります。
併せて、独立可能な機能*5を別アプリケーションに分離することも検討したいですね。

耳タコ感はありますが、SOLID(特に単一責任の原則)やデメテルの法則、「命じろ、尋ねるな」なんかはこれらの分離を進める上で強固な拠り所になりますので、チームで事に当たる場合には強く意識して進めることをおすすめします。以下のように方針を掲げるのもよいでしょう。

  • 1つのクラスやメソッドには1つ以上のことをさせないようにしよう
  • 日本語で処理を説明したときに読点が登場するなら、そこで処理を分けよう

これらが徹底されることで、コードの可読性が向上し、「変更に閉じ拡張に開いた」設計がなされ、負債が生まれにくく価値が最大化されるという理想の状態に一歩近づくことができると考えています。

さて、翻ってロコガイドはというと、まさにこの水平・垂直方向の分離に向けて一歩を踏み出したところで、Developer Experience・プロダクト価値双方の向上という二兎を狙って日々奮闘しています。
次回は「ドキュメント編」と題しまして、コードに依らない部分で生じる負債の原因と、その立ち向かい方について考察します。
さいごに、こういった試行錯誤に興味がある方はぜひWantedlyTwitterでご連絡ください😉

*1:これは金銭的なものだけでなく、開発者生産性への影響も含まれます

*2:わかりみは深いのですが…

*3:残念ながら2021/04/19時点で再公開のアナウンスはありませんが

*4:“過剰な適用”を試してみましたが、Railsアプリケーションでの完全なDDDの実現は、システム負荷・組織への負荷双方の観点からおすすめできないという結論に至りました。しかしながら、垂直・水平方向の分離の道筋がつかないことには、巨大なモノリスアプリケーションを別プラットフォームへ一足飛びに式年遷宮させることは不可能だとも考えています。組織とコードベースに合ったちょうどよい塩梅を探し当てるのが、テックリードなりアーキテクトなりの腕の見せ所だと感じています。

*5:例えば事業ドメインが増える可能性が出てきた場合には、認証基盤は切り出しておいたほうがよいかもしれません