はじめに
こんにちは!menu事業部でフロントエンドエンジニアをしています、25卒の佐藤です。
突然ですが、皆さんは「良いコード」と聞いて、どんなコードを思い浮かべますか?
パフォーマンスの良いコード? 洗練されたアルゴリズム? それとも流行りの設計手法を取り入れたモダンなコードでしょうか?
研修中、この「良いコード」の捉え方を間違えたことで、苦い経験をしました。今回はその失敗談を交えながら、私が研修や実務を通して学んだ「状況に応じた良いコードの考え方」についてお話ししたいと思います。
完璧なはずが…ハッカソンでの失敗
研修では、2か月の講義を終えた後に2週間ほどのハッカソンをエンジニア4人、デザイナー1人のチームで行いました。
インフラにおいては、FirebaseやGoogle Cloudなどが利用可能でしたが、私たちのチームはFirebaseを選択しました。それに伴い、全員が未経験でしたが、Firebaseと互換性の高い「Flutter」を使ってアプリを作ることにしました。
開発初日、私たちは「どうせなら"良いコード"で作りたい!」と意気込み、Flutterアプリのアーキテクチャについて調べ始めました。そして、あるベストプラクティスを見つけ、最終的に以下のアーキテクチャで進めていくことになりました。
lib/
├── main.dart
├── core/
│ ├── utils/
│ └── constants/
├── data/
│ ├── providers/
│ ├── repositories/
├── domain/
│ ├── entities/
│ ├── use_cases/
│ └── repositories/
├── presentation/
│ ├── view_models/
│ ├── views/
│ └── widgets/
└── routes/
これはオニオンアーキテクチャと言われる構造で、分かる方には馴染みのある構造だと思いますが、研修当時の私たちはこう思っていました。
「これで行こう!よく分からないけど、ベストプラクティスなら間違いない。研修だし進めながら学んでいけばいいや!」
しかし、結果的にこの判断に苦しめられることになりました。
現実①: チームメンバーはFlutterを学ぶので手一杯。誰もアーキテクチャを正しく理解できない。
現実②: オニオンアーキテクチャの利点は運用、保守が容易になること。例えば、外部システム(Firebase)の移行が容易など。しかし、短期間のハッカソンで移行なんて考える必要ない。
現実③: 結果として、本来不要な複雑さをコードに持ち込んでしまい、開発は難航。プロダクトを形にすることから、私たちは遠ざかってしまいました。
この経験から、「世間一般で"良い"とされるコードが、必ずしも今のチームにとって"良い"コードではない」という、当たり前で、しかし重要な事実を痛感しました。
ただ一方で、先人たちが積み上げて来た、世間一般で良いとされるコード・アーキテクチャの原則に従うことで恩恵を得られることが多いのはまた事実で、実際の現場ではこの原則が当てはまることの方が多いでしょう。
そこで、研修中「良いコードについて」学ぶ講義があったので、そこで学んだいくつかの原則をご紹介します。
そもそも「良いコード」とは?
では、一般的に「良いコード」とはどのような状態を指すのでしょうか。大きく2つの側面があると言われています。
最適化されている
- メモリやCPUの使用量が少ない
- 実行スピードが速い
可読性が高い(内部品質が高い)
- コードを読んだだけで、どういう処理をしているかすぐに分かる
- どこにその処理が書かれているか見つけやすい
現場では「とにかく速度優先で!」と言われ、開発速度が重視される場面も多いかもしれません。しかし、そのために内部品質を犠牲にし続けると、後で大きなツケを払うことになります。
「窓割れ理論」のように、一度品質の低いコード(割れた窓)を放置すると、それが当たり前になってしまい、コードベース全体の品質がどんどん悪化していくのです。
品質の高いコードを書くのは時間がかかると思われがちですが、長い目で見れば、むしろ開発速度は上がります。散らかった部屋から物を探すより、整理整頓された部屋から探す方が圧倒的に速いのと同じです。
特に、実務では新規開発よりも既存コードの改修がほとんど。「コードを読む時間」をいかに短縮できるかが、生産性に直結するのです。
明日から実践できる「良いコード」の基本原則
では、コードの認知負荷を下げ、未来の自分とチームを楽にするためには、具体的に何をすれば良いのでしょうか。研修で学んだ内容を元に、「心構え」から「具体的な設計」 へと順を追ってご紹介します。
いくつかの原則
まず設計の基本となる考え方を押さえておきましょう。これらはあらゆる場面でコーディングの指針となります。
-
KISS (Keep It Simple, Stupid): とにかくシンプルに!複雑なコードはバグの温床です。
-
YAGNI (You Ain’t Gonna Need It): 「いつか使うかも」は不要。今必要な機能だけを実装しましょう。
-
SRP (Single Responsibility Principle): 「単一責任の原則」。1つのクラスや関数が持つべき責任(役割)は1つだけにする、という考え方です。
分かりやすい命名をしよう
コードを書いてると何度も命名する機会があると思います。ここの分かりやすさが、可読性を大きく左右します。
-
関数名は「何をするか」が分かる命令文に:
例:getUserData()
,calculateTotalPrice()
-
flag
やcheck
のような曖昧な単語は避ける: 何のフラグなのか分かりません。 -
肯定的な単語を選ぶ: 二重否定(
isNotDisabled
がfalse
の時…?)は混乱の元。isEnabled
のように肯定的な表現を使いましょう。 - 紛らわしい略語は使わない: 自分しか分からない略語は禁物です。
-
単位や型を接尾辞で補う:
timeoutSeconds
,userList
,widthPx
のように、単位や型が分かると親切です。 - 迷ったらAIに考えてもらうのも有効です。
- 同じリポジトリ内の命名規則を真似しましょう。 統一感が大事です。
再利用しやすい「部品」を作るための設計
分かりやすい名前をつけたら、次はそれらの要素をどのようにグループ化し、再利用しやすい 「部品(モジュール)」 にしていくかを考えます。ここで重要になるのが「凝集度」と「結合度」です。
-
凝集度: 一つのモジュール内に、関連性の高い機能がどれだけ集まっているかの指標。高い方が良い状態です。(例: ユーザーに関する処理は
UserModel
やUserService
にまとまっている) - 結合度: モジュール同士がどれだけ強く依存し合っているかの指標。低い方が良い状態です。(例: Aモジュールを修正したら、無関係のはずのZモジュールも修正が必要になった…というのは結合度が高い)
これを意識することで、修正の影響範囲を限定でき、再利用性の高い部品を作ることができます。
プロジェクト全体を整理整頓するアーキテクチャ
個々の部品がうまく作れたら、最後はプロジェクト全体の設計、「アーキテクチャ」 の視点です。これは、私たちがハッカソンで苦戦した部分でもあります。
中途半端な理解だとかえって複雑に見えるかもしれませんが、きちんと理解すれば圧倒的に保守、運用を楽にしてくれます。
アーキテクチャの目的
主な目的は、「関心の分離」です。
例えば、「画面表示」「ビジネス上の計算」「データベースとの通信」といった役割(関心事)ごとにコードを置き場所(レイヤー)に分け、依存関係を一方通行に整理します。
代表的なアーキテクチャ
-
MVC: Model, View, Controllerに分ける、Web開発でよく用いられるイメージ
- Model:データの保持を行う(ビジネスロジックの実装を持たせることもある)
- View:ユーザーインターフェースに当たる部分.表示や入出力の処理を行う
- Controller:ModelとViewそれぞれに指示をだし、全体の処理を制御する
-
レイヤードアーキテクチャ: 役割を階層(レイヤー)で分離して、依存関係を一方通行にする。
↓の順番で上から下にのみ依存する。- プレゼンテーション層:表示における関心毎を扱う(UI層とも言う)
- アプリケーション層:ドメインロジックを制御し、機能を作る
- ドメイン層:ドメインロジックを実装する
- インフラストラクチャー層:データの永続化や外部サービスとのやりとりを行う
-
オニオンアーキテクチャ: レイヤードアーキテクチャと似ていますが、ビジネスの核となるルール(ドメイン層)を中央に置き、依存の方向が全て内側に向かうように設計します。これによってインフラ層への依存が下がり、外部ツールへの依存度を下げることができます。
これらのアーキテクチャを導入すると、「どこに何が書いてあるか」の予測がつき、テストもしやすく、結果的に保守性が高まる、と言われています。
結論:チームにとっての「銀の弾丸」を見つけよう
ハッカソンの話に戻りますが、オニオンアーキテクチャが優れた設計思想であることは間違いありません。しかし、Flutter初心者のチームが2週間でアプリを作るという状況では、明らかに過剰品質でした。
今回ご紹介した原則やアーキテクチャは、コードを良くするための強力な武器ですが、絶対的な正解、銀の弾丸ではありません。
私たちが目指すべきゴールは、チーム開発を円滑にすることです。
- チームメンバーが他の人のコードを理解しやすくする
- コンフリクトを起こしにくくする
- 実装が楽に、速くできる
これらの目的が達成できるなら、それがどんなやり方であれ、そのチーム、プロダクトにとっての「良いコード」だと思います。
プロジェクトの期間、規模、メンバーのスキルセット。様々な状況を考慮して、チームにとっての最適な「良いコード」を見つけ出すことが大事ですね!
▼採用情報
レアゾン・ホールディングスは、「世界一の企業へ」というビジョンを掲げ、「新しい"当たり前"を作り続ける」というミッションを推進しています。
現在、エンジニア採用を積極的に行っておりますので、ご興味をお持ちいただけましたら、ぜひ下記リンクからご応募ください。