コンテンツにスキップ

01.C# コーディング規約

このドキュメントでは、本プロジェクトでC#を記述する際の、コーディングスタイルと規約について定めます。

共通原則との関係

本規約は、01.共通コーディング原則 をC#言語に特化・具体化したものです。必ず共通原則にも目を通してください。


1.基本方針 (Guiding Principles)

2.命名規則 (Naming Conventions)

  • PascalCase: クラス名、メソッド名、プロパティ名、イベント名、enum型名、enumメンバー名、定数 (const)、読み取り専用静的フィールド (static readonly)。
  • camelCase: メソッドの引数名、ローカル変数名。
  • インターフェース名: 接頭辞 I を付け、PascalCase。例: IBufferProvider
  • プライベートインスタンスフィールド: 接頭辞 _ を付け、camelCase。例: _internalBuffer
  • 非公開の静的フィールド: s_ プレフィックス (camelCase) または t_ プレフィックス (スレッド静的な場合、camelCase)。

3.レイアウトと書式設定 (Layout and Formatting)

  • コードのフォーマットは、.NET公式のフォーマットツールであるdotnet formatによって、機械的に統一します。
  • dotnet formatは、リポジトリのルートに配置された.editorconfigファイルで定義されたルールを読み込み、それに従ってコードを自動整形します。
  • 開発者は、コミット前にdotnet formatコマンドを実行するか、エディタのフォーマット機能(Visual StudioのCtrl+K, Dなど)を利用して、ファイルが規約通りであることを保証してください。

  • インデント: 半角スペース4つ。タブは使用せず、エディタの設定でスペースに自動変換することを推奨します。

  • 波括弧 {}:
    • 型定義、名前空間、メソッド、制御構文など、全てのブロックで波括弧は次の行に配置します (Allmanスタイル)。
    • 1行のステートメントでも、常に波括弧を使用します。
  • 1行の長さ: 約120文字以内を目安とします。
  • 空行: メソッド間、論理ブロック間に適切に挿入します。
  • スペース: 演算子、カンマの前後などに適切に挿入します。
  • this. の使用: 原則として、曖昧さがない限り this. は省略します。

CI/CDによる自動チェック

GitHub Actionsのワークフローにdotnet format --verify-no-changesコマンドを組み込むことで、フォーマットが規約に違反しているコードのマージを自動的にブロックします。

4.コメント (Comments)

  • XMLドキュメントコメント (///) の不使用:
    • 本プロジェクトでは、ソースコードの可読性を優先するため、公開APIに対するXMLドキュメントコメント (///) は原則として使用しません。
    • 公開APIの仕様や説明は、ソースコード内ではなく、02.設計仕様/01.API仕様 に記述します。
  • 通常のコメント (// または /* ... */):
    • コードの意図が自明でない場合、複雑なロジック、将来の改善点(// TODO:)などを説明するために使用します。
    • コードが「何をしているか」よりも「なぜそうしているのか」という設計意図や背景を説明するように心がけます。
    • コメントは、コードの変更に合わせて常に最新の状態に保ちます。
  • 要求IDとの連携:

    • 01.共通コーディング原則 で定められた通り、機能の実装やテストコードには、対応する要求IDをコメントとして明記します。
    • 特にテストコードでは、可読性と機械的な処理のしやすさを考慮し、カスタムアトリビュートの利用を推奨します。 ```csharp // REQ-AUTH-1.2: アカウントロックのロジック public void LockUserAccount(User user) { // ... }

      [Fact] [Requirement("REQ-AUTH-1.2")] // テストコードではアトリビュートを推奨 public void LockUserAccount_WhenLoginAttemptsExceeded_ShouldLockAccount() { // ... } ```

5.言語機能の利用方針

ターゲットフレームワークは .NET 6 以上であるため、最新のC#言語機能を適切に活用し、コードの簡潔性、可読性、安全性を高めることを目指します。

  • var の使用: 型が右辺から明らかで、可読性を損なわない場合は積極的に使用。
  • LINQ (Language-Integrated Query):
    • 基本方針: コレクション操作には、可読性の高いLINQを積極的に利用します。ただし、パフォーマンスがクリティカルな箇所では、その影響を理解した上で使用してください。
    • メソッド構文 vs クエリ構文: 原則として、より簡潔なメソッド構文 (.Where(...).Select(...)) を使用します。複数の fromlet が絡む複雑なクエリで、可読性が向上する場合にのみクエリ構文を許容します。
    • 遅延実行 (Deferred Execution):
      • 多くのLINQクエリは、ToList()foreach などで実際に列挙されるまで実行されない「遅延実行」の特性を持つことを常に意識してください。
      • この特性を理解せずに同じクエリを複数回列挙すると、意図しないパフォーマンス低下(特にデータベースクエリの場合)を招く可能性があります。
      • 結果を即座に評価・キャッシュしたい場合は、ToList(), ToArray(), ToDictionary() などを明示的に呼び出してください。
    • 適切なメソッドの選択:
      • 存在チェックには Count() > 0 ではなく、より効率的な Any() を使用してください。
      • 同様に、最初の要素を取得する場合は FirstOrDefault()First() を適切に使い分けます。
    • 可読性: 長いメソッドチェーンは、各メソッドの呼び出しで改行し、インデントを揃えることで可読性を高めます。
      // 良い例
      var activeUserNames = users
          .Where(user => user.IsActive)
          .OrderBy(user => user.LastName)
          .Select(user => user.Name);
      
  • プロパティ: フィールドは原則 private、外部アクセスはプロパティ経由。
  • using ステートメント/宣言: IDisposable リソースに必須。
  • null許容参照型 (#nullable enable): プロジェクト全体で有効化します。
  • 式形式メンバー: 単純なメソッドやプロパティに利用可。
  • タプル: 複数値を返す場合に適切に利用。
  • パターンマッチング: 型チェックや条件分岐に積極的に活用。
  • レコード型 (record class, record struct): データ保持、値の等価性、イミュータビリティが重要な場合に推奨。
  • init アクセサ: オブジェクト初期化時のみ設定可能なプロパティに使用。
  • ファイルスコープ名前空間: C# 10.0以降。新しいファイルでは積極的に採用します。
  • global using ディレクティブ: C# 10.0以降。プロジェクト全体で共通して使用する名前空間は、専用のファイル (例: GlobalUsings.cs) にまとめて定義します。

6.エラー処理と例外 (Error Handling and Exceptions)

  • APIの境界では、不正な引数などに対して標準的な例外 (ArgumentNullException等) をスローします。
  • エラーハンドリングに関する詳細な戦略は、今後専用のドキュメント(例: 03_エラーハンドリング戦略.md)で定義・拡充される予定です。

7.非同期処理 (async/await)

  • 基本方針: I/Oバウンドな操作(ファイルアクセス、ネットワーク通信など)や、長時間実行される可能性のある処理では、スレッドをブロックしないように asyncawait を積極的に利用します。

  • 命名規則: 非同期メソッドには、必ず Async 接尾辞を付けます。

    • 例: public Task<User> GetUserAsync(int id);
  • 戻り値の型:

    • 原則として Task / Task<T> を使用:
      • ほとんどのアプリケーションコードでは、シンプルで安全な Task / Task<T> を使用します。
    • ValueTask / ValueTask<T> の利用を検討するケース:
      • パフォーマンスが非常にクリティカルなライブラリコードで、かつ、メソッドが同期的に完了する可能性が高い場合に、不要なメモリアロケーションを避けるために ValueTask / ValueTask<T> の利用を検討します。
      • 例: 内部にバッファやキャッシュを持ち、データが既に手元にある場合。
    • ValueTask の注意点:
      • ValueTask構造体(struct) であり、意図しない変更を防ぐために、readonly 修飾子を付けて宣言することを推奨します。これにより、ValueTask インスタンスの不変性が保証され、誤って値を変更してしまうリスクを低減できます。
      • ValueTask一度しか await できません。
      • 複数回 await する必要がある場合や、複数のコンシューマーに渡す場合は、.AsTask() を呼び出して Task に変換してください。
    • .Result.GetAwaiter().GetResult() の使用禁止:
      • これらの同期的な待機は、デッドロックや予期しない動作の原因となるため、TaskValueTask のいずれに対しても原則として使用を禁止します
      • 特に、UIアプリケーションやASP.NET Coreアプリケーションでは、デッドロックを引き起こす可能性が高いため、絶対に避けてください。
    • async void の禁止:
      • イベントハンドラなど、呼び出し元が待機する必要のない特殊なケースを除き、async void の使用は原則として禁止します。
      • async void メソッド内で発生した例外は、呼び出し元でキャッチできず、アプリケーションのクラッシュに繋がる可能性があります。
  • ConfigureAwait(false) の利用:

    • UIコンテキスト(WPF, WinForms, MAUIなど)を持つアプリケーションから利用される可能性のある、汎用的なライブラリコード内では、デッドロックを避けるために await の後には必ず .ConfigureAwait(false) を付けます。
    • これにより、await 後の処理が元の同期コンテキストに戻る必要がなくなり、パフォーマンスと安定性が向上します。
      // ライブラリコードでの良い例
      var content = await httpClient.GetStringAsync(url).ConfigureAwait(false);
      
  • キャンセルのサポート (CancellationToken):

    • 完了までに時間がかかる可能性のある非同期操作には、CancellationToken を引数として受け取り、キャンセルの要求を適切に処理できるように設計することを強く推奨します。
      public async Task LongRunningOperationAsync(CancellationToken cancellationToken)
      {
          // ...
          cancellationToken.ThrowIfCancellationRequested();
          // ...
      }
      
  • Task.Run との使い分け:

    • async/await は主に I/Oバウンドな処理の非同期化に用います。
    • CPUを長時間占有するCPUバウンドな処理をUIスレッドなどからオフロードする場合は、Task.Run を使用して、処理をバックグラウンドのスレッドプールに委譲します。

8.パフォーマンスに関する考慮事項 (Performance Considerations)

  • 文字列結合: ループ内で多数の文字列を結合する場合は、+演算子や文字列補間ではなく、StringBuilderクラスを使用します。
  • struct vs class: 小さなデータ構造で、不変性が高く、コピーのコストが低い場合は、ヒープ割り当てを避けるためにstruct(特にreadonly struct)の利用を検討します。
  • 例外処理のコスト: パフォーマンスが非常にクリティカルなコードパスでは、例外を通常の制御フローとして使用しないでください。try-catchブロックはオーバーヘッドを伴います。
  • ボックス化の回避: 値型をobject型やインターフェース型として扱う際に発生するボックス化(Boxing)は、パフォーマンスに影響を与える可能性があります。ジェネリクスを適切に利用して、不要なボックス化を避けてください。
  • LINQの再確認:
    • LINQは可読性に優れますが、内部的には多くのオブジェクトを生成する場合があります。
    • パフォーマンスが最重要視されるループ内などでは、従来のforループやforeachループの方が高速な場合があります。
    • 必ずしもLINQを避ける必要はありませんが、ボトルネックになっている場合は、プロファイリングを行った上で最適な方法を選択してください。

9. その他

  • イミュータビリティ: 可能な限り、イミュータブルな型やデータ構造の利用を検討します。
  • コメントアウトされたコード: 不要なコードは残さず、Gitで履歴を管理します。
  • 警告の扱い: コンパイラの警告は原則として全て修正します。意図的な抑制は理由を明記し最小範囲で。