「カラムが足りない」――最高であり最悪でもあったコードベース
(jimmyhmiller.github.io)"merchants2 テーブルですか? はい、merchants のカラムが足りなかったので merchants2 を作りました。"
- プログラミングを始めたばかりの頃、人々がプログラミングでお金を稼いでいることを知らなかった
- 最初のソフトウェアの仕事場で多くを学んだが、そこのコードベースは最悪であり最高でもあった
データベースは永遠に生き残る
- レガシーシステムにおいて、データベースは単なるデータ保存場所以上の役割を果たす。システム全体の制約を定め、すべてのコードが交わる地点でもある
- SQL Server にはテーブルの列数制限がある。当時は 1024、現在は 4096 だ。Merchants テーブルの列が足りなくなったため、Merchants2 テーブルが作られた(500 列以上を含む)
- Merchants と Merchants2 はシステムの中核だった。あらゆるものが Merchants につながっていた。ほかに正規化されたテーブルもあったが、それらも Merchants と外部キー関係を持っていた
SequenceKey
- SequenceKey は、1 つの列と 1 つの値しか持たない単純なテーブルだった
- これは ID 生成に使われていた。SQL Server が自動増分 ID をサポートしていなかったために作られたものだと思われる
- すべてのストアドプロシージャが SequenceKey からキーを取得して増やし、それを複数テーブルの ID として使っていた。暗黙的な join の役割も果たしていた
Calendar
- Calendar は手入力されたカレンダーテーブルだった。Calendar が期限切れになると、システムにログインできなくなった
- 数年前に実際にそうなり、インターンがさらに 5 年分を埋めた。どのシステムがこれを使っているのかは誰も知らない
Employees
- 毎朝 7 時 15 分に employees テーブルが削除され、ADP から受け取った CSV で再投入されていた
- この作業中はシステムにログインできない。ときどきこの処理は失敗した
- データは本社に複製する必要があったため、メールである一人に送られ、その人が毎日ボタンを押してデータをコピーしていた
置き換え用データベース
- データベースを整理できるのではないか、と思うかもしれない。会社もそう考えた
- データベースのコピーがあり、データはおよそ 10 分遅れていた。同期は一方向にしか行われなかった
- このデータベースは正規化されていたため、merchants から電話番号を探すには 7 つの join が必要だった
販売実績
- すべての営業担当者には、毎月達成すべき "win" というノルマがあった
- それを管理するテーブルは非常に複雑だった。毎日ジョブが走り、追加・変更された行を見つけて本社システムと同期していた
- ある営業担当者が、レコードを手動で変更してほしいと依頼してきたことで問題が起きた
- その営業担当者はすでに win を達成しており、その月にさらに大きな販売を加えたが、それを翌月へ移したいと望んだ
- インターンがこの作業を担当し、うわさが広まると、3 年にわたって依頼は指数関数的に増えていった
- ある時期には、インターン 3 人がひたすら SQL 文を書くのが仕事になっていた。これ専用のアプリケーションを作るのは難しすぎると考えられていたのだ
コードベース
- 私が最初に触れたコードベースは Team Foundation Server にあった。中央集権型のバージョン管理システムだ
- 主に作業していたコードベースは半分が VB、半分が C# で書かれていた。IIS 上で動き、セッション状態をあらゆる場所で使っていた
- つまり、Path A からページに入るか Path B から入るかで、そのページで見える内容がまったく違うことが実際に起きていた
- 当時存在していたあらゆる JavaScript フレームワークがこのリポジトリにチェックインされていた。主に、作者が必要だと考えたカスタム変更付きでだ。特に knockout、backbone、marionette が目立ち、jquery と jquery プラグインもあった
- このコードベースのほかに、12 個ほどの SOAP サービスと複数のネイティブ Windows アプリケーションもあった
Gilfoyle のハードドライブ
- Gilfoyle はとてつもなく速いプログラマーとして知られていた。会ったことはなかったが、彼のコードとハードドライブに残されたコードを通じて彼を知っていた
- Munch は、Gilfoyle が会社を去って何年も経った後も、彼のハードドライブを RAID 構成のまま机の上に置いていた
- なぜなら Gilfoyle はコードをチェックインせず、単一ユーザー向けの場当たり的な使い捨て Windows アプリケーションを作ることで有名だったからだ
- Gilfoyle のハードドライブにしか存在しないアプリケーションのバグ報告をユーザーが持ってくることも珍しくなかった
配送バグ
- 私の仕事の大半は、チームが担当したがらないバグを追跡することだった
- 数か月に一度発生する、とりわけ厄介なバグがあった。出荷後に配送キューへ引っかかった注文があり、すでに出荷済みなのに未発送のように見えていた
- 解決のためにさまざまな対処策(SQL スクリプト、Windows アプリケーションなど)を試した。根本原因を追うなと助言されたが、どうしても我慢できなかった
- その過程で Gilfoyle の考え方を学んだ。配送アプリはデータベース全体を取り込み、その後日付でフィルタして、アプリの開始日以降の注文をすべて保持していた
- アプリは SOAP サービスに依存していたが、サービスらしい仕事をするためではなく、純粋関数だった。すべての副作用を引き起こしていたのはクライアント側だった
- そのクライアントの中で巨大なクラス階層を見つけた。120 個のクラスがそれぞれさまざまなメソッドを持ち、継承は 10 階層にも及んでいた
- 唯一の問題は、すべてのメソッドが空だったことだ
- これはリフレクションを使える構造を作るためのものだった。そのリフレクションは、パイプ区切りの文字列(データベース駆動だが完全に静的な構造)を作り、ソケット経由で送っていた
- 最終的にそれは Kewill に送られていた。運送業者と通信するサービスだ。バグが起きた理由は、Kewill が毎月 9 桁の番号を再利用しており、しかも古い注文を削除する cron ジョブを誰かが無効化していたからだった
美しい混沌
- このコードベースについては、まだまだ語れることが多い。5 年間コードをリリースせずに全体を書き直そうとしていた主任開発者チームのことや、すべてを支配する単一データベースを作ろうとした Red Hat のコンサルタントのことなどだ
- このコードベースには狂気じみた一面が山ほどあり、単一機能をゼロから始めるためだけに専任チームが必要と思える理由もいくらでもあった
- しかし、最も重要な話は Justin が Merchants Search ページを改善したことだ。このページはアプリケーション全体の入口だった
- すべてのカスタマーサービス担当者は、加盟店と電話しながら ID や名前を入力して情報を探していた。すると、あらゆる情報が詰め込まれた巨大なページへ移動した
- このページは、必要な情報と行きたいリンクがすべて詰まっていて、良い意味で情報密度が高かった。しかし、とてつもなく遅かった
- Justin は私のグループで唯一のシニア開発者だった。頭が切れ、皮肉屋で、ビジネスそのものにはあまり関心がなかった
- 彼は思ったことをそのまま言い、手加減せず、常に周囲のチームより速く単独で問題を解決できた
- ある日 Justin は、加盟店検索ページがいかに遅いかという話を聞き続けるのにうんざりし、行って直してしまった
- 画面上のすべてのボックスがそれぞれ独自のエンドポイントになった。読み込み時にはファーストビュー内のすべての取得が始まり、1 つ読み込まれるとさらに多くのリクエストが飛ぶようになった
- ページ読み込み時間は数分から 1 秒未満に短縮された
デカップリングの 2 つのやり方
- Justin がこうできたのは、このコードベースにマスタープランがなかったからだ
- システムが従うべき大まかな設計図も、API の想定形式も、文書化されたデザインシステムも、一貫性を保証するアーキテクチャレビュー委員会もなかった
- アプリは完全にめちゃくちゃだった。誰も直せなかったので、誰も直そうとしなかった。その代わり、私たちはそれぞれ自分なりの小さな理性の世界を作った
- このモノリシックなアプリは、純粋に必要に迫られて、周縁部に良くて小さなアプリの小宇宙へと成長していった
- アプリの一部を改善する任務を与えられた各人は、結局そのクモの巣をほどこうとする試みをあきらめ、新しいものを作れる良くて小さな隅を見つけた。そして少しずつリンクを更新して良い新しいものを指すようにし、古いものを孤児化していった
- これはひどい話に聞こえるかもしれない。しかし、驚くほど働きやすかった。コード重複への心配は消え、一貫性への心配も、スケーラビリティへの心配も消えた
- コードは使うために書かれ、周辺領域にはできるだけ触れず、簡単に置き換えられるように書かれていた。私たちのコードはデカップルされていた。なぜなら、結合させるほうが単純に難しかったからだ
その後
- それ以降のキャリアで、あれほど見事に醜いコードベースで働ける特権に恵まれたことはない
- それ以降に出会ったどんな醜いコードベースも、一貫性への必要を超えることはできなかった
- おそらく「真面目な」開発者たちがずっと昔にそのコードベースを見捨てていたからだろう。残っていたのは、雑多なインターンとジュニア開発者だけだった
- あるいは、開発者とユーザーの間に中間レイヤーがなかったからかもしれない。翻訳も、要件収集も、カードもなかった。ただカスタマーサービス担当者の机の前に立ち、どうすれば彼らの生活を良くできるかを尋ねるだけだった
- あの直接的なつながりが恋しい。速いフィードバック、大げさな計画を立てる必要がなかったこと、単純な問題とコードとの結びつきが
- たぶん単なるナイーブなノスタルジーなのだろう。しかし、私が子ども時代の最悪の数年間に戻りたくなるのと同じように、「エンタープライズ設計パターン」に直面するたび、私の心はあの美しく恐ろしいコードベースへと引き戻される
GN⁺ の意見
- この記事は、レガシーシステムを扱う開発者に共感と慰めを与えるかもしれない。完璧ではないコードベースにも価値があり、学ぶ点が多いことを示している
- しかし、このコードベースの問題をロマン化してはならない。技術的負債が積み上がると開発速度は大きく低下し、保守は難しくなる。長期的にはコストも増える
- コードベースを改善しようとする努力をあきらめ、応急処置を積み上げ続けることが正解ではない。レガシーシステムを段階的に改善、あるいは移行していく戦略が必要だ
- 開発文化とプロセスも重要だ。コードレビュー、アーキテクチャ設計、文書化などのエンジニアリングプラクティスをしっかり守ることが、より良いコードベース作りに役立つ
- ユーザーとの密接なコミュニケーションと速いフィードバックは良い点だ。Agile のような方法論でこれを促進しつつ、コード品質も管理するのが理想的だ
- 結局のところ、すべてはバランスの問題だ。完璧を追い求めるよりも、持続可能な形でユーザーのニーズを満たし、技術的負債を管理することが重要だ
3件のコメント
最初の職場でファームウェア開発者として与えられた任務は、開発者もソースコードもない製品の8051 MCUのhexから抽出したアセンブリコードを解析して、Cで再実装することでしたね…
それでも一応動く製品があったので、コードを見て製品をテストしてを繰り返しながら、なんとかやり遂げはしましたが…
地方出張に行って、直してこい、さもないと指を切って帰れ、みたいな脅しを受けたこともあったし
正体不明のバグの原因が、実は装置が設置された壁の裏にあったエレベーターだったこともありましたし
釜山の冬柏島 APEC 会議場が正式オープンする前に入って、あれこれ設置した記憶もありますね(笑)
Hacker Newsの意見
最初の会社で複雑なVBアプリケーションを保守していた
最初の職場でCOBOLとJavaで書かれたレガシー製品を保守していた
12,000行を超えるPerlスクリプトをリファクタリングした
理論と現実の違いを実感した
TelegramのAndroidクライアントのコードベースは非常に複雑だった
最初の職場でレポート処理のメモリ問題を解決した
顧客と直接やり取りしながら問題を解決した経験はよかった
複数の市場をサポートするシステムを作った
最初の職場には経験不足の人が多かった
現代のSQL Serverで問題を解決する方法を説明した
採番テーブル(SequenceKey)と営業日テーブル(Calendar)
懐かしいですね。今はどうしているのかわかりませんが、以前はよく使われていたテーブルです。SIをやると、業務共通パートで関連機能を実装していました。