01.C# コーディング規約
このドキュメントでは、本プロジェクトでC#を記述する際の、コーディングスタイルと規約について定めます。
共通原則との関係
本規約は、01.共通コーディング原則 をC#言語に特化・具体化したものです。必ず共通原則にも目を通してください。
1.基本方針 (Guiding Principles)
- 本プロジェクトのコーディングスタイルは、基本的にMicrosoftが提唱するC#のコーディング規則に準拠します。
- 公式ガイド: C# のコーディング規則 - Microsoft Docs
- 命名ガイドライン: 名前付けのガイドライン - Microsoft Docs
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仕様 に記述します。
- 本プロジェクトでは、ソースコードの可読性を優先するため、公開APIに対するXMLドキュメントコメント (
- 通常のコメント (
//
または/* ... */
):- コードの意図が自明でない場合、複雑なロジック、将来の改善点(
// 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(...)
) を使用します。複数のfrom
やlet
が絡む複雑なクエリで、可読性が向上する場合にのみクエリ構文を許容します。 - 遅延実行 (Deferred Execution):
- 多くのLINQクエリは、
ToList()
やforeach
などで実際に列挙されるまで実行されない「遅延実行」の特性を持つことを常に意識してください。 - この特性を理解せずに同じクエリを複数回列挙すると、意図しないパフォーマンス低下(特にデータベースクエリの場合)を招く可能性があります。
- 結果を即座に評価・キャッシュしたい場合は、
ToList()
,ToArray()
,ToDictionary()
などを明示的に呼び出してください。
- 多くのLINQクエリは、
- 適切なメソッドの選択:
- 存在チェックには
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バウンドな操作(ファイルアクセス、ネットワーク通信など)や、長時間実行される可能性のある処理では、スレッドをブロックしないように
async
とawait
を積極的に利用します。 -
命名規則: 非同期メソッドには、必ず
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()
の使用禁止:- これらの同期的な待機は、デッドロックや予期しない動作の原因となるため、
Task
やValueTask
のいずれに対しても原則として使用を禁止します。 - 特に、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);
- UIコンテキスト(WPF, WinForms, MAUIなど)を持つアプリケーションから利用される可能性のある、汎用的なライブラリコード内では、デッドロックを避けるために
-
キャンセルのサポート (
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
vsclass
: 小さなデータ構造で、不変性が高く、コピーのコストが低い場合は、ヒープ割り当てを避けるためにstruct
(特にreadonly struct
)の利用を検討します。- 例外処理のコスト: パフォーマンスが非常にクリティカルなコードパスでは、例外を通常の制御フローとして使用しないでください。
try-catch
ブロックはオーバーヘッドを伴います。 - ボックス化の回避: 値型を
object
型やインターフェース型として扱う際に発生するボックス化(Boxing)は、パフォーマンスに影響を与える可能性があります。ジェネリクスを適切に利用して、不要なボックス化を避けてください。 - LINQの再確認:
- LINQは可読性に優れますが、内部的には多くのオブジェクトを生成する場合があります。
- パフォーマンスが最重要視されるループ内などでは、従来の
for
ループやforeach
ループの方が高速な場合があります。 - 必ずしもLINQを避ける必要はありませんが、ボトルネックになっている場合は、プロファイリングを行った上で最適な方法を選択してください。
9. その他
- イミュータビリティ: 可能な限り、イミュータブルな型やデータ構造の利用を検討します。
- コメントアウトされたコード: 不要なコードは残さず、Gitで履歴を管理します。
- 警告の扱い: コンパイラの警告は原則として全て修正します。意図的な抑制は理由を明記し最小範囲で。