LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

システム設計・開発の実践Tips

こんにちは。ソフトウェアエンジニアの眞井です。私はこれまでアーキテクトとして、検索連動型ショッピング広告のレポートシステムに関連する2つの新規システム開発や、その他数多くの機能追加に携わってきました。本記事では、その経験から得た「新規システム・新機能を設計する際のTips」を、開発の時系列に沿ってご紹介します。

仕様策定時

目的を理解し、目的を達成できる最も簡易な手段を検討する

開発への要求は多くの場合、「〇〇という機能を実装してほしい」という具体的な手段の形で届きます。これをそのまま実装するのではなく、一度「その機能によって、どんな目的を達成したいのか?」という点について注目します。

ヒアリングしてみると、意外に依頼側も目的を掴み切れていないことがあったりします。対話を通じて「本施策で何を実現したいのか」を明らかにします。目的が明らかになれば、システムの内部構造に詳しい開発チームだからこそ可能な「より簡易で効果的な代替案の提案」ができるはずです。

ここで「最も簡易な手段」を選択できれば、不要な機能開発における工数のみならず、その後の運用工数も完全に排除でき、大幅なコスト削減につながります。

基本設計時

技術選定

理由なき「挑戦」は避ける

技術選定においては、明確な理由がない限りチャレンジングな選択はしません。基本的には「安定性」に重点を置き、最新の技術よりも、実績が豊富でノウハウが蓄積された、いわゆる「枯れた技術」を優先的に選びます。

しかし、その技術を習得することが将来的に大きなリターン(レバレッジ)を生むと確信できる場合や、他に解決手段がない場合にはあえて挑戦的な選定をすることもあります。

トリッキーなハックを避け「標準」を徹底する

あらゆるツールやミドルウェアやフレームワークにおいて、その用途に応じた「標準的な使い方」を徹底します。これらにはそれぞれ得意な領域(思想)があります。そこから外れたトリッキーな使い方は、機能の十分なサポートを受けることができません。

ある問題に悩んだ時、ミドルウェアをトリッキーに使うことで解決したくなることがありますが、短期的には工数削減ができても、長期的には必ず負債になります。

常に「最小構成」からスタートする

未来の予測は難しいため、「念のため」の過大な構成は負債になることが多いです。必要になれば簡単に構成変更できるようにしておくことの方が大事です。

データの扱い

整合性を死守し、冗長性を排除する

基本原則として、「他のデータから導出(計算)できるデータ」は別途保持しないようにします。

例えば、注文の合計金額を total_price カラムとして保存するのではなく、必要な都度、各注文明細の金額を合計して算出します。もし冗長にデータを持たせてしまうと、明細変更時に合計金額の更新漏れが発生するなど、データ修正の難易度と不整合のリスクが跳ね上がってしまうからです。

パフォーマンス要件により、どうしても都度の算出が難しい場合は、計算結果を「キャッシュ」として保持します。あくまでキャッシュとして扱うため、たとえそのデータが消失しても「パフォーマンスが一時的に低下するだけで、システムとしての正しさは失われない」設計を徹底します。

依存関係のコントロール

コアロジックを外部依存から隔離する

クリーンアーキテクチャなどに倣い依存性を管理します。特に「外部要因に依存させないようにコアロジックを守る」を徹底します。データベースや外部API、フレームワークといった「外側の世界」からロジックを切り離すことで、コアロジックを純粋なユニットテストで強固に保護できるようになります。また、リリース後の運用において、外部仕様の変更やミドルウェアの刷新が必要になった際も、システムの根幹を揺るがすことなく柔軟に追従することが可能になります。

チーム開発時

設計思想を「骨組み」で伝える

設計思想などの抽象的な考え方は、口頭の説明だけではなかなか伝わりきりません。実務を通じて少しずつ体感し、感覚を掴んでもらう必要があります。チーム開発をスムーズに進めるために、私はアーキテクトとして「インターフェース」や「ディレクトリ構造」といった設計の骨組みを先に定義し、メンバーにその「中身」を埋めてもらう形でタスクを依頼するようにしています。大切なのは、メンバーの習熟度に合わせて骨組みの粒度を調整することです。詳細なテンプレートを用意するのか、あるいは大枠の制約に留めるのか。このバランスを最適化することで、品質の管理を行います。

自動テストの仕組みは「初動」で完結させる

ユニットテストやCI/CDによる自動テストの仕組みは、プロジェクトの最初期に構築してしまいます。このタイミングを逃すと、後からの導入は想像以上に困難になります。

困難になる理由はいくつかありますが、代表的なものが「心理的なプレッシャー」です。 開発が軌道に乗り、周囲から定期的な進捗を求められるようになると、「今は基盤整備よりも機能実装を優先すべきではないか」というバイアスが強く働くようになります。一度このフェーズに入ってしまうと、立ち止まってテスト環境を整えるという決断は、技術的な手間以上に心理的なハードルが高くなってしまいます。

常に「動く状態」を維持しながら作り進める

開発の極めて早い段階で「最小限で動く状態(骨組み)」を作り、それ以降は常に「動くこと」を前提に機能を追加していきます。常に動く状態をキープすることで、問題が混入した際に即座に気づけるようになります。不具合を放置せず、その都度修正する習慣がつくため、結果として開発終盤のバグ出し期間を大幅に短縮し、最終的な品質を高く保つことができます。

扱うデータ量・計算量に気を配る

常に、扱うデータ量と計算量に対する感度を高く持つことが重要です。

開発・検証環境で扱うテストデータと、本番環境のデータ量には、往々にして大きな乖離があります。データ量の前提が「100件」なのか「100万件」なのかで、最適なアルゴリズムやDB設計は大きく変わります。検証環境での成功に満足せず、「本番のワークロード」を常に想定した実装方針を選択します。

バッチ処理における実装哲学

私はバッチ処理の開発にも多く関わってきました。ここでは実装時における、特にバッチ処理に関するTipsに触れてみます。

メモリだけでなくディスクを使うことも検討する

扱うデータが巨大な場合、すべてをメモリ上で完結させようとすると、OOM(メモリ不足)による異常終了のリスクが常に付きまといます。そこで重要になるのが、適宜中間ファイルを書き出すなど、ディスクを戦略的に活用することです。一般的に「ディスクは遅い」と敬遠されがちですが、シーケンシャルな読み書き(連続したデータの処理)であれば、バッチ処理において必要十分なスループットを確保できるケースが多々あります。

一部の失敗で「全体」を止めない

バッチ処理中に想定外のデータによるエラーが発生しても、処理全体を停止させない設計を徹底します。具体的には、エラーが発生したレコードのみをスキップし、内容をエラーログや専用のテーブルに記録した上で、後続データの処理を継続させます。たった一つの異常データのために、正常な数万件の処理まで機能不全に陥る事態を避けるためです。処理に失敗したデータについては、原因を調査した後に「そのデータだけ」を安全に再実行できる仕組みを用意しておくことで、バッチ全体の安定性とリカバリの柔軟性を両立させます。

一時的なエラーは、自律的に復旧されることを目指す

大規模な処理を実行していると、ネットワークの瞬断といった「一時的な外部要因のエラー」は避けられません。こうしたエラーに対して、いちいち手動で介入するのではなく、システムが自律的に復旧できる仕組みを整えておきます。

肝となるのは、処理に冪等性を持たせておくことです。何度実行しても同じ結果になるように設計されていれば、エラー発生時に単純な自動リトライを行うだけで、多くの問題は解決します。

リリース後の運用・保守時

エラーの可観測性

リリース前のテストでは、どうしても「正常に動くこと」に主眼が置かれ、運用や保守のための設計は後回しにされがちです。しかし、運用フェーズにおける調査コストを削減する仕組みは、機能実装そのものと同じくらい重要です。

エラーが発生した際、ログや通知を見ただけで「何が、どこで、なぜ起こっているか」を即座に把握できる状態を目指します。この可観測性を構築するために十分な工数を割くことは、リリース後の障害対応時間を短縮する、極めて投資効果の高い施策となります。

おわりに

設計や考え方に唯一無二の正解はありません。しかし、「なぜこの技術を選んだのか」「なぜこの構成にしたのか」という問いに対し、自分なりの明確な理由を持つことはできます。その積み重ねが、変化に強く、誰にでも愛される堅牢なシステムを作ると信じています。ぜひ皆さんの現場に合わせて、これらのTipsをカスタマイズして使ってみてください。より良いエンジニアリングを、共に楽しんでいきましょう!