🎃

10万行・50名のプロダクト、100万行・50名のプロダクトのコードをどうやって改善するか

2023/07/02に公開

あるサービスがローンチから5年経過し、10万行のソースコード、開発メンバー50名の体制でメンテする、という話をツイッターで見かけました。ソースコードには、神クラスが含まれるとか、含まれないとか。

ソースコードの桁を間違えちゃったのかな、と思うのですが、10万行・100万行の場合について、それぞれ私だったらどういう戦略で修正するか、を考えてみます。この戦略は、自分がメンバークラスでもリーダークラスでも有効です。

前提:恵まれたビジネス

このサービスには、50名でメンテする価値があるという事になります。一般論として、5年経過したサービスを50名体制で開発をするとしたら、それは損切りをクリアして50名でペイすると判断をされて開発しているということなので、そのサービスが生み出す売上はそこそこの額であると考えられます。
世の中には開発だけで成立するサービスもあると思いますが、神クラスが出来ているとか設計知識が乏しいといった仮定をするとしたら、おそらくは開発中心というよりは営業がある程度必要となるサービスなのでしょう。そこで、仮に同数の営業が存在すると仮定したら、100名程度の従業員を支えられるサービスでなければなりません。そうすると、まあ売上10億は固いんじゃないかな...と思います。

そうすると、5年で売上10億、またはそれと同等以上の見込みがあると判断されたという事なので、 このビジネスはかなり良いビジネス です。創業5年で売上10億立てられる企業はほんの一握りの存在です。
そのような目線では、ビジネス領域にはすごく恵まれているサービスである、という仮説が成り立ちます。

もちろん、ビジネス的にそんなに良くないという可能性もあるのですが、もしそうだとしたら、5年かけてビジネスとして成立してもいないサービスを50人で開発している(しかも神クラスを含む品質の10万行)という事になるので、単純にビジネスとして救いがなく、そのビジネスを捨てるべきです。

そのような意味で、仮にその環境で頑張る価値があるとすれば、このビジネスは恵まれていると思って差し支えないでしょう。

10万行というコード量がどういった量か

彼を知り己を知れば百戦して殆からず。ここで改めて10万行というコードの量について考えます。私が6年前に書き始めて、今もいろんな人と書いているメインプロダクトのコードの量が約55万行です。

※実績が紫で、赤は参考の線です

RDBのテーブルの数が206、全期間の平均的な開発者の人数はフル勤務換算で 4〜5人程度 です。

我々のコードはPythonでかなり短く簡潔に書いているつもりですが(たとえばC#で同程度のものを書くと2〜3倍ぐらいになるんじゃないかなという気がします)、それでも10万行というのは1年で達成できる程度の量です。

ちなみに、Mastodonという有名なSocial Network Serverがありますが、こちらはRoR等で作られていて、7年半で約52万行でした。

以下のコードでmigrateからcreate_tableの数をカウントすると、93個でした。

cat db/migrate/*.rb | grep create_table | wc -l

必ずしもコードの量から実現している機能の単純計算ができる訳ではないですが、ある程度効率的に記述されているとして、それがwebアプリだったとすれば20〜40程度のテーブル数のwebアプリ。小規模開発でも1年で十分に達成される程度の量、というところだと思います。
そのコードを50名で保守して、またそれによるサービスを元に10億の売上が立つとしたら、すごいビジネスですね。
このように、具体的な数値で"彼我"を捉えなおすことは有効です。もし普段コードの量を意識していなければ、10万行というのはとても大きな量に思えますが、実際にはそうでもないという事がわかります。そのコードが把握しやすいか否かは別として、10万行で実現できる機能性の総量は限られており、十分に処理できる対象です。

10万行・50名のプロダクトのコードをどうやって改善するか

ではこのプロダクトのコードをどんな戦略で改善していくか。
10万行であれば、長く見て半年かけてコードを解析すれば、大半のコードが何をやっているかは理解できるようになるでしょう。その状態で、さらに長く見て数ヶ月かければ、重要な部分はリファクタ/再構築できるはずです。既に50名で開発しているプロジェクトで、1名がそれに注力したところで、その工数が致命的な問題になるようなことはもはや無いでしょう。沢山の人が薄く作業をするようなOSSであれば別でしょうが、普通のプロダクト10万行に50名がついている時点で生産性が悪すぎるので、その状態での1名の作業など微々たるものです。

というような感覚をベースに、いくつかの具体的な対応を考えてみます。

そもそも問題があるのかを考える

大前提として、現在ビジネスとして成立しているならば、問題がない可能性もゼロではありません。今後の事業成長等を考えると一般的にはよくない状況のようにも見えますが、仮にその会社のビジネスがいくつか存在していて、このビジネスについては今と同じ規模感を維持できればよいのであれば、そもそも改善する必要が無い可能性があります。
10万行50名は桁違いではと思うぐらいに効率が悪いのは事実ですが、それが作業効率のよくない人のための慈善事業、つまり給料を出すための仕事であれば特に問題はありません。そもそも問題があるのかどうかについて、例えば事業計画を開発的に実現できるのかといった観点で経営陣とも認識を合わせた方がよいでしょう。事業計画を実現し続けられるのであれば問題はなく、事業計画を実現できなくなるからこそ問題があります。

※例えば、セキュリティが不足していても事業計画を実現できるといった主張はあり得ますが、それはリスクとして評価すれば「事故によって事業計画を実現できない」というように事業計画の実現の問題に帰着できます。これはバグの出やすさやコンプライアンスについても同様です。もしいま「困っている」としたら、それはノルマ達成や具体的な品質低下などの観点で困っているはずで、困っている事が事業計画の実現の障害になるはずです。まず、そのことについて認識を合わせます。

一人でちゃっちゃと片付けてしまう

10万行ぐらいであれば、上述の通り一人でちゃっちゃとやれそう...ですが、以下、それ以外の対応について述べていきます。

すごい事業であることを開発者視点からも力説して、みんなを盛り上げる

まず、今のサービスのコードはどうあれ、開発しているものがビジネス的に素晴らしい事を話して、みんなのやる気を引き出します。
もちろん全員というわけにはいかないでしょうが、ある程度は賛同してくれる人がいるでしょう。その人達を中心に、改善プロジェクトを進められる体制を作っていきます。これは権限がないと難しいと思う人もいるかもしれませんが、メンバークラスでもボトムアップでやれます。ただし、本当にすごい事業であるという事を数値で説明できる必要はあるので、客観的に外から見える数値で事業のすごさを説明できるようにします。

※どうしても事業そのものを褒める事が難しい場合、事業そのものではなくて、経営陣がすごいと力説する可能性もあります。仮に現時点でサービスの売上がなかったとしても、少なくとも50名の開発者のチームを組織できるだけの予算はあるという事なので、売上なしでそれが出来るとしたら資金調達などの才能が凄まじいです。

この課題対応が楽しいものだという認識を持つ

すごいビジネスに対して、現状のシステムの在り方がダメダメだとすれば、改善できたらすごく楽しいと思います。
この現状であれば、50名の"生産性"としては10倍を目指せる状態だと思います。それが実現できたとしたら、これまで50名の誰にもできなかった・やらなかった事を自分手動でできたという事になるので、自分の価値を肩書や人間関係ではなくて実績で語る事ができ、自信につながります。このノウハウは、おそらく様々な場面で生きる種類のものであると思うので、将来的に食いっぱぐれる事も少なくなるでしょう。
逆に万一失敗したとしても、50名でできなかった事が51名でできなかった事になるだけです。ある意味では気楽にやれます。
(もちろん、無責任にやるということではありません)

この課題対応が重要だという認識を持つ

一方で、いまこの課題に取り組むという事は、それが事業計画の実現をいつかどこかで困難にするからでした。そのため、この課題対応によって得られるものはとても重要で、それがなければいつか事業が成立しなくなります。もしそうでなければ、やる意味がありません。

この課題を楽しむこと・重要と思うことについても、人を巻き込んで説明して盛り上げていくようにします。
人に説明すると自分自身の認識が強くなりますし、仲間を増やしていくことができます。
メンバークラスでは、やれる事が限られていて難しい...と思われがちですが、実際にやることはメンバークラスでもリーダークラスでも変わらず、まずは課題が本質的なものであるという認識を共有した上で、自分のマインドセットを再設定して課題を重要かつ楽しめるものにしたうえで、人に全力で訴えて巻き込んでいく、という事になります。

メンバーを小さい組織に分けて活かす

50名もいれば、その中には相対的に運用が得意、コードを速く書くのが得意、テストをするのが得意、といった差があるはずです。そうした好みや技術によって、メンバーの作業を分けていきます。私が組織するとしたら、ざっくり新規開発、運用、テスト作成、検証環境構築(その他)ぐらいに分割するかなと思いました。

  • 機能開発が完全に止まると経営・営業等から怒られる可能性があるので、機能開発は止めない
  • システムの運用を、ある程度得意・好きな人で整理して固める
  • 改修で壊れていない事を保証するためにテストを準備していく
  • 自由にテストや検証ができるように検証環境、さらには新しい本番環境を準備していく

明示的に組織を作ることができない場合も、有志を募って仕事の20%の時間を使うなどでなんとか時間を捻出してもらいます。元々50名もいるのに10万行の規模であれば、さすがにできるはず...

このとき、より丁寧に進める必要があるならば、現状の各メンバーが開発業務として具体的にどんな事をやっているかを調べることから始めます。例えば、極論で50名のうち45名が実は運用に相当する業務をやっていた、などとなっても全然不思議ではないと思いますが、その運用にも必要なものと不要なものがあって、という事を見極めながら進められるからです。ただ、自分に与えられた権限や信頼の有無などによっては、これは非効率な事もあるので、一部の有志で小規模にすすめていくといった形式であれば、必ずしも現在の開発業務の分析を細かく行う必要はないと思います。(概要ぐらいは把握しておいた方がよいでしょうが)

直接的に組織構造を変えていくには権限が必要ですが、リーダーを説得できればリーダーにそれをやってもらえばよく、また草の根活動的に有志で展開していくならばメンバーでもやれます。

コードを読んでいてわからないところは、関係するユーザーに聞いて整理する

仕様書などが存在しない場合は、仕様についてはユーザーの方が詳しいという場面もしばしば発生します。そこで、臆する事なくユーザーに仕様を確認していきます。
特に開発だけの組織でない場合には、営業やユーザーを巻き込んでいく事が重要です。これまで開発速度が遅くてユーザーにも何らかの不満があるはずで、その辺の改善に向かって進んでいるという印象を与えながら積極的に巻き込むようにします。コードの状況からすれば、おそらく非開発の人も現状に不満があるはずです。非開発の人からどのように見えているかは、例えば次のような記事が参考になると思います。

https://zenn.dev/neinc_tech/articles/13ca4cb3e9310f

Q, さくらいさんは、なぜエンジニアになったんですか?
A, 開発部に不満があるのに、何もできない自分が悔しかったからです。

運用手順をまとめて、運用フローから業務フローをつくり、仕様の大枠を整理する

現時点で運用をしている人が社内に存在している人なので、その人達から断片的な情報を収集し、全体の運用手順としてまとめていきます。それによって運用フローができあがるので、それを元に一般的なユーザーの動きも含む業務フローを作成します。
これによって、仕様の大枠、流れを整理することができます(そもそも、フローというのは流れですね)。
これも可能ならユーザーの力を借りていきましょう。50名いれば、こうしたオペレーションが得意なタイプの調整型エンジニア(?)みたいな人もいるのではないかと思います。
フローやユースケースを作って、ユーザーに確認することで理解も深まっていくでしょう。
ただし、ここで重要なのは、ユーザーに確認をしてもユーザーは常に全網羅的な観点で回答をくれるとは限らないことです。つまり、今まさにやっている業務であっても、雰囲気があっていればOKと回答する人もいますし、逆にすごく細かいところで例外的なフローがあったりする事を全て列挙する人もいるでしょう。この時に重要なのは、いままとめているフローが決して100%ではなく、せいぜい80%の内容だと意識をすることです。(これについては後述します。)

ちなみに、このような活動をやっていると、自然とシステムへの要望を聞き出すこともできます。この要望は非常に重要で、私の経験では本質的な事が多いです。あるいは、その人の知らない手順で今のシステムでもうまくやれるといった場合も存在して、そういった方法を伝えることで運用が改善します。もちろん、そうした改善のような事を考えている人ばかりではないのですが、開発者がコードに問題を見出すのと同程度には現在の運用に問題を見出している人も存在するはずで、そのような人を積極的に巻き込んでgive&takeでやっていきます。こうした地道な活動で信頼が生まれて、改善活動が広がっていきます。

検証環境をつくる

特殊な環境で環境が一つしかない、本番でしか動かないといった場合には、外部システムのモックを作成するなどしながら、本番に近い環境をつくっていきます。
外部システムのモックは、外部システムの応答と同等の内容を固定で返却するようなものでもいいので、仮に技術力が低いメンバーであってもきちんと手順を踏めば作成することができます。仕様が多少複雑であったとしても、要件が明確なので答えのある振りやすい仕事です。50名もいれば、そうした仕事を依頼できる人がいるはずなので、モックを作っていきます。

環境構築などは、メンバーレベルではなかなか許可が降りない場合もありますが、物によってはローカルで動かしたり、空のインスタンスを作ってもらってそこで動かしたり、といったことはできると思うので、可能な範囲でやっていきます。私自身、一メンバーであった頃には、社内のちょっとしたサーバーを間借りするなどして環境を作ったりしていました。

テストをつくる

テストをつくるのは難しいのですが、とりあえず今の仕様を担保するための仕組みを作っていきます。
100%ではなくても、うっかり既存プログラムの動作を壊してしまう事から守ってくれる場合があります。
運用フローや業務フローを参考に、シナリオ的なテストを作ることにも挑戦しましょう。
これも、とりあえず今のコードで動くテストという事なので、正解は存在しています。面倒で時間がかかる場合や、テストとしての実装が難しい事もあるでしょうが、テストの作り方を整理すれば振れる仕事になっていくと思います。(ただ、テストを書くのは大変かも...)

コードを"良くデザインされた状態"にする

ここまでの一連の対応を踏まえて、必要であれば実際にコードを"良くデザインされた状態"にしていきます。
実際には、コードを読み進める時点で明確なデッドロジックや無意味な分岐などを見つけて直すこともありますが、なるべく対応を進められる状況を整えてから整理していきます。
どういったコードを目指すかは諸説ありますが、とりあえずチームのメンバーの多くが平易に読める書き方が良いかなと思います。
一般論としては、

  • 適切な変数・関数の名前をつける
  • なるべく一つの関数の行数を少なくする
  • 概念を整理して共通化する(異なる概念を共通化しない)
  • 同じ機能性を実現する場合は同じ書き方をする(定形パターン化)
  • なるべく不要な定義を増やさない
  • なるべく宣言的にする
  • なるべく全体像を把握しやすくする

などといった事があると思います。その他参考にするとしたら、A Philosophy of Software Designなどがよいと思います。
なお、 こうした技術は仕様を理解した上で適用する という事が前提です。彼を知り己を知れば百戦して殆からず、という言葉は汎用的であらゆるレイヤーに適用でき、ここでもまずは仕様と自分の実力や目指すものを知った上で対応を進める事が必要になります。

もっとも、次に述べる80%本番環境のように、やむを得ず仕様を理解していなくても簡略化したものを作る事はあり、またそれによって理解が進むのでとりあえず技術を適用するという場面もあります。ただ、それは事前にそのような準備をしていたり、あるいは最終的に捨てるコードとして理解をするためと割り切って手元でやるといった条件付きのことで、一般に推奨できることではありません。

新しい本番環境をつくる(不要な複雑な機能を切り捨てる)

ドメイン等の厳しい制約がなければ、新しい本番環境を作る事を検討します。
現状が10万行ぐらいで、かつ開発者が50名いるのであれば、月1000行コードを書ける人10人で1年かけて別のシュッとしたシステムを作ることができるでしょう。よほど天才的な人物の書いたコードでなければ、10万行で実現されているアプリケーションの本質的な内容は限られています。
この別のシステムは、現在の本番環境の動作を100%カバーできていない可能性はありますが、運用フローなどを元にすれば80%程度であればすぐにカバーできるでしょう。それを利用して、その80%の機能で済むユーザーにはさっさと新しいシステムに移行してもらう、という方法があります。

特に、複雑だがごく一部のユーザーしか使っていない、といった機能を洗い出して、ユーザーと相談をしながらもそれを躊躇なく廃止していくことで、無理やり複雑度を下げていきます。その一つの方法として、簡潔な新本番を作成してそちらに移行していき、機能が足りないユーザーは一旦現行を使ってもらいつつも、その機能を新本番に実装するか明示的に切り捨てるかを検討していく、といった感じです。

このときの重要なポイントですが、新しいシステムは機能が少なくても 安定動作 するようにします。旧本番よりも機能が少ないけれども移行する価値があるというのは、安定性などの非機能要件で優れているから、以外には中々無いでしょう。セキュリティは重要ですがユーザーからは見えにくく、ユーザーから見えやすいものとしては安定性があるので、安定性を押すようにします。「機能は少ないが、確実に動く」状態から、「機能が多く、かつ確実に動く」を目指していきます。

ちなみに、ビジネスの内容にもよりますが、例えばユーザー数の爆発的な増加が見込まれるとすれば、それに耐えられる構成をこのタイミングでついでに入れる、といった事も考えられます。可用性や機密性といった要素については、現状の本番環境の動作とは別で純粋なシステム要件として検討します。これも事業計画、あるいはざっくりどんな風にシステムを伸ばしていきたいか、という事をベースに考えます。
このとき、たとえば負荷試験などは50名に1名ぐらいは興味がある人がいると思うので、そのような人をアサインしていきます。

ここまでの判断はさすがに個人ではできないので、自分がメンバーであればリーダーを積極的に動かしていくようにします。ただ、この判断を実行できるかどうかについては開発以外との関係値も大きく影響します。ユーザーへの質問や運用手順まとめなどを通して、

100万行・50名のプロダクトのコードをどうやって改善するか

さて、では100万行の場合にはどうでしょうか。
定性的には、ちゃっちゃとリファクタしてしまう作戦以外は全て機能するように思えます。
ただ、100万行ということは、例えばwebアプリであれば関係するテーブルは数百、場合によっては1000を超えてもおかしくないので、新しい本番環境を作る際にシュッとしたコードで作り直すのが相当大きな仕事になってしまいます。まあ、作るのに5年かけてきたシステムなので、それは当然だとは思いますが。累積投資額としては数億は下らず、ある程度の企業の基幹システムに相当するでしょう。

※人数が時期に比例して増えたとして平均25人、仮で一人あたり400万円/(年・人)で計算をしたとしても400万円/(年・人)×5年×25人 = 5億円です。仮に50人に増えたのがごく最近で、それまでずっと小規模だったとして平均10人でも2億円です。

システムの寿命を定義する

そのような場合、まず システムの寿命を定義する ことから始めるのが良いかなと思います。つまり、今のコードをベースにしたシステムをいつまで使い続ける想定で作業を進めるか。これが1年、3年、5年、10年、30年、いずれかによって対応は大きく変わります。
もちろん、ここで決めた寿命が今後絶対的であるという事ではなくて、この寿命を最適化の条件として直近の計画を立てるということです。これによって、システムの作り直しのタイミングが定義されるので、現システムでやることと新システムでやることが整理されます。その上で、現システムでやることを効率的に達成するために、必要ならリファクタリングを進めていくという事になります。
(こうして実際にやってみた結果、思ったより出来が良かったので延命するとか、社会的な事情や法律が変わったので早く潰すとか、事業として利益が出なかったので早く潰すとか、そういった評価・戦略再定義は都度行うようにします。)

何が本質的な課題なのかを整理して捉える

このような規模の場合、何か一つの事を改善することで劇的に開発効率が改善する、といった事は極めて稀です。一開発者としては、目の前のコードを改修しにくいという課題を抱えているかもしれませんが、その課題の要因は複合的な場合があります。
仕様がわからないなら仕様を整理する、運用が複雑なら運用を整理して適宜システム化する、検証用の環境がなければ検証用の環境をつくる、(自動)テストがなければ作る、不要な複雑な機能があるならばまず複雑な機能を切り捨てられる状況を作った上で切り捨てていく。
読みにくいコードを読みやすいコードにすることは、それ自体何もしないよりは確実にした方がよいことですが、それがプロダクトの改善という意味で一番の手段とは限らないですし、それが安全にできる環境を整えていく事も重要です。
一開発者としては、読みにくいコードで開発作業をするのは、散らかった環境で工事をする事や不衛生な環境で料理する事と同じような印象を受け、単純に改善したいことがあると思います。ただ、それが50名の開発チームの抱える問題を解決するとは限らないので、それと並行してより大きな設計のレベルでも物事を考えるとよいのかな、と思いました。

まとめ

  • 10万行で50名の開発者を支えられているとしたら、ビジネスモデルがすごい
  • そもそもコードを直すことが事業計画に影響する課題だという認識を共有しよう
  • コード以外の準備をしっかりしてからコードを直そう
  • 周りの人をどんどん巻き込んでいこう

おしまい。

※この記事はQiitaに書いたものを加筆修正したものです。

Discussion