未経験からエンジニア 奮闘記

未経験からエンジニアで自由に生きる途中

MENU

N+1問題とは?基礎から対策まで解説

この記事を読むと得られること

  • N+1とは何か をしっかり理解できる
  • N+1の対策 を自信を持って打てるようになる
  • データ取得時に意識すべきパフォーマンス観点 を身につけられる

データベースとアプリケーションの間でやりとりされるSQLクエリ。
その中でも開発初期によくある落とし穴が「N+1問題」です。

この問題を放置すると、リリース後にアプリが遅くなるなど重大な影響を及ぼします。
この記事では、N+1がどういう場面で発生しやすいのか、そしてどのように対策を打つべきかを丁寧に解説していきます。


N+1問題とは何か?どんな時に発生するのか?

N+1問題とは、データ取得の際に 不要に多くのSQLが実行されてしまう状態 を指します。

たとえば、以下のような処理を見てみましょう。

// ユーザー一覧を取得
users := db.Find(&[]User{})

for _, user := range users {
    // 各ユーザーのTodoを取得
    db.Where("user_id = ?", user.ID).Find(&[]Todo{})
}

この場合、ユーザー一覧を取得する1回のクエリに加え、ユーザーごとに関連するTodoを取得しており、ユーザーの数だけクエリが追加で実行されます。
つまり、N人のユーザーに対してN+1回のクエリが発行されることになります。


データ取得で気をつけるべき2つのポイント

N+1を理解するためには、まずパフォーマンスに影響する基本的な観点を押さえることが大切です。

1. SQLのリクエスト回数

クエリ数が増えるほど、アプリケーションとデータベースの間の通信が増え、処理速度に悪影響を及ぼします。

2. テーブル結合(JOIN)のコスト

複数のテーブルを結合するとDB内での処理が重くなります。大量データをJOINする場合は特に注意が必要です。


N+1の何が問題なのか?

要点は「SQLのリクエスト回数が増えてしまう」という点に尽きます。

1件ずつ関連データを取得することで、ユーザー数に比例してクエリ数が増加。
その結果、処理が遅くなったり、DBへの負荷が急増したりと、アプリケーションのパフォーマンスが著しく悪化します。


N+1問題の対策方法

N+1問題を回避するためには、以下のような方法があります。

Preload(GORMなどのORマッパーで使用)

関連データをあらかじめ一括で取得しておく方法です。

db.Preload("Todos").Find(&users)

これにより、ユーザー一覧と各ユーザーのTodoを 2クエリで一気に取得 できます。

JOIN を使う(用途に応じて)

場合によっては JOIN を使って関連データをまとめて取得する方法も有効です。ただし、JOINを多用するとデータ量が膨らみ、パフォーマンスに影響する可能性もあるため、慎重に使う必要があります。

💡 PreloadとJOIN、どちらを選ぶべきか?
この判断は場面ごとに異なり、データ構造や用途に応じた知識が必要になります。
このテーマは中級者以上向けの内容になるため、別記事で詳しく解説します。


まとめ:中級者への第一歩は「パフォーマンス意識」

今回は「N+1問題」について基本から対策までを紹介しました。

重要なのは、単にコードを書くのではなく、クエリの数や処理の重さに対する意識を常に持つことです。

  • クエリが何回実行されているか?
  • 1回のクエリでどれだけのデータを持ってくるか?
  • PreloadやJOINは適切に使われているか?

こうした視点を身につければ、日々の開発で「なんとなく書いている処理」が一段レベルアップし、パフォーマンスを意識できる中級者へのステップアップになります。


📘 次回予告:PreloadとJOINの違いと選び方を徹底解説!
さらに深く知りたい方のために、別記事で実際の使い分けパターンやベンチマーク結果も紹介する予定です。


このままブログに貼ってもOKな形に整えましたが、必要なら図やコード例を増やすカスタマイズも対応できます。どうしますか?

🧑‍💻 バックエンドエンジニアの独り立ちに必要な知識まとめ

前提: フロントとAPIが分離された構成(SPA + REST APIなど)を想定。


1. 🔐 認証・認可の理解と実装

✅ 認証(Authentication)

  • 基本: email + password による認証
  • パスワードのハッシュ化(bcryptやargon2など)
  • セッション管理の考え方(クッキー or トークン)
  • ※余裕があれば: OAuth2やGoogleログインなどのソーシャルログインも理解しておくと◎

✅ 認可(Authorization)


2. 📋 要件定義と設計力

✅ 要件定義

  • ユースケースの洗い出し(誰が、いつ、なにをする)
  • 機能一覧の作成(CRUD単位など)
  • 非機能要件の検討(パフォーマンス、セキュリティ、可用性など)

✅ 詳細設計

  • 画面ワイヤーフレーム(フロントとの連携を意識)
  • APIインターフェース設計(REST or GraphQL)
    • OpenAPI(Swagger)による仕様書管理ができると◎
  • DB設計
    • ER図が描ける
    • 正規化・非正規化の理解
    • インデックス設計
    • N+1問題の回避

3. 💻 実装力

✅ コードの質

✅ 言語習熟

  • 1つの言語を習得(Go, Ruby, Python, Java など)
  • 標準ライブラリの活用
  • パッケージ構成・依存関係の管理(Go modules、Bundler など)

SQL

  • CRUD操作
  • JOIN・サブクエリ・ウィンドウ関数
  • SQL実行計画(EXPLAIN)を読んでチューニング

4. 🧪 テストと品質保証


5. 🧯 エラーハンドリング・ロギング

  • 適切なHTTPステータスコードの返却
  • エラー構造体を使った統一的なレスポンス
  • ログ出力(zerolog, zap, logrusなど)
    • リクエストIDを入れる
    • 構造化ログの出力
  • エラートラッキングツールの導入(Sentryなど)

6. 🛡 セキュリティの基本


7. 🚢 運用・デプロイ

  • Dockerでのローカル開発環境構築
  • デプロイの流れ(GitHub Actions + VPS/AWS/Heroku など)
  • 簡単なインフラの知識(DNS, TLS, Webサーバなど)
  • APIの監視・メトリクス収集(Prometheus + Grafana)
  • エラーログの通知(Slack連携など)

✅ おまけ: こんな状態なら独り立ち!

  • 要件を聞いて自分でAPIを設計・実装できる
  • DBスキーマを自分で設計し、マイグレーションを管理できる
  • バグが起きてもログから原因を追える
  • 他の人のコードを読んで、レビュー・改善提案ができる
  • 小さくても本番環境にデプロイした経験がある

✍️ まとめ

バックエンドエンジニアの独り立ちに必要なのは、
「設計 → 実装 → テスト → デプロイ」までを自分の力でやり切れる力

本質は「一人でシステムを回せるか」ではなく
「チームで困っているときに自分が支えられるか」だから、
広く浅くじゃなくて、「一点突破で深く」でもOK!

未経験からエンジニアを目指す人へ:まずは業態と職種の違いを知ろう

はじめに

エンジニアを目指している方へ、最初に強く伝えたいことがあります。

エンジニアになりたいなら、まず「企業の業態」と「エンジニアの種類」を理解しましょう。

なぜそれが大事なのか?

努力の方向を間違えてしまうからです。
例えば、少しプログラミングを学んだだけで知識が浅い状態でSES企業に入社してしまうと、開発業務に関われないままキャリアが停滞することがあります。
これは本当によくある話です。


エンジニアを目指す前に確認すべき「2つの軸」

1. 企業の業態(どのような会社で働くか)

企業には様々な業態があり、それによってエンジニアとしてのキャリアの積み方も大きく異なります。以下に主な業態を紹介します。

1. 自社開発エンジニア

自社サービスの運用・改善・新規開発に関わります。プロダクトの成功が自社の成長に直結するため、エンジニアの影響力が大きいです。
参考1, 参考2

2. SES(System Engineering Service)

クライアント企業に常駐し、システム開発や保守を行います。柔軟な人員配置が可能で、案件によって働く場所や内容が変わります。
参考, 参考, 参考

3. SIerシステムインテグレーター

要件定義から設計、開発、導入、保守まで一貫してITシステムを請け負う企業。プロジェクトマネジメント色が強いです。
参考, 参考

4. 社内SE

自社の業務効率化のために社内システムを開発・運用します。内製化が進んでいる会社では開発スキルも求められます。
参考

5. コーポレートエンジニア

会社全体の課題をITで解決することに重きを置き、経営に近い視点を持つことが求められるエンジニアです。
参考


2. エンジニアの職種(どんな技術に携わるか)

もう1つの軸は、エンジニアとしての「専門分野」です。ここを間違えると、やりたい仕事に辿り着けません。

職種 概要
フロントエンド Webページやアプリの「見える部分」を作るエンジニア。HTML/CSS/JavaScript などを使います。
バックエンド サーバー側のロジックを担当。APIの設計やデータベースとの連携など。主にGo、JavaPythonなど。
インフラ サーバーやネットワークの設計・構築・運用を担当。クラウドAWS, GCPなど)の知識も必要。
ネットワーク 通信機器の設定やネットワークのセキュリティ対策を行う。物理的な機器に近いレイヤーを扱います。
モバイル iOS/Androidアプリの開発。SwiftやKotlinが主な言語。
データエンジニア ビッグデータやデータ分析の基盤を作る。ETLパイプライン構築、SQLPythonなどを多用。

🔰 未経験から始めるならここ!

未経験からエンジニアを目指すなら、フロントエンド・バックエンド・モバイルのいずれかから始めるのがおすすめです。

その理由は以下の通りです:

  • 実際に手を動かしてアプリやWebサイトが動く体験が得られるため、学習のモチベーションを維持しやすい
  • ITの専門知識が少なくても始めやすい
  • 求人数が多く、需要が高い職種である

例えば、フロントエンドでは簡単なWebページを作ることから始められ、モバイル開発ではスマホアプリを自分で作ってストアに公開することも可能です。

これらの分野は「成果物を見せやすい」という点でも、ポートフォリオを作って転職活動に活かしやすいのが特徴です。

結論:事業会社を目指すべし

SNSYouTubeでよく見かける「キラキラしたエンジニア像」の多くは、事業会社良質なSESに属する人たちです。

その中でも、未経験からエンジニアになるなら、事業会社を目指すのが圧倒的におすすめです。

理由1:転職ルートが確立されている

未経験から事業会社に転職するためのロードマップや教材が豊富にあります。
学習方法やポートフォリオの作り方については、別記事で詳しく解説予定です。

理由2:SIerは「マネジメント職」が中心

SIerは開発というよりプロジェクト管理が主な業務です。そのため、技術力を高めたいなら不向きかもしれません。

理由3:SESには"ガチャ"要素がある

SESでは、案件にアサインされた時点で売上が立つため、スキルのマッチよりも「配置できるかどうか」が重視されがちです。
もちろん良いSES企業もありますが、未経験のうちは見極めが非常に難しいため、信頼できる知人やエンジニアの助言が必要です。


まとめ:まずは事業会社を目指して戦略を立てよう

エンジニアになりたいなら、まずは事業会社のエンジニアを目指すことをおすすめします。

そして、最初の狙い目はベンチャー企業です。

ベンチャー企業が未経験でも採用する理由や、狙い方の戦略については、また別の記事で詳しく解説します。


今後も、未経験からエンジニアになるためのリアルな戦略を発信していきます。
少しでも参考になったら、ぜひ他の記事もチェックしてみてください!

AI頼りのジュニアエンジニアが増加する現状への警鐘

はじめに

近年、AIツールに依存するジュニアエンジニアが増加傾向にあります。AIの活用自体は有効な手段ですが、キャリアの初期段階にあるエンジニアは、まずは基礎力を養うことに注力すべきではないでしょうか。

現状の課題

AIの予測変換を使って同じようなコードを10回書くよりも、試行錯誤しながら数回書く方が、確実に技術力として身につきます。

なぜ今、自力でスキルを磨くべきなのか

1. AIによる市場価値の変化

  • AIの普及により、未経験者でも一定レベルのポートフォリオ作成が可能に
  • ジュニアエンジニアの差別化が困難に
  • 表面的なスキル基準の上昇により、本質的な技術力向上の時間が減少
  • 今後数年が、自力での技術力向上の重要な期間

2. 信頼性の問題

  • AIに依存したコード作成は、コードレビューで説明が困難
  • チーム内でのコミュニケーションや技術的な議論で支障をきたす
  • 複雑な実装では、AI単独では対応できない場面が発生

3. シニアエンジニアとの競争

  • AIの効果的な活用には、適切なプロンプトエンジニアリングが重要
  • シニアエンジニアがAIを本格活用し始めた場合、経験値の差が更に顕著に
  • ジュニアエンジニアの仕事機会が減少するリスク

結論

ジュニアエンジニアにとって、今後数年間が技術力向上の重要な機会となります。AIツールを補助的に活用しつつ、基礎的な技術力の向上に注力することが、長期的なキャリア形成において重要だと思います。 エンジニアは、年数が上がると給料が割と上がりやすい反面、年数に見合ったスキルがないと仕事がしんどくなってしまうので、離脱してしまう人も多いのではないかなと思っています。

結構SNSで見かけるのが、ジュニアエンジニアでスキルを培うべき時間を、SNSで小遣い稼ぎすることに時間を割いている人をよくみます。 その類の人は割と高い確率で、エンジニアを辞めてしまっているイメージです

AWS におけるサブネットの切り方

AWS におけるサブネットの切り方

AWS でのネットワーク構成を考える際に、VPC(Virtual Private Cloud)とサブネットの設計は非常に重要です。本記事では、細かい技術的な話ではなく、イメージがつきやすい形で AWS におけるサブネットの考え方を調べてみました

AWS でサブネットを切る目的

AWS でサブネットを切る目的は、サーバーやデータベースなどのリソースを配置する空間を作ることです。サブネットは、VPC 内で IP アドレスの範囲を分割し、管理しやすくする役割を持っています。

例えば、VPC を一つの "建物" に例えると、サブネットは "部屋" に相当します。部屋ごとに用途を決めることで、管理しやすくなり、セキュリティ面でも役立ちます。

なぜ 10.0.0.0/16 を使うのか?

AWS では、VPC のデフォルトの CIDR(IP アドレス範囲)として 10.0.0.0/16 がよく使われます。これは、次のような理由から選ばれることが多いです。

  1. 十分な IP アドレス数10.0.0.0/16 は 65,536 個(216)の IP アドレスを確保できます。企業内ネットワークでも十分な数です。
  2. プライベート IP アドレス10.x.x.x はプライベート IP アドレスとして予約されており、インターネット上で重複しても問題がありません。
  3. 管理しやすい10.0.0.0/16 を基準にして、用途ごとに 10.0.x.0/24 のサブネットを作るのが一般的です。

なぜサブネットは 10.0.0.0/24 で切られることが多いのか?

サブネットを 10.0.0.0/24 で切る理由は、

  1. 適度な IP アドレス数/24 のサブネットでは 256 個(28)の IP アドレスを確保できます。
  2. 管理がしやすい:用途ごとに 10.0.1.0/2410.0.2.0/24 など、規則的に区分できるため、シンプルなネットワーク構成に適しています。
  3. AWS の推奨AWS のドキュメントでも、一般的な用途では /24 を推奨しており、設定時のデフォルト値にもなっています。

/25 でサブネットを切ると管理が細かくなる理由

/25 のサブネットは、128 個(27)の IP アドレスを持ちますが、管理が煩雑になりやすいです。

  1. IP アドレスの管理が複雑になる

    • /24 なら 256 個の IP をひとつのまとまりとして扱えますが、/25 にすると 128 個ずつに分かれ、管理する範囲が増えます。
    • 例: 10.0.1.0/2510.0.1.128/25 の 2 つのサブネットができ、それぞれでルーティングやセキュリティグループを設定する必要があります。
  2. 繰り上がりの管理が必要になる

    • /24 なら 10.0.1.0 → 10.0.2.0 → 10.0.3.0 とシンプルに増えていきます。
    • /25 では 10.0.1.0 → 10.0.1.128 → 10.0.2.0 → 10.0.2.128 というように、途中で繰り上がりを考慮する必要が出てきます。
  3. セキュリティグループやルートテーブルの管理が煩雑になる

    • /25 で細かく分けると、サブネットごとにルートテーブルやファイアウォールルールを個別に設定する必要があり、運用が複雑になります。

シンプルなネットワーク構成の例

例えば、API サーバーと DB サーバーを設置するシンプルな構成を考えてみます。

  1. VPC: 10.0.0.0/16
  2. API サーバー用サブネット(パブリックサブネット): 10.0.1.0/24
  3. DB サーバー用サブネット(プライベートサブネット): 10.0.2.0/24

ネットワーク構成のイメージ

VPC: 10.0.0.0/16
  ├── Public Subnet: 10.0.1.0/24 (API サーバー配置)
  ├── Private Subnet: 10.0.2.0/24 (DB サーバー配置)

なぜこの構成にするのか?

  • API サーバーをパブリックサブネットに配置: インターネットからアクセスできるようにするため。
  • DB サーバーをプライベートサブネットに配置: 外部から直接アクセスできないようにし、セキュリティを確保するため。
  • NAT ゲートウェイの利用: DB サーバーがインターネットに接続する必要がある場合は、NAT ゲートウェイをパブリックサブネットに配置。

まとめ

AWS でのサブネットの切り方は、シンプルな構成にすることで管理しやすくなります。

  • VPC10.0.0.0/16 を使うことが多い
  • サブネットは 10.0.x.0/24 で切ると管理しやすい
  • /25 で分けると管理が煩雑になるため、適切な設計が必要
  • API サーバーと DB サーバーを配置する場合、パブリックサブネットとプライベートサブネットを分ける

この考え方を元に、自身のシステムに最適なネットワーク構成を考えてみてください。

Go における `make` の使うタイミング

Go における make の使うタイミング

Go でスライスやマップ、チャネルを作成する際に make を使うべきタイミングについて調べてみました

make のメリット

make を使う最大のメリットは 動的に変数を定義できること です。しかし、スライスに関しては make を使わず []T{}リテラルで定義することも可能です。では、なぜ make を使う必要があるのでしょうか?

make を使う理由: メモリ再割り当てを避ける

スライスの append による動的な拡張時には、キャパシティ(capacity)を超えるとメモリの再割り当てが発生 します。これにより、 - 新しいメモリ領域の確保 - 既存の要素のコピー

といったコストが発生し、パフォーマンスが低下する可能性があります。

append によるメモリ再割り当ての発生ケース

package main

import "fmt"

func main() {
    s := []int{} // 初期キャパシティなし
    fmt.Println("初期:", len(s), cap(s)) // len: 0, cap: 0

    for i := 0; i < 10; i++ {
        s = append(s, i)
        fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
    }
}

実行結果(キャパシティ増加の様子)

初期: 0 0
len: 1, cap: 1
len: 2, cap: 2
len: 3, cap: 4
len: 4, cap: 4
len: 5, cap: 8
len: 6, cap: 8
len: 7, cap: 8
len: 8, cap: 8
len: 9, cap: 16
len: 10, cap: 16

キャパシティが足りなくなるたびに 2倍に拡張 されているのが分かります。要素が増えるたびにメモリ再割り当てが発生し、コピー処理が行われるため、append のコストが上がってしまいます。

make を使った改善

make を使って あらかじめキャパシティを確保 すれば、メモリの再割り当てを減らし、パフォーマンスを改善できます。

package main

import "fmt"

func main() {
    s := make([]int, 0, 10) // キャパシティを10に設定
    fmt.Println("初期:", len(s), cap(s)) // len: 0, cap: 10

    for i := 0; i < 10; i++ {
        s = append(s, i)
        fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
    }
}

実行結果(キャパシティが一定で再割り当てなし)

初期: 0 10
len: 1, cap: 10
len: 2, cap: 10
len: 3, cap: 10
len: 4, cap: 10
len: 5, cap: 10
len: 6, cap: 10
len: 7, cap: 10
len: 8, cap: 10
len: 9, cap: 10
len: 10, cap: 10

make([]int, 0, 10) を使うことで、最初からキャパシティ 10 のスライスを確保し、不要なメモリの再割り当てを防ぐことができました。

まとめ

方法 メリット デメリット 使いどころ
[]T{} (リテラル) 簡潔に定義できる append 時にメモリ再割り当てが発生しやすい 素数が少なく、頻繁に変更しない場合
make([]T, len, cap) キャパシティを事前確保できる 初期値を持てない 要素を追加予定で、メモリ管理を最適化したい場合

スライスのサイズが事前に分かっている場合は、make を使うことで パフォーマンスの最適化 が可能です。特に append を多用する場合、make([]T, 0, cap) を使ってキャパシティを適切に設定するのが推奨されます。

Go の make はメモリ管理を考慮すると非常に便利なツールなので、適切に使い分けましょう。

Goにおけるパッケージの利用可能範囲と共通化の自分ベストプラクティス

Goにおけるパッケージの利用可能範囲と共通化の自分ベストプラクティス

なぜ調べたのか?

Goを使ったプロジェクトで以下のような問題に直面し、調査を行いました。

  1. ディレクトリ間でのパッケージ利用時のエラー
    • ディレクトリAで定義した機能をディレクトリBから利用しようとしたところエラーが発生。
    • パッケージのスコープやアクセス可能範囲に疑問を抱きました。
  2. 既存コードの意図を汲み取る必要性
    • 他者が書いた既存コードを理解する過程で、特定のパッケージがどこまで利用可能なのかを知りたい場面がありました。

これらの背景から、Goにおけるパッケージの適用範囲や構造に関する知識を整理する必要があると感じました。


結論

調査の結果、以下の知見を得ました。

  1. 機能上のパッケージのスコープ
    • go.mod以下に存在するすべてのディレクトリにパッケージはアクセス可能。
    • ただし、同じパッケージ名が複数存在すると名前衝突が発生するため、別名をつけてインポートする必要がある。
  2. 実務での運用ルール
    • Goではディレクトリ単位でパッケージを分離して利用するのが一般的。
    • 名前衝突を避けるため、共通機能を別ディレクトリ(例: common)に移しておくことが推奨される。
  3. 共通機能の整理方法
    • 以下のようなディレクトリ構造を採用すると、コードの再利用性が向上しやすい。

        RootDir
          ├─ common/
          │   ├─ utility.go  // 共通機能
          │   ├─ package common
          ├─ A/
          │   ├─ middleware/
          │   │   ├─ xx.go
          │   │   ├─ package middleware
          │   ├─ main.go
          ├─ B/
          │   ├─ middleware/
          │   │   ├─ xx.go
          │   │   ├─ package middleware
          │   ├─ main.go
          ├─ C/
          │   ├─ hoge/
          │   │   ├─ main.go
          ├─ go.mod
      

パッケージの適応範囲の検証内容

ケース1: go.mod配下でのパッケージ利用

以下のディレクトリ構成では、CディレクトリからAmiddlewareパッケージの関数を呼び出せます。

RootDir/
  ├─ A/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   │   ├─ AMiddlewareFunc
  ├─ B/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   │   ├─ BMiddlewareFunc
  ├─ C/
  │   ├─ hoge/
  │   │   ├─ main.go
  ├─ go.mod

検証結果:

  • C/hoge/main.go内でimport "RootDir/A/middleware"とすることで、AMiddlewareFuncを呼び出せました。
  • すべてのディレクトリが同じgo.modの管理下にある場合、パッケージの利用に制約は少ない。

ケース2: 各ディレクトリに独自のgo.modが存在する場合

以下のようにディレクトリごとに独自のgo.modを持たせた場合、CディレクトリからABのパッケージを利用できません。

RootDir/
  ├─ A/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   │   ├─ AMiddlewareFunc
  │   ├─ go.mod
  │   ├─ main.go
  ├─ B/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   │   ├─ BMiddlewareFunc
  │   ├─ go.mod
  │   ├─ main.go
  ├─ C/
  │   ├─ hoge/
  │   │   ├─ main.go

検証結果:

  • C/hoge/main.goからA/middlewareB/middlewareを呼び出せません。
  • ディレクトリが独立したgo.modを持つ場合、外部パッケージとして明示的にインポートする必要があります。

通化のためのディレクトリ構造の提案

以下の構造を採用することで、共通機能の管理が容易になります。

RootDir/
  ├─ common/
  │   ├─ utility.go (package common)
  ├─ A/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   ├─ main.go
  ├─ B/
  │   ├─ middleware/
  │   │   ├─ xx.go (package middleware)
  │   ├─ main.go
  ├─ C/
  │   ├─ hoge/
  │   │   ├─ main.go
  ├─ go.mod

このように、commonディレクトリを作成して共通機能を格納すると、以下のメリットがあります。

  1. コードの再利用性向上
    • 共通処理を一元化できるため、修正が容易。
  2. 名前衝突の回避
    • 各パッケージが独立しているため、衝突の心配がない。
  3. 保守性の向上
    • 共通機能が明確に分離され、開発者全員がアクセスしやすくなる。

まとめ

Goでは、go.mod以下のディレクトリでパッケージを利用できますが、名前衝突の可能性を考慮する必要があります。実務ではディレクトリ単位でパッケージを分離し、共通機能をcommonディレクトリなどに集約することが推奨されます。このような構造を採用することで、コードの保守性や再利用性を高めることができます。

Goプロジェクトを設計する際の参考になれば幸いです!