5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

私のコーディング規約

5
Last updated at Posted at 2025-04-06

はじめに

C/C++・C#・Unity・Unreal Engine を対象としたコーディング規約のダイジェストです。
共通規約を土台に、言語別 → エンジン別の順で上書きする 3 層構成になっています(競合時はより具体的な層が優先)。

共通(common) < 言語別(C++ / C#) < エンジン別(Unity / Unreal)

共通規約

ファイル形式

項目 ルール
エンコード UTF-8(BOM 付き)
ファイル末尾 空行を 1 行
インデント スペース 4 文字
1 行の最大文字数 100 文字以内

ファイル先頭には専用書式のヘッダーを付ける。

//---------------------------------------
/// @file   ファイル名
///
/// @brief  何を行うクラスか
///
/// @date   年月日
///
/// @author 著者名
//---------------------------------------

命名規則

種別 規則 例
クラス・構造体・型 PascalCase PlayerController
関数・メソッド PascalCase(動詞始まり) GetPlayerCount()
ローカル変数 camelCase playerCount
bool 型 is / has / should / can を先頭に isAlive
定数 UPPER_SNAKE_CASE MAX_PLAYER
値渡し引数 _ + PascalCase(全言語・全エンジン) _PlayerId, _IsAlive
参照・ポインタ引数 プレフィックスなし target
列挙定数 UPPER_SNAKE_CASE(定数と同ルール)+ 最後に必ず MAX TITLE, MAIN_GAME, MAX
int GetPlayerCount();           // ✅ 動詞始まり + PascalCase.
Enemy FindEnemyById(int _Id);   // ✅ 検索処理は Find を含める.
void OnButtonClicked();         // ✅ イベントハンドラーは On で始める.
Task LoadDataAsync();           // ✅ 非同期関数は Async で終わる.

// ❌ どうやるか(実装手段)を名前に含めている.
void SaveToFile();

// ✅ 何をするか(役割)で命名する.
void Save();

名前の品質

❌ ✅ 理由
int c; int playerCount; 略語・1 文字変数は禁止(ループカウンタ i、j のみ例外)
UserInfo userInfo; UserInfo player; 型名の繰り返しは禁止
bool isNotAlive; bool isDead; 否定形の bool 名は禁止

型・値

float speed = 0.5f;         // ✅ float リテラルには必ず f をつける.
int count = GetCount();     // ✅ 型は明示する(C++ は型が自明な場合のみ auto 可).
// ❌ マジックナンバー. 30 が何を指すか読み取れない.
if (count > 30) { Reject(); }

// ✅ 意味を持つ数値は命名定数に切り出す.
const int MAX_PLAYER_COUNT = 30;

if (count > MAX_PLAYER_COUNT) { Reject(); }
// ❌ スコープ内で一度しか使わないローカル変数.
int total = basePrice * quantity + shippingFee;
return total;

// ✅ そのまま返す. 式が複雑なら関数に切り出す.
return CalculateTotalPrice(basePrice, quantity, shippingFee);

簡潔な記述の優先

条件に基づく変換・集計は制御構文ではなく LINQ / STL アルゴリズムに置き換える。

// ✅ C#: LINQ.
List<string> names = players.Where(player => player.IsAlive)
                            .Select(player => player.Name)
                            .ToList();
// ✅ C++: STL アルゴリズム.
auto it = std::find_if(players.begin(), players.end(),
                       [](const Player& player) { return player.IsAlive(); });

制御構文

  • 波括弧必須: if / else / for / while / do-while は 1 文でも必ず波括弧
  • 早期 return: 入れ子が深くなったらガード節で防ぐ
  • 条件式の統合: 同じ結果に至る条件は一つに(if (isDead || hitPoint <= 0))
  • ループ: 事前に計算できる値はループの外へ
  • 三項演算子: 1 行 100 文字未満に収まる場合のみ使用可
  • switch: default は一番上。複数行の処理は関数に切り出す。状態ごとに処理が大きく異なるなら State パターンを検討
  • while: while (true) は無限ループ専用。それ以外は for / do-while
// ✅ ガード節(早期 return)で入れ子を防ぐ.
void Process()
{
    if (!isAlive)
    {
        return;
    }
    if (!hasItem)
    {
        return;
    }

    Execute();
}

関数・クラスの設計

  • 引数が多い場合は構造体・クラスにまとめる
  • 二次元配列のインデックス計算(cells[y * width + x])は関数化する(GetCell(x, y))
  • オーバーライドする関数には override を明示する
  • メンバ変数は最小限に。他のメンバから導出できる値はプロパティ・ゲッターで返す

設計原則

原則 概要
不変性 既存オブジェクトを書き換えず、新しいオブジェクトを返す(エンジンのホットパスは例外 — 後述)
KISS 動く最もシンプルな解を選ぶ。巧妙さより明快さ
DRY 繰り返しが実際に発生したら共通化する(先回りの抽象化はしない)
YAGNI 必要になるまで作らない
SOLID 単一責任 / 開放・閉鎖 / リスコフ置換 / インターフェース分離 / 依存関係逆転
// ❌ デメテルの法則違反: 関係のないオブジェクトの内部に依存(ドット 2 回以上).
player.GetInventory().GetWeapon().GetDamage();

// ✅ 直接知っているオブジェクトのみ呼び出す.
player.GetWeaponDamage();

// ※ LINQ / STL のチェーンは対象外(同一コレクションの変換のため).

空行

位置 空行
メンバ変数・メンバ関数の宣言の前後 1 行
型・メンバ定義レベルの波括弧の前後 1 行
波括弧の内側({ 直後・} 直前) 入れない
制御構文と非制御構文の境界 1 行
制御構文どうしの間 入れない

コメント

初心者でも理解できる平易な言葉で、「なぜそうするか」が伝わるように書く。

ルール 例
1 行の末尾はピリオド // プレイヤー数.
全角と半角の間に半角スペース プレイヤー ID
-er / -or / -ar 語尾は長音符を付ける Player → プレイヤー
  • 変数・関数の宣言には概要コメントを直上に 1 行
  • 関数定義・クラス・構造体・列挙体には XML 形式(/// <summary>)
  • // TODO: はやるべきこと、// NOTE: は備忘録・注意書き

C++ 規約

モダン C++(C++17/20/23)

  • C スタイルの構文よりモダン C++ の機能を優先する
  • 型が文脈から自明な場合は auto を使ってよい
  • コンパイル時定数には constexpr
  • 構造化束縛を使う: auto [key, value] = map_entry;

リソース管理(RAII)

  • 手動の new / delete は禁止
  • 排他的所有権には std::unique_ptr、共有が本当に必要な場合のみ std::shared_ptr
  • 生の new より std::make_unique / std::make_shared

シグネチャにおける所有権

状況 使用する型
関数実行後にアクセスしない 参照で渡す
所有権を共有する(内部でコピーして保持) const shared_ptr<T>&
所有権を要求しない const weak_ptr<T>&
所有権を受け取り以降管理する shared_ptr<T>&&(ムーブ)
戻り値(所有権を譲渡しない) weak_ptr<T>(原則)
戻り値(所有権を譲渡する) shared_ptr<T>

初期化・列挙体

// ✅ 変数宣言時の初期化はブレース初期化 {} で統一する.
int tmp{};                  // = 0
std::vector<int> values{};  // = 空のベクター.
MyClass* node{};            // = nullptr

// ✅ enum class のみ使用する(スコープ無し enum は名前空間を汚染する).
// ✅ 列挙定数は定数と同じ UPPER_SNAKE_CASE、最後に必ず MAX.
enum class SceneType { TITLE, MAIN_GAME, MAX };

インクルード

自ファイルに対応するヘッダーを先頭に置き、1 行空けてから他の include を並べる。

優先順位 ルール
1 階層が浅い順(/ の数が少ないものを上に)
2 同一階層内はアルファベット順
3 相対パス(../)は絶対パスの後
  • インクルードガードは #pragma once
  • ヘッダーで使う型は前方宣言に統一し、実体が必要な .cpp でのみインクルード

引数・クラス

  • 基本型は値渡し(_PlayerId 形式)、基本型以外は const T& / T&
  • メンバ変数を変更しない関数(特にゲッター)は const メンバ関数にする
  • メンバの宣言順: public 関数 → public 変数(やむを得ない場合のみ)→ private 関数 → private 変数
  • メンバ変数は snake_case_(末尾アンダースコア)または m_ プレフィックス
  • フォーマットは clang-format に委ねる

C# 規約

型とモデル

  • null 許容参照型(nullable reference types)を有効にする
  • 不変の値的モデルには record / record struct、エンティティには class、抽象化には interface
  • dynamic は避ける
// ✅ 不変モデルは record + with 式で更新する.
public sealed record UserProfile(string Name, string Email);

public static UserProfile Rename(UserProfile _Profile, string _Name) =>
    _Profile with { Name = _Name };

変数の初期化

型カテゴリ 初期化
値型(プリミティブ・構造体・enum) default
参照型(クラス) new()
配列・コレクション [](コレクション式)

その他の規則

  • using は System 名前空間を最上部、それ以外はアルファベット順
  • private は省略してよい(public / internal は明示)
  • コンパイル時に確定する定数は const、それ以外のみ readonly
  • 大きい構造体は in T(変更しない)/ ref T(変更する)で参照渡し

アロー演算子(式形式メンバー)

// ✅ 一行で表せるメソッドとゲッタープロパティはアロー演算子で実装する.
// ✅ => は関数名・プロパティ名の次の行に記述する.
public int Count
    => items.Count;

// ❌ 一行で表せるのに本体を波括弧で書いている.
public int Count
{
    get { return items.Count; }
}

非同期

  • .Result / .Wait() などのブロッキングより async / await
  • public な非同期 API には CancellationToken を通す
  • フォーマットは dotnet format に委ねる

Unity 規約

対象: Assets/ 配下の C#。C# 規約を土台に、競合時は本規約が優先。

MonoBehaviour の作法

  • 1 ファイル 1 MonoBehaviour。ファイル名はクラス名と一致させる
  • Inspector 公開は public フィールドではなく [SerializeField] private
  • コンポーネント参照は Awake / Start でキャッシュ — Update 内で GetComponent を呼ばない
  • GetComponent + null チェックより TryGetComponent
public sealed class PlayerMovement : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    private Rigidbody cachedRigidbody;

    private void Awake()
    {
        cachedRigidbody = GetComponent<Rigidbody>();
    }
}

ライフサイクル

Awake → OnEnable → Start → Update → LateUpdate → OnDisable → OnDestroy
                              ↑
                        FixedUpdate(物理)
タイミング 用途
Awake 自己完結する初期化
Start 他コンポーネントの参照解決
  • Unity の標準メッセージ関数(Awake、Update 等)には XML コメント不要

不変性の例外

共通規約の「常に新しいオブジェクトを生成」は Unity のホットパスでは緩和される。

  • MonoBehaviour / ScriptableObject は new せず Instantiate / AddComponent / ScriptableObject.CreateInstance
  • 毎フレームのコードでは GC 圧を避けるため構造体やプールされたオブジェクトを再利用してよい
  • フレームループ外のゲームロジックは不変性を維持する

null 判定・非同期

// ❌ ?. / ?? は破棄済みオブジェクトの判定をすり抜ける.
target?.DoSomething();

// ✅ UnityEngine.Object には == で判定する.
if (target == null)
{
    return;
}
  • フレームベースの待ちはコルーチンまたは Awaitable(Unity 6+)
  • async void はイベントハンドラー以外で使わない(例外が握りつぶされる)
  • コルーチン停止・イベント購読解除は OnDisable / OnDestroy で必ず行う

パフォーマンス・リソース

  • Update 内の new・文字列連結は GC Alloc の原因 — 値が変わったときだけ更新する
  • GameObject.Find / FindObjectOfType は初期化時のみ。毎フレーム呼び出しは禁止
  • Resources.Load は使わない(レガシー API)— Addressables を優先
  • [RequireComponent] / [Header] / [Tooltip] / [Range] などの補助属性を活用する

Unreal Engine 規約

対象: UE5 の Source/ 配下の C++。C++ 規約を土台に、Epic のコーディング標準が汎用の命名規則を上書きする。

命名(Epic 規約 — 必須)

種別 規則 例
UObject 派生 U 接頭辞 UInventoryComponent
Actor 派生 A 接頭辞 APlayerCharacter
通常の構造体 F 接頭辞 FItemData
インターフェース I 接頭辞 IInteractable
列挙体 E 接頭辞 + 列挙定数は UPPER_SNAKE_CASE + MAX ESceneType { TITLE, MAIN_GAME, MAX }
bool 型 b 接頭辞 bIsAlive
関数・変数・引数 PascalCase(値渡し引数は _ を付ける) _DamageAmount

メモリ管理(GC)

std:: スマートポインタはエンジン型には使わない。

// ❌ new で生成し、delete で解放している.
UWeapon* Weapon = new UWeapon();
delete Weapon;

// ✅ 生成は NewObject / CreateDefaultSubobject / SpawnActor.
// ✅ 解放は GC に委ねる. UObject を delete しない.
UPROPERTY()
TObjectPtr<UWeapon> Weapon;

Weapon = NewObject<UWeapon>(this);
  • メンバ保持は UPROPERTY() + TObjectPtr<T>(UPROPERTY 無しの生ポインタは GC 後にダングリング)
  • 非所有参照は TWeakObjectPtr<T>
  • 非 UObject 型には TUniquePtr / TSharedPtr / TSharedRef

コンテナ・文字列

汎用 C++ Unreal Engine
std::vector<T> TArray<T>
std::map<K, V> TMap<K, V>
std::string FString(可変)/ FName(識別子)/ FText(表示・ローカライズ)
// ✅ 文字列リテラルは TEXT("...") で囲む.
FString Name = TEXT("Player");

その他の規則

  • UObject 派生型の引数は参照ではなく生ポインタで渡す(AActor* Target)
  • #include "X.generated.h" は必ず最後のインクルード
  • ログは UE_LOG(LogTemp, Warning, TEXT("..."), ...)
  • 波括弧は Allman スタイル(Epic 標準)
用途 マクロ
不変条件(回復不可能) check() / checkf()
回復可能な検証 ensure() / ensureMsgf()
Blueprint 公開イベント DECLARE_DYNAMIC_MULTICAST_DELEGATE*
C++ 内部のみのイベント DECLARE_MULTICAST_DELEGATE*

Visual Studio の設定

書式設定

  1. 「ツール」タブ →「オプション」をクリック
  2. 「テキストエディター」→「C/C++」または「C#」→「コードスタイル」→「書式設定」→「改行」を開く
  3. チェックをすべて付ける(C/C++ の場合はすべて「新しい行に追加」を選択)

コードのクリーンアップ

  1. ソリューションエクスプローラーを右クリック
  2. 「コードのクリーンアップ」→「コード クリーンアップを実行」

上記の手順を行うと、インデントや改行を設定どおりに整えてくれる。

GitHub

コミット時

コミットメッセージは Conventional Commits 形式で、以下の 8 種のうちどれかを必ず先頭に付ける。

<type>: <説明>
type 用途
feat 新しい機能の追加・既存機能の変更
fix バグを修正した場合
refactor バグ修正・機能追加を伴わないコード変更
docs ドキュメントのみの変更
test テストの追加・修正
chore ビルド設定・依存関係など機能に関係しない雑務的変更
perf パフォーマンス改善
ci CI 設定・スクリプトの変更

プルリクエスト時

  1. 題名には、大まかに何を行ったかを明記する
  2. コメントには、何のファイルにどのような変更・追加を行ったかを明記する

参考

UnityでのC#コーディング規約
Unreal EngineでのC++コーディング規約

5
8
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?