C#コーディング規約の作り方|保守性を高める命名・フォーマット・設計ルールを実例で解説
はじめに
C#でチーム開発を進めるとき、コードの品質を安定させるために欠かせないのが「コーディング規約」です。
コーディング規約がない状態では、メンバーごとに命名、インデント、クラス設計、例外処理、コメントの書き方がばらばらになり、コードレビューや保守作業に余計な時間がかかります。最初は小さな違いに見えても、プロジェクトが成長するにつれて、可読性の低下、修正漏れ、属人化、バグの混入につながります。
C#はMicrosoft公式のコーディング規則や.NETの設計ガイドラインが整っているため、ゼロから独自ルールを作る必要はありません。まずは公式の考え方をベースにし、プロジェクトの規模、開発体制、使用フレームワーク、運用方針に合わせて必要なルールを追加するのが現実的です。
この記事では、C#コーディング規約の作り方を、命名規則、フォーマット規約、設計ルール、実装ルール、テスト、レビュー、自動化、テンプレートまで実例付きで解説します。
1. C#コーディング規約とは?チーム開発で必要になる理由
C#コーディング規約とは、C#のコードを書くときに守るべきルールをまとめたものです。対象は、命名、インデント、改行、コメント、クラス設計、例外処理、非同期処理、テストコード、レビュー観点など多岐にわたります。
重要なのは、コーディング規約は「誰かの好みを押し付けるための文書」ではないという点です。目的は、チーム全体で読みやすく、変更しやすく、バグを見つけやすいコードを維持することです。
1-1. コーディング規約の目的は「好みの統一」ではなく保守性の向上
コーディング規約を作るときにありがちな失敗は、「波括弧はどこに置くべきか」「privateフィールドにアンダースコアを付けるべきか」といった見た目の議論だけに時間を使ってしまうことです。
もちろん見た目の統一も大切ですが、本来の目的は保守性の向上です。
たとえば、次のような状態を減らすことが目的になります。
同じ意味の変数名が複数の書き方で存在する
似た処理が複数のクラスに重複している
例外処理の方針が場所によって違う
publicメソッドが増えすぎて影響範囲が読めない
コメントが古くなって実装と矛盾している
レビューで毎回同じ指摘が繰り返される
良いC#コーディング規約は、開発者の自由を不必要に制限するものではありません。判断に迷いやすい部分を明文化し、チームが本質的な設計や仕様の議論に集中できるようにするためのものです。
1-2. C#で規約を作るときに押さえるべき対象範囲
C#のコーディング規約では、少なくとも次の範囲を対象にすると実用的です。
| 分類 | 主な内容 |
|---|---|
| 命名規則 | クラス名、メソッド名、変数名、定数名、インターフェース名 |
| フォーマット | インデント、改行、空行、波括弧、usingの並び順 |
| 設計ルール | クラスの責務、public範囲、依存関係、継承、インターフェース |
| 実装ルール | 例外処理、null、async/await、LINQ、コメント |
| テスト規約 | テスト名、テストデータ、モック、テスト対象 |
| レビュー規約 | 指摘基準、自動チェック対象、人が確認する観点 |
| 自動化 | .editorconfig、Analyzer、dotnet format、CI/CD |
すべてを最初から細かく決める必要はありません。まずは命名、フォーマット、例外処理、nullの扱い、レビュー観点など、チームで認識がずれやすい部分から始めるとよいでしょう。
1-3. 命名・フォーマット・設計ルールの違い
コーディング規約は、大きく分けると「命名」「フォーマット」「設計」の3種類に分類できます。
命名規則は、名前の付け方に関するルールです。たとえば、クラス名はPascalCase、ローカル変数はcamelCase、インターフェース名はIから始める、といったルールです。
フォーマット規約は、コードの見た目に関するルールです。インデント、スペース、改行、波括弧、空行、1行の長さなどが該当します。多くは自動整形ツールで統一できます。
設計ルールは、クラスやメソッドの責務、依存関係、公開範囲、継承の使い方など、コードの構造に関するルールです。自動チェックが難しいため、レビューや設計方針として明文化することが重要です。
この3つを混同すると、規約が読みにくくなります。規約ドキュメントでは分類を分け、どのルールが機械的にチェックできるのか、どのルールがレビューで判断するものなのかを明確にしましょう。
1-4. 規約がないC#プロジェクトで起こりやすい問題
C#コーディング規約がないプロジェクトでは、次のような問題が起こりやすくなります。
C#public class user_service
{
public string get_user_name(int ID)
{
var u = Find(ID);
if (u == null) { return ""; }
return u.Name;
}
}
このコードは動作するかもしれませんが、C#の一般的な命名規則から外れています。クラス名、メソッド名、引数名が統一されておらず、戻り値として空文字を返す方針も明確ではありません。
規約を適用すると、たとえば次のようになります。
C#public class UserService
{
public string? GetUserName(int userId)
{
var user = Find(userId);
return user?.Name;
}
}
名前から役割が読み取りやすくなり、nullを返す可能性も型で表現できます。小さな違いに見えますが、プロジェクト全体で統一されると、コードの読みやすさと保守性は大きく変わります。
2. C#コーディング規約を作る前に決めるべき基本方針
C#コーディング規約を作る前に、まず基本方針を決めておくことが重要です。最初に方針がないまま細かいルールを増やすと、規約が膨らみすぎて運用されなくなります。
規約は「完璧な文書」ではなく、「チームが継続的に使える基準」です。守れないほど細かい規約より、重要な判断基準が明確で、ツールによって自動化しやすい規約を目指しましょう。
2-1. Microsoft公式のC#コーディング規則をベースにする
C#コーディング規約を作る場合、まずMicrosoft公式のC#コーディング規則をベースにするのがおすすめです。
C#では、PascalCase、camelCase、インターフェース名のIプレフィックス、非同期メソッド名のAsyncサフィックスなど、広く使われている慣習があります。これらに合わせることで、新しく参加したメンバーもコードを理解しやすくなります。
独自ルールを作ること自体は問題ありませんが、理由なく一般的な慣習から外れると、学習コストやレビューコストが増えます。特別な事情がない限り、公式・標準的な慣習を採用し、必要な部分だけプロジェクト向けに補足する方針がよいでしょう。
2-2. プロジェクト特性に合わせて追加・変更する
公式ルールだけでは、実際のプロジェクトで必要な判断まではカバーできません。
たとえば、Web API、業務システム、ゲーム開発、デスクトップアプリ、ライブラリ開発では、重視するポイントが異なります。
Web APIであれば、Controller、Service、Repository、DTO、Entityの責務分担を明確にしたほうがよいでしょう。ライブラリ開発であれば、public APIの互換性、XMLドキュメントコメント、例外の種類などを厳格に決める必要があります。
プロジェクト特性に合わせて、次のような観点を追加します。
レイヤーごとの責務
DTO、Entity、Modelの命名
APIレスポンスの設計
例外をどの層で処理するか
ログ出力のルール
設定値の管理方法
テストコードの書き方
Nullable参照型の運用方針
C#コーディング規約は、一般論だけでなく、そのプロジェクトの設計思想を反映させることで実用性が高まります。
2-3. ルールは「必須」「推奨」「禁止」に分ける
すべてのルールを同じ強さで扱うと、レビューでの指摘が重くなりすぎます。規約は、重要度に応じて分類しましょう。
| 区分 | 意味 | 例 |
|---|---|---|
| 必須 | 原則として必ず守る | publicメンバーはPascalCaseにする |
| 推奨 | できるだけ守る | メソッドは短く保つ |
| 禁止 | 原則として使わない | 空のcatchブロックは禁止 |
たとえば、次のように書くとレビューで判断しやすくなります。
必須: クラス名、メソッド名、プロパティ名はPascalCaseにする。
推奨: メソッドは1つの責務に絞り、長くなりすぎる場合は分割を検討する。
禁止: catchブロックで例外を握りつぶしてはならない。
「必須」と「推奨」を分けることで、レビュー時に必ず修正すべき指摘と、改善提案として扱う指摘を区別できます。
2-4. 例外ルールと判断基準も明文化する
コーディング規約には例外が必要です。すべてのケースを一律に縛ると、かえって不自然なコードになります。
たとえば、通常はvarを使わない方針でも、右辺から型が明らかな場合は使用を許可する、というルールが考えられます。
C#var user = new User();
var orders = new List<Order>();
一方で、右辺から型が読み取りにくい場合は明示的な型を書くとよいでしょう。
C#IEnumerable<Order> orders = repository.GetActiveOrders();
例外ルールは、単に「場合による」と書くのではなく、判断基準まで明文化することが大切です。
varは右辺から型が明らかな場合に使用してよい。
戻り値の型が読み手にとって重要な場合は明示的な型を書く。
このように書くことで、レビュー時の議論を減らせます。
2-5. レビューで指摘しやすい粒度に整理する
コーディング規約は、レビューで使いやすい粒度に整理する必要があります。
悪い例は、次のような抽象的すぎるルールです。
読みやすいコードを書くこと。
これは正しい方針ですが、レビューで指摘しにくいルールです。より実用的にするなら、次のように具体化します。
メソッド名は処理内容が分かる動詞または動詞句にする。
複雑な条件式は意味のある変数またはメソッドに切り出す。
ネストが深くなる場合は早期returnを検討する。
レビューで使える規約にするには、「何を見ればよいか」「違反しているかどうか」「どう直せばよいか」が分かる形にすることが重要です。
3. C#の命名規則|クラス・メソッド・変数名の付け方
命名規則は、C#コーディング規約の中でも特に重要です。名前が分かりやすければ、コードの意図をコメントなしで読み取れるようになります。逆に、名前が曖昧だと、実装を追わなければ意味が分からず、保守性が下がります。
C#では、型やメンバーに応じてPascalCaseとcamelCaseを使い分けるのが一般的です。
3-1. C#でよく使われる命名スタイル一覧
C#でよく使われる命名スタイルは次のとおりです。
| スタイル | 例 | 主な用途 |
|---|---|---|
| PascalCase | UserService | クラス、メソッド、プロパティ |
| camelCase | userName | ローカル変数、引数 |
| _camelCase | _userRepository | privateフィールド |
| UPPER_CASE | MAX_COUNT | C#では一般的にはあまり使わない |
| I + PascalCase | IUserRepository | インターフェース |
C#では、JavaScriptやPythonで見られるsnake_caseは通常使いません。定数についても、C#ではMaxRetryCountのようにPascalCaseを使うことが一般的です。
3-2. クラス・構造体・列挙型はPascalCaseにする
クラス、構造体、列挙型の名前はPascalCaseにします。
良い例は次のとおりです。
C#public class UserService
{
}
public struct Money
{
}
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Cancelled
}
避けるべき例は次のような名前です。
C#public class user_service
{
}
public class userservice
{
}
public enum order_status
{
}
型名はコード全体で何度も参照されるため、命名のばらつきがあると可読性に大きく影響します。名詞または名詞句を使い、役割が分かる名前にしましょう。
3-3. メソッド・プロパティ・イベントはPascalCaseにする
メソッド、プロパティ、イベントもPascalCaseにします。
C#public class OrderService
{
public int MaxRetryCount { get; set; }
public event EventHandler? OrderCompleted;
public Order GetOrder(int orderId)
{
// ...
}
}
メソッド名は、処理内容が分かる動詞または動詞句にします。
C#public User FindUser(int userId)
public void SaveOrder(Order order)
public bool CanCancel(Order order)
public decimal CalculateTotalPrice(Order order)
Process、Execute、Handleのような名前は便利ですが、意味が広すぎるため多用するとコードの意図が分かりにくくなります。必要に応じて、ProcessPayment、ExecuteImportJob、HandleUserRegisteredEventのように具体化しましょう。
3-4. ローカル変数・引数はcamelCaseにする
ローカル変数とメソッド引数はcamelCaseにします。
C#public Order GetOrder(int orderId)
{
var activeOrders = GetActiveOrders();
var targetOrder = activeOrders.FirstOrDefault(order => order.Id == orderId);
return targetOrder;
}
変数名は短すぎても長すぎても読みにくくなります。
避けるべき例は次のとおりです。
C#var x = GetUser();
var data = GetOrders();
var list = GetActiveCustomers();
一時的なループ変数としてiやjを使うのは問題ありませんが、業務上の意味を持つ値には具体的な名前を付けます。
C#foreach (var activeCustomer in activeCustomers)
{
SendNotification(activeCustomer);
}
data、info、value、tempのような名前は、意味が曖昧になりやすいため注意が必要です。
3-5. インターフェース名はIプレフィックスを付ける
C#では、インターフェース名にIプレフィックスを付けるのが一般的です。
C#public interface IUserRepository
{
User? FindById(int userId);
}
public class UserRepository : IUserRepository
{
public User? FindById(int userId)
{
// ...
}
}
インターフェースは、実装ではなく契約を表します。そのため、名前は実装方法ではなく役割を表すものにします。
良い例は次のとおりです。
C#IUserRepository
INotificationSender
IPaymentGateway
IClock
避けたい例は次のとおりです。
C#IUserRepositoryImpl
ISqlUserRepository
ICommon
IManager
ただし、実装がSQL専用であること自体が契約として重要な場合は、ISqlConnectionFactoryのような名前が適切な場合もあります。
3-6. privateフィールドの命名ルールを統一する
privateフィールドの命名は、C#コーディング規約で議論になりやすいポイントです。代表的なスタイルは次の2つです。
C#private readonly IUserRepository _userRepository;
または
C#private readonly IUserRepository userRepository;
どちらを採用しても構いませんが、チーム内で統一することが重要です。
実務では、privateフィールドに_camelCaseを使うプロジェクトも多くあります。コンストラクター引数とフィールドを区別しやすいからです。
C#public class UserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
}
規約には次のように書くと明確です。
privateフィールドは_camelCaseとする。
readonlyにできる依存関係フィールドはreadonlyを付ける。
フィールド名にm_や型名プレフィックスを付ける古いスタイルは、現在のC#では避けるのが一般的です。
3-7. bool型・非同期メソッド・定数の命名例
bool型の変数やプロパティは、真偽値であることが分かる名前にします。
C#public bool IsActive { get; set; }
public bool HasPermission { get; set; }
public bool CanCancel { get; set; }
public bool ShouldRetry { get; set; }
避けたい例は次のとおりです。
C#public bool Active { get; set; }
public bool Permission { get; set; }
public bool Retry { get; set; }
非同期メソッドはAsyncサフィックスを付けます。
C#public async Task<User?> FindUserAsync(int userId)
{
return await _userRepository.FindByIdAsync(userId);
}
定数はPascalCaseにします。
C#private const int MaxRetryCount = 3;
private const string DefaultDateFormat = "yyyy-MM-dd";
C#では、定数だからといってMAX_RETRY_COUNTのようなUPPER_CASEにする必要はありません。プロジェクトで特別な理由がない限り、PascalCaseに統一するとよいでしょう。
3-8. 避けるべき命名|省略語・曖昧な名前・ハンガリアン記法
C#の命名で避けるべき代表例は、省略語、曖昧な名前、ハンガリアン記法です。
省略語は、チーム全員に意味が伝わる場合を除いて避けます。
C#// 避けたい例
var usr = GetUser();
var ordCnt = GetOrderCount();
// 良い例
var user = GetUser();
var orderCount = GetOrderCount();
曖昧な名前も避けます。
C#// 避けたい例
var data = GetCustomers();
var result = CalculateTotalPrice(order);
// 良い例
var customers = GetCustomers();
var totalPrice = CalculateTotalPrice(order);
ハンガリアン記法も、現在のC#では基本的に不要です。
C#// 避けたい例
string strName;
int intCount;
bool bIsActive;
// 良い例
string name;
int count;
bool isActive;
型はIDEやコンパイラが把握できるため、変数名に型情報を埋め込む必要はありません。名前には型ではなく、役割や意味を表現しましょう。
4. C#のフォーマット規約|読みやすいコードに統一するルール
フォーマット規約は、コードの見た目を統一するためのルールです。インデント、スペース、改行、波括弧、空行、usingの並び順などが対象です。
フォーマットの議論は好みに寄りやすいため、できるだけツールで自動化することが大切です。Visual Studio、Rider、VS Code、.editorconfig、dotnet formatを使えば、多くのルールを機械的に統一できます。
4-1. インデント・スペース・改行の基本ルール
C#では、インデントにスペース4つを使うのが一般的です。
C#public class UserService
{
public User? FindUser(int userId)
{
if (userId <= 0)
{
return null;
}
return _userRepository.FindById(userId);
}
}
規約では、次のような基本ルールを決めておきます。
インデントはスペース4つとする。
タブ文字は使用しない。
演算子の前後にはスペースを入れる。
カンマの後にはスペースを入れる。
不要な連続空行は避ける。
悪い例は次のとおりです。
C#if(userId<=0){
return null;
}
良い例は次のとおりです。
C#if (userId <= 0)
{
return null;
}
小さな違いですが、プロジェクト全体で統一されると読みやすさが大きく変わります。
4-2. 波括弧の位置とブロックの書き方
C#では、波括弧を次の行に置くスタイルが一般的です。
C#if (isActive)
{
Activate();
}
else
{
Deactivate();
}
1行だけのifでも、波括弧を省略しないルールにしておくと、後から処理を追加したときのバグを防ぎやすくなります。
C#// 推奨
if (user == null)
{
return;
}
// 非推奨
if (user == null)
return;
規約には次のように書けます。
if、for、foreach、while、usingなどのブロックでは、処理が1行でも波括弧を省略しない。
波括弧はC#標準スタイルに合わせて次の行に配置する。
ただし、プロジェクトでファイルスコープ名前空間や簡潔なプロパティを使う場合は、例外として1行表記を許可してもよいでしょう。
4-3. usingディレクティブの並び順と不要usingの削除
usingディレクティブは、ファイルの先頭にまとめます。不要なusingは削除し、並び順を統一します。
C#using System;
using System.Collections.Generic;
using System.Linq;
using MyApp.Domain.Users;
using MyApp.Infrastructure.Database;
一般的には、System名前空間を先に置き、その後に自社・プロジェクト固有の名前空間を置きます。ただし、IDEや.editorconfigの設定で自動整理できるため、手作業で管理するよりもツールに任せるのがよいでしょう。
規約例は次のとおりです。
不要なusingは削除する。
usingはファイル先頭にまとめる。
System名前空間を先に並べる。
usingの整理はIDEまたはdotnet formatで自動化する。
C# 10以降ではglobal usingも利用できます。プロジェクト全体で使う名前空間はGlobalUsings.csなどにまとめると、各ファイルのusingを減らせます。
C#global using System;
global using System.Collections.Generic;
global using System.Linq;
4-4. varを使う場面・明示的な型を書く場面
varを使うかどうかは、C#コーディング規約で必ず決めておきたい項目です。
varは型推論によってコードを簡潔にできますが、使いすぎると型が分かりにくくなることがあります。
varを使ってよい例は次のとおりです。
C#var user = new User();
var orders = new List<Order>();
var totalPrice = order.CalculateTotalPrice();
右辺から型が明らかな場合や、型名を繰り返すと冗長になる場合はvarが有効です。
一方、次のような場合は明示的な型を書いたほうが読みやすいことがあります。
C#IEnumerable<Order> orders = repository.GetActiveOrders();
DateTime currentDate = clock.Now;
decimal totalPrice = calculator.Calculate(order);
規約例は次のとおりです。
右辺から型が明らかな場合はvarを使用してよい。
戻り値の型が読み手にとって重要な場合は明示的な型を書く。
匿名型を使う場合はvarを使用する。
重要なのは、varを全面禁止または全面強制にすることではなく、読みやすさを基準に判断できるルールにすることです。
4-5. 1行の長さ・空行・メソッド分割の目安
1行が長すぎるコードは、横スクロールが必要になり、レビューしにくくなります。厳密な文字数はプロジェクトによって異なりますが、100〜120文字程度を目安にするとよいでしょう。
C#var activeUsers = users
.Where(user => user.IsActive)
.OrderBy(user => user.Name)
.ToList();
空行は、処理のまとまりを分けるために使います。
C#public void Register(User user)
{
Validate(user);
var existingUser = _userRepository.FindByEmail(user.Email);
if (existingUser != null)
{
throw new InvalidOperationException("User already exists.");
}
_userRepository.Save(user);
}
メソッドが長くなりすぎる場合は、責務が増えている可能性があります。ただし、「何行を超えたら必ず分割」と機械的に決めるよりも、処理のまとまりで判断するほうが実用的です。
規約例は次のとおりです。
1行は原則120文字以内を目安とする。
空行は処理のまとまりを分けるために使用する。
メソッドが長くなった場合は、責務単位で分割できないか検討する。
4-6. プロパティ・ラムダ式・LINQの整形ルール
プロパティは、簡潔に書ける場合は式形式を使っても構いません。
C#public string FullName => $"{FirstName} {LastName}";
ただし、処理が複雑になる場合は通常のブロック形式にしたほうが読みやすくなります。
C#public decimal TotalPrice
{
get
{
var subtotal = CalculateSubtotal();
var tax = CalculateTax(subtotal);
return subtotal + tax;
}
}
ラムダ式やLINQは、短い場合は1行で書けます。
C#var activeUsers = users.Where(user => user.IsActive).ToList();
条件が複数ある場合は、改行してチェーンを読みやすくします。
C#var activeUsers = users
.Where(user => user.IsActive)
.Where(user => user.LastLoginAt >= threshold)
.OrderBy(user => user.Name)
.ToList();
ただし、LINQが複雑になりすぎる場合は、途中の条件をメソッドや変数に切り出します。
C#var targetUsers = users
.Where(IsNotificationTarget)
.OrderBy(user => user.Name)
.ToList();
private static bool IsNotificationTarget(User user)
{
return user.IsActive && user.HasEmail && !user.IsLocked;
}
4-7. Visual StudioやEditorConfigで自動整形する方法
フォーマット規約は、人が手作業で守るよりも自動化するべきです。
C#では、.editorconfigを使うことで、インデント、改行、命名規則、varの使用方針などをプロジェクト単位で共有できます。
簡単な例は次のとおりです。
INIroot = true
[*.cs]
indent_style = space
indent_size = 4
end_of_line = crlf
insert_final_newline = true
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true:suggestion
.editorconfigをリポジトリに含めることで、Visual Studio、Rider、VS Codeなど複数のエディタで同じルールを共有できます。
また、dotnet formatを使えば、コマンドラインやCI/CDで自動整形を実行できます。
Bashdotnet format
フォーマットに関するレビュー指摘を減らすためにも、規約とツール設定はセットで管理しましょう。
5. C#の設計ルール|保守性を高めるクラス設計・責務分割
設計ルールは、C#コーディング規約の中でも特に保守性に直結します。命名やフォーマットはツールである程度統一できますが、クラスの責務や依存関係は人が判断する必要があります。
設計ルールでは、「どのようなコードを良い設計とするか」「どのような状態を避けるか」をチームで共有することが重要です。
5-1. 1クラス1責務を意識した設計にする
クラスは、1つの明確な責務を持つように設計します。責務が多すぎるクラスは、変更理由が増え、影響範囲が広がります。
悪い例は次のとおりです。
C#public class UserService
{
public void Register(User user)
{
// バリデーション
// データベース保存
// メール送信
// ログ出力
// 外部API通知
}
}
このクラスは、ユーザー登録だけでなく、保存、通知、ログ、外部連携まで抱えています。変更理由が多く、テストもしにくくなります。
責務を分けると、次のようになります。
C#public class UserRegistrationService
{
private readonly IUserRepository _userRepository;
private readonly INotificationSender _notificationSender;
public UserRegistrationService(
IUserRepository userRepository,
INotificationSender notificationSender)
{
_userRepository = userRepository;
_notificationSender = notificationSender;
}
public void Register(User user)
{
Validate(user);
_userRepository.Save(user);
_notificationSender.SendWelcomeMessage(user);
}
private static void Validate(User user)
{
if (string.IsNullOrWhiteSpace(user.Email))
{
throw new ArgumentException("Email is required.", nameof(user));
}
}
}
クラスの責務を小さくすると、変更しやすく、テストしやすく、再利用しやすいコードになります。
5-2. publicメンバーを増やしすぎない
publicメンバーは、外部から利用される契約です。一度公開すると、変更や削除が難しくなります。そのため、必要以上にpublicにしないことが重要です。
C#public class Order
{
public decimal CalculateTotalPrice()
{
return CalculateSubtotal() + CalculateTax();
}
private decimal CalculateSubtotal()
{
// ...
}
private decimal CalculateTax()
{
// ...
}
}
外部から呼び出す必要がないメソッドはprivateにします。テストのためだけにprivateメソッドをpublicにするのは避けましょう。privateメソッドを直接テストしたくなる場合は、クラスの責務が大きすぎる可能性があります。
規約例は次のとおりです。
メンバーのアクセス修飾子は必要最小限にする。
外部から利用されないメソッドはprivateにする。
テスト目的だけでアクセス範囲を広げない。
5-3. 継承よりコンポジションを優先する
C#では継承を使えますが、安易な継承はクラス間の結合を強めます。共通処理を再利用したいだけなら、継承ではなくコンポジションを検討しましょう。
継承に頼りすぎた例です。
C#public class EmailNotificationService : NotificationServiceBase
{
}
継承元の実装に強く依存すると、基底クラスの変更が派生クラス全体に影響します。
コンポジションを使う例です。
C#public class EmailNotificationService
{
private readonly IMessageFormatter _messageFormatter;
public EmailNotificationService(IMessageFormatter messageFormatter)
{
_messageFormatter = messageFormatter;
}
public void Send(User user)
{
var message = _messageFormatter.Format(user);
// メール送信
}
}
継承を禁止する必要はありませんが、規約では次のように判断基準を明確にするとよいでしょう。
共通処理の再利用だけを目的とした継承は避ける。
差し替え可能な振る舞いはインターフェースとコンポジションで表現する。
継承を使う場合は、is-a関係が明確な場合に限定する。
5-4. インターフェースを使うべき場面・避けるべき場面
インターフェースは、依存関係を抽象化し、テストや差し替えをしやすくするために有効です。
使うべき場面の例は次のとおりです。
外部サービスやデータベースアクセスを抽象化する
テストでモックに差し替えたい
複数の実装が存在する
ドメイン層をインフラ層から分離したい
C#public interface IClock
{
DateTime Now { get; }
}
public class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}
一方で、実装が1つしかなく、差し替える予定もなく、テスト上も不要な場合に、機械的にインターフェースを作るとコードが増えすぎます。
C#public interface IUserNameFormatter
{
string Format(User user);
}
public class UserNameFormatter : IUserNameFormatter
{
public string Format(User user)
{
return $"{user.LastName} {user.FirstName}";
}
}
このような単純な処理では、インターフェースが過剰になることもあります。
規約では、次のように書くと実用的です。
外部依存、複数実装、テストでの差し替えが必要な場合はインターフェースを使用する。
単一実装で差し替え予定がないクラスに対して、機械的にインターフェースを作成しない。
5-5. staticクラス・拡張メソッドの利用基準
staticクラスや拡張メソッドは便利ですが、使いすぎると依存関係が見えにくくなります。
staticクラスが向いているのは、状態を持たない純粋なユーティリティ処理です。
C#public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date)
{
return date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
}
一方で、外部サービス、設定、ログ、データベースに依存する処理をstaticにすると、テストしにくくなります。
C#// 避けたい例
public static class UserHelper
{
public static User GetCurrentUser()
{
// DBやHTTPコンテキストに依存
}
}
規約例は次のとおりです。
staticクラスは状態を持たない純粋な処理に限定する。
外部依存を持つ処理はDI可能なクラスとして実装する。
拡張メソッドは既存型の表現力を高める用途に限定し、業務ロジックを詰め込みすぎない。
5-6. nullを扱うルールとNullable参照型の活用
C#では、null参照によるバグを防ぐためにNullable参照型を活用できます。
プロジェクトでは、Nullable参照型を有効にすることを推奨します。
XML<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
nullを返す可能性がある場合は、型で明示します。
C#public User? FindById(int userId)
{
return _users.FirstOrDefault(user => user.Id == userId);
}
呼び出し側では、nullチェックを行います。
C#var user = _userRepository.FindById(userId);
if (user == null)
{
return NotFound();
}
nullを許容しない場合は、コンストラクターやメソッドの入口で検証します。
C#public UserService(IUserRepository userRepository)
{
_userRepository = userRepository
?? throw new ArgumentNullException(nameof(userRepository));
}
規約例は次のとおりです。
Nullable参照型を有効にする。
nullを返す可能性がある戻り値は?で明示する。
引数にnullを許可しない場合は入口で検証する。
nullの意味が曖昧な場合は、結果型や例外の利用を検討する。
5-7. 依存関係を疎結合に保つためのルール
依存関係が密結合になると、変更やテストが難しくなります。C#では、依存性注入を使って疎結合に保つ設計がよく使われます。
悪い例は、クラス内で依存オブジェクトを直接生成することです。
C#public class OrderService
{
private readonly OrderRepository _orderRepository = new OrderRepository();
}
この書き方では、テスト時にOrderRepositoryを差し替えにくくなります。
コンストラクター注入を使うと、依存関係が明示されます。
C#public class OrderService
{
private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
}
規約例は次のとおりです。
外部依存はコンストラクター注入で受け取る。
クラス内で具象クラスを直接newしない。
依存方向は上位レイヤーから下位レイヤーに一方向となるように設計する。
6. C#の実装ルール|バグを減らす書き方の基準
実装ルールは、日々のコードを書くときにバグを減らすための基準です。例外処理、非同期処理、マジックナンバー、条件分岐、LINQ、コメントなどを対象にします。
命名やフォーマットよりも判断が必要な項目が多いため、良い例と悪い例をセットで示すと運用しやすくなります。
6-1. 例外処理の書き方とcatchすべき例外の範囲
例外処理では、必要以上に広い例外をcatchしないことが重要です。
避けたい例です。
C#try
{
Import();
}
catch (Exception)
{
}
このコードは例外を握りつぶしており、障害の原因が分からなくなります。
適切な例は次のとおりです。
C#try
{
Import();
}
catch (FileNotFoundException ex)
{
_logger.LogWarning(ex, "Import file was not found.");
throw;
}
また、例外を再スローする場合はthrow ex;ではなくthrow;を使います。
C#catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred.");
throw;
}
規約例は次のとおりです。
catchする例外は、処理可能な範囲に限定する。
例外を握りつぶしてはならない。
ログ出力後に再スローする場合はthrow;を使用する。
catch (Exception) はアプリケーション境界や共通エラーハンドリングなど必要な場所に限定する。
6-2. async/awaitの命名・戻り値・ConfigureAwaitの考え方
非同期メソッドにはAsyncサフィックスを付けます。
C#public async Task<User?> FindUserAsync(int userId)
{
return await _userRepository.FindByIdAsync(userId);
}
戻り値は、値を返さない場合はTask、値を返す場合はTask<T>にします。イベントハンドラーなど特別な場合を除き、async voidは避けます。
C#// 推奨
public async Task SendAsync(Message message)
{
await _sender.SendAsync(message);
}
// 避けたい例
public async void Send(Message message)
{
await _sender.SendAsync(message);
}
ConfigureAwait(false)については、アプリケーションの種類によって方針を決めます。ライブラリでは呼び出し元の同期コンテキストに依存しないように使うことがあります。一方、ASP.NET Coreでは通常、過度に付ける必要はありません。
規約例は次のとおりです。
非同期メソッド名にはAsyncサフィックスを付ける。
async voidはイベントハンドラーを除き使用しない。
Task.ResultやTask.Wait()による同期ブロックは避ける。
ConfigureAwait(false)の使用方針はアプリケーション種別ごとに決める。
6-3. マジックナンバーを避けて定数・設定値に切り出す
意味の分からない数値や文字列をコードに直接書くと、後から変更しにくくなります。
避けたい例です。
C#if (retryCount >= 3)
{
throw new InvalidOperationException("Retry limit exceeded.");
}
改善例です。
C#private const int MaxRetryCount = 3;
if (retryCount >= MaxRetryCount)
{
throw new InvalidOperationException("Retry limit exceeded.");
}
環境ごとに変わる値は、定数ではなく設定ファイルに切り出します。
C#public class RetryOptions
{
public int MaxRetryCount { get; set; } = 3;
}
規約例は次のとおりです。
意味を持つ数値や文字列は定数、設定値、列挙型に切り出す。
環境によって変わる値は設定ファイルで管理する。
0、1、空文字など意味が明らかな値は例外として直接記述してよい。
6-4. 条件分岐を読みやすくする早期returnの使い方
ネストが深いコードは読みづらくなります。早期returnを使うと、正常系の処理を分かりやすくできます。
避けたい例です。
C#public void Register(User user)
{
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.Email))
{
Save(user);
SendMail(user);
}
}
}
改善例です。
C#public void Register(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrWhiteSpace(user.Email))
{
throw new ArgumentException("Email is required.", nameof(user));
}
Save(user);
SendMail(user);
}
早期returnは、エラー条件や対象外条件を先に排除する場合に有効です。
C#public void SendNotification(User user)
{
if (!user.IsActive)
{
return;
}
if (!user.HasEmail)
{
return;
}
_notificationSender.Send(user);
}
規約例は次のとおりです。
ガード節を使い、異常系や対象外条件を先に処理する。
ネストが深くなる場合は早期returnやメソッド分割を検討する。
条件式が複雑な場合は意味のある変数またはメソッドに切り出す。
6-5. LINQを使う場面と使いすぎを避ける判断基準
LINQはC#らしい書き方の一つで、コレクション操作を簡潔に表現できます。
C#var activeUsers = users
.Where(user => user.IsActive)
.OrderBy(user => user.Name)
.ToList();
ただし、複雑なLINQは読みづらくなることがあります。
C#var result = users
.Where(u => u.IsActive && u.Orders.Any(o => o.Items.Any(i => i.Price > 1000)))
.Select(u => new { u.Name, Count = u.Orders.Count(o => o.Status == OrderStatus.Paid) })
.Where(x => x.Count > 3)
.ToList();
このような場合は、条件をメソッドに切り出すと読みやすくなります。
C#var result = users
.Where(IsHighValueActiveUser)
.Select(CreateUserSummary)
.Where(summary => summary.PaidOrderCount > 3)
.ToList();
規約例は次のとおりです。
単純な抽出、変換、並び替えにはLINQを使用してよい。
条件が複雑な場合はメソッドに切り出す。
副作用を伴う処理をLINQ内に書かない。
パフォーマンスが重要な箇所では遅延評価とクエリ回数に注意する。
6-6. コメントを書くべき箇所・書かないほうがよい箇所
コメントは、コードから読み取れない意図を補足するために書きます。コードをそのまま説明するコメントは避けましょう。
避けたい例です。
C#// userIdが0以下の場合
if (userId <= 0)
{
return null;
}
このコメントはコードを読めば分かる内容です。
良いコメントは、背景や理由を説明します。
C#// 外部システムの仕様により、退会済みユーザーにも30日間は通知を送る必要がある。
if (user.WithdrawnAt >= _clock.Now.AddDays(-30))
{
SendNotification(user);
}
コメントではなく、名前で表現できる場合は名前を改善します。
C#// 避けたい例
// アクティブでメールアドレスを持つユーザーか判定する
if (user.IsActive && !string.IsNullOrWhiteSpace(user.Email))
{
}
// 良い例
if (CanReceiveEmail(user))
{
}
規約例は次のとおりです。
コメントはコードから読み取れない意図、背景、制約を書く。
コードをそのまま説明するコメントは避ける。
コメントが必要なほど複雑な処理は、命名やメソッド分割で改善できないか検討する。
TODOコメントには理由、担当、期限またはチケット番号を付ける。
6-7. XMLドキュメントコメントの運用ルール
XMLドキュメントコメントは、public APIやライブラリで特に有効です。
C#/// <summary>
/// 指定されたユーザーIDに対応するユーザーを取得します。
/// </summary>
/// <param name="userId">ユーザーID。</param>
/// <returns>ユーザーが存在する場合はUser、それ以外の場合はnull。</returns>
public User? FindById(int userId)
{
// ...
}
ただし、すべてのprivateメソッドにXMLコメントを書くと、かえって保守コストが増えることがあります。
規約例は次のとおりです。
外部公開するpublic APIにはXMLドキュメントコメントを書く。
internalまたはprivateメンバーへのXMLコメントは、意図や制約の説明が必要な場合に限定する。
実装変更時はコメントも更新する。
コメントと実装が矛盾する場合は、実装よりコメントが信頼されない状態になるため必ず修正する。
7. テスト・レビューに関するC#コーディング規約
C#コーディング規約では、本番コードだけでなくテストコードとレビュー観点も対象にするべきです。テストコードが読みづらいと、仕様変更時に安全に修正できません。
また、レビューで毎回同じ指摘が出る場合は、その内容を規約や自動チェックに反映することで、チーム全体の効率が上がります。
7-1. 単体テストの命名規則
単体テストの命名規則は、テストの意図が分かる形にします。代表的な形式は次のとおりです。
MethodName_Condition_ExpectedResult
例です。
C#public class UserServiceTests
{
[Fact]
public void Register_WhenEmailIsEmpty_ThrowsArgumentException()
{
// ...
}
[Fact]
public void FindUser_WhenUserExists_ReturnsUser()
{
// ...
}
}
日本語のチームであれば、日本語のテスト名を許可することもできます。
C#[Fact]
public void メールアドレスが空の場合は例外を投げる()
{
// ...
}
どちらを採用しても構いませんが、プロジェクト内で統一することが重要です。
規約例は次のとおりです。
テストメソッド名は、条件と期待結果が分かる名前にする。
1つのテストでは1つの振る舞いを検証する。
テスト名から仕様が読み取れることを重視する。
7-2. テスト対象・テストデータ・モックの書き方
テストコードでは、Arrange、Act、Assertの構成を意識すると読みやすくなります。
C#[Fact]
public void CalculateTotalPrice_WhenOrderHasItems_ReturnsSumOfItems()
{
// Arrange
var order = new Order();
order.AddItem(new OrderItem("Book", 1000));
order.AddItem(new OrderItem("Pen", 200));
// Act
var totalPrice = order.CalculateTotalPrice();
// Assert
Assert.Equal(1200, totalPrice);
}
テストデータは、テストの意図が分かる最小限の内容にします。関係のない値を大量に設定すると、何を検証しているのか分かりにくくなります。
モックは、外部依存を切り離すために使います。ただし、何でもモックにすると、実装詳細に依存した壊れやすいテストになります。
規約例は次のとおりです。
テストはArrange、Act、Assertの構成を基本とする。
テストデータは検証内容に必要な最小限にする。
外部サービス、時刻、乱数、データベースなどは必要に応じてモック化する。
実装詳細ではなく、外部から見た振る舞いを検証する。
7-3. コードレビューで確認する規約チェック項目
コードレビューでは、命名やフォーマットだけでなく、設計や保守性も確認します。
レビュー観点の例は次のとおりです。
命名:
- クラス名、メソッド名、変数名は役割を表しているか
- bool型の名前は真偽値として自然に読めるか
フォーマット:
- 自動整形が適用されているか
- 不要なusingや空行がないか
設計:
- クラスやメソッドの責務が大きすぎないか
- publicメンバーが必要最小限になっているか
- 依存関係が密結合になっていないか
実装:
- 例外を握りつぶしていないか
- nullの扱いが明確か
- async/awaitが適切に使われているか
- 複雑な条件分岐が読みやすく整理されているか
テスト:
- 重要な分岐や異常系がテストされているか
- テスト名から仕様が読み取れるか
レビュー項目は多すぎると使われなくなります。プロジェクトで特に重要な観点に絞り、必要に応じて更新しましょう。
7-4. 規約違反をレビューで指摘するときの基準
レビューで規約違反を指摘するときは、指摘の強さを分けると円滑です。
たとえば、次のように分類します。
must: 必ず修正してほしい指摘
should: 修正を推奨する指摘
nits: 軽微な指摘
question: 確認したい点
例です。
must: 例外を握りつぶしているため、ログ出力または再スローをお願いします。
should: この条件式は複雑なので、メソッドに切り出すと読みやすくなりそうです。
nits: 変数名はuserListよりusersのほうが自然かもしれません。
question: ここでnullを返す仕様で問題ないでしょうか?
規約違反を指摘するときは、「なぜ直す必要があるのか」を説明できることが重要です。単に「規約だから」ではなく、「保守性」「可読性」「バグ防止」「影響範囲の明確化」といった理由を添えると、チームに定着しやすくなります。
7-5. 自動チェックできる項目と人が判断すべき項目
コーディング規約は、自動チェックできる項目と、人が判断すべき項目に分けて管理します。
自動チェックに向いている項目は次のとおりです。
インデント
改行
不要using
命名スタイル
varの使用方針波括弧の有無
一部のAnalyzer警告
人が判断すべき項目は次のとおりです。
クラスの責務が適切か
メソッド名が意図を表しているか
インターフェースが過剰ではないか
例外処理の範囲が妥当か
コメントが必要な背景を説明しているか
テストが仕様を十分に表しているか
フォーマットのような機械的な指摘はツールに任せ、人は設計や仕様のレビューに集中できる状態を目指しましょう。
8. C#コーディング規約を自動化する方法
C#コーディング規約は、ドキュメントに書くだけでは定着しません。自動整形、自動チェック、CI/CDへの組み込みによって、自然に守られる状態を作ることが重要です。
自動化できる項目は積極的にツールに任せ、レビューでは人間にしか判断できない部分に集中しましょう。
8-1. .editorconfigでフォーマットと命名規則を管理する
.editorconfigは、エディタやIDEに共通のコードスタイル設定を伝えるためのファイルです。C#では、インデントや改行だけでなく、命名規則やvarの使用方針も設定できます。
例です。
INIroot = true
[*.cs]
indent_style = space
indent_size = 4
insert_final_newline = true
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true:warning
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion
命名規則も設定できます。
INIdotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_camel_case.style = prefix_underscore
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.required_prefix = _
dotnet_naming_style.prefix_underscore.capitalization = camel_case
.editorconfigはリポジトリに含め、全員が同じ設定を使えるようにします。
8-2. StyleCop Analyzersで規約違反を検出する
StyleCop Analyzersは、C#のコードスタイルやドキュメントコメントに関する規約違反を検出するためのAnalyzerです。
導入すると、命名、レイアウト、可読性、ドキュメントコメントなどに関する警告を出せます。
プロジェクトに追加する例です。
Bashdotnet add package StyleCop.Analyzers
ただし、StyleCop Analyzersはルールが多いため、すべてを有効にすると警告が大量に出ることがあります。最初は必要なルールだけ有効にし、チームで段階的に適用するのがおすすめです。
規約例は次のとおりです。
StyleCop Analyzersは新規コードから適用する。
既存コードへの適用は警告数を確認したうえで段階的に行う。
プロジェクトに不要なルールは無効化し、理由を記録する。
8-3. Roslyn Analyzerで独自ルールを追加する
標準のAnalyzerやStyleCopでは検出できないプロジェクト固有のルールは、Roslyn Analyzerで独自に実装できます。
たとえば、次のようなルールをチェックできます。
ControllerからRepositoryを直接呼び出していないか
特定の名前空間への依存を禁止する
DateTime.NowではなくIClockを使っているか特定の例外型を直接throwしていないか
命名規則がプロジェクト固有のルールに合っているか
ただし、独自Analyzerは作成・保守コストがかかります。まずは.editorconfig、標準Analyzer、StyleCopで対応し、それでも必要な場合に検討するとよいでしょう。
規約例は次のとおりです。
プロジェクト固有の重要ルールで、レビュー指摘が繰り返されるものはAnalyzer化を検討する。
一時的な好みや頻度の低い指摘はAnalyzer化しない。
8-4. dotnet formatで整形を自動実行する
dotnet formatは、.NETプロジェクトのコードスタイルやフォーマットを自動修正するためのコマンドです。
基本的な使い方は次のとおりです。
Bashdotnet format
CIでチェックだけ行う場合は、次のように実行できます。
Bashdotnet format --verify-no-changes
これにより、フォーマット違反がある場合にCIを失敗させることができます。
規約例は次のとおりです。
コミット前にdotnet formatを実行する。
CIではdotnet format --verify-no-changesを実行し、未整形コードの混入を防ぐ。
自動修正できる指摘はレビューで扱わない。
8-5. CI/CDで規約チェックを必須化する
C#コーディング規約を定着させるには、CI/CDで自動チェックを必須化するのが効果的です。
CIで実行する項目の例は次のとおりです。
Bashdotnet restore
dotnet build --no-restore
dotnet test --no-build
dotnet format --verify-no-changes
警告をエラーとして扱う場合は、プロジェクト設定で制御できます。
XML<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
ただし、既存プロジェクトでいきなりすべての警告をエラーにすると、開発が止まる可能性があります。新規コードから適用する、対象ルールを絞る、段階的に厳格化するなど、現実的な導入が必要です。
8-6. Visual Studio・Rider・VS Codeで設定を共有する
チームで複数のエディタを使っている場合でも、.editorconfigを中心に設定を共有できます。
Visual Studioでは、保存時の整形、Analyzer警告、コードスタイル提案などを確認できます。RiderやVS Codeでも、C#拡張機能やOmniSharp、C# Dev Kitなどを通じて.editorconfigの設定を反映できます。
規約には、推奨設定も記載しておくと親切です。
リポジトリの.editorconfigを有効にする。
保存時にフォーマットを実行する。
Analyzer警告を無視せず、必要に応じて修正または抑制理由を記録する。
開発環境の差によるフォーマットの揺れを防ぐため、設定ファイルはリポジトリで管理しましょう。
9. C#コーディング規約のサンプルテンプレート
ここでは、C#コーディング規約を作るときに使えるテンプレートを紹介します。プロジェクトに合わせて、そのまま利用または調整してください。
規約は長すぎると読まれなくなります。まずは重要な項目に絞り、必要に応じて追加していくのがおすすめです。
9-1. 規約ドキュメントに入れるべき項目一覧
C#コーディング規約のドキュメントには、次の項目を入れると実用的です。
1. 目的
2. 適用範囲
3. ルールの分類
4. 命名規則
5. フォーマット規約
6. 設計ルール
7. 実装ルール
8. テスト規約
9. レビュー観点
10. 自動チェック方法
11. 例外ルール
12. 規約変更の手順
目的の例です。
本規約は、C#プロジェクトにおけるコードの可読性、保守性、品質を向上させることを目的とする。
個人の好みを統一することではなく、チーム全体で安全に変更できるコードを維持するための基準とする。
適用範囲の例です。
本規約は、アプリケーションコード、テストコード、サンプルコードに適用する。
自動生成コード、外部ライブラリ、マイグレーションファイルは対象外とする場合がある。
9-2. 命名規則テンプレート
命名規則のテンプレート例です。
## 命名規則
### 基本方針
- 名前は役割や意図が分かるものにする。
- 不要な省略語は使わない。
- 型名ではなく意味を表す名前にする。
- プロジェクト全体で一貫した命名を行う。
### クラス・構造体・列挙型
- PascalCaseを使用する。
- 名詞または名詞句にする。
- 例: UserService, OrderStatus, Money
### メソッド
- PascalCaseを使用する。
- 動詞または動詞句にする。
- 例: FindUser, SaveOrder, CalculateTotalPrice
### プロパティ
- PascalCaseを使用する。
- bool型はIs, Has, Can, Shouldなどで始める。
- 例: IsActive, HasPermission, CanCancel
### ローカル変数・引数
- camelCaseを使用する。
- 役割が分かる名前にする。
- 例: userId, activeUsers, totalPrice
### privateフィールド
- _camelCaseを使用する。
- readonlyにできるものはreadonlyを付ける。
- 例: _userRepository, _clock
### インターフェース
- I + PascalCaseを使用する。
- 例: IUserRepository, INotificationSender
### 非同期メソッド
- Asyncサフィックスを付ける。
- 例: FindUserAsync, SaveOrderAsync
9-3. フォーマット規約テンプレート
フォーマット規約のテンプレート例です。
## フォーマット規約
### インデント
- スペース4つを使用する。
- タブ文字は使用しない。
### 波括弧
- 波括弧は次の行に配置する。
- if、for、foreach、whileなどでは、1行でも波括弧を省略しない。
### スペース
- 演算子の前後にスペースを入れる。
- カンマの後にスペースを入れる。
- メソッド呼び出しの括弧前にはスペースを入れない。
### 改行
- 1行は120文字以内を目安とする。
- メソッドチェーンが長い場合は改行する。
- 引数が多い場合は1行ずつ改行する。
### using
- 不要なusingは削除する。
- System名前空間を先に並べる。
- usingの整理はIDEまたはdotnet formatで行う。
### var
- 右辺から型が明らかな場合はvarを使用してよい。
- 型が読み手にとって重要な場合は明示的な型を書く。
9-4. 設計・実装ルールテンプレート
設計・実装ルールのテンプレート例です。
## 設計ルール
- クラスは1つの責務を持つように設計する。
- publicメンバーは必要最小限にする。
- 外部依存はコンストラクター注入で受け取る。
- 共通処理の再利用だけを目的とした継承は避ける。
- 継承よりコンポジションを優先する。
- インターフェースは、複数実装、外部依存、テストでの差し替えが必要な場合に使用する。
- staticクラスは状態を持たない純粋な処理に限定する。
- Nullable参照型を有効にし、nullの可能性を型で表現する。
## 実装ルール
- 例外を握りつぶさない。
- catchする例外は処理可能な範囲に限定する。
- 非同期メソッドにはAsyncサフィックスを付ける。
- async voidはイベントハンドラーを除き使用しない。
- マジックナンバーは定数または設定値に切り出す。
- 複雑な条件式は変数またはメソッドに切り出す。
- LINQ内で副作用のある処理を行わない。
- コメントは意図、背景、制約を説明するために書く。
9-5. レビュー観点テンプレート
コードレビューで使えるテンプレート例です。
## レビュー観点
### 命名
- 名前から役割や意図が分かるか
- C#の命名規則に沿っているか
- 曖昧な名前や不要な省略語がないか
### フォーマット
- 自動整形が適用されているか
- 不要なusingがないか
- 改行や空行が読みやすいか
### 設計
- クラスやメソッドの責務が大きすぎないか
- publicメンバーが増えすぎていないか
- 依存関係が密結合になっていないか
- インターフェースや継承が過剰ではないか
### 実装
- 例外処理が適切か
- nullの扱いが明確か
- 条件分岐が読みやすいか
- マジックナンバーがないか
- async/awaitが適切に使われているか
### テスト
- 正常系、異常系、境界値がテストされているか
- テスト名から仕様が読み取れるか
- テストデータが過剰ではないか
レビューコメントの分類も決めておくと便利です。
must: 必ず修正する
should: 修正を推奨する
nits: 軽微な指摘
question: 確認
9-6. .editorconfigのサンプル
C#プロジェクトで使える.editorconfigのサンプルです。
INIroot = true
[*.cs]
indent_style = space
indent_size = 4
tab_width = 4
insert_final_newline = true
charset = utf-8-bom
# using
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# braces
csharp_prefer_braces = true:warning
csharp_new_line_before_open_brace = all
# var
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_elsewhere = false:suggestion
# expression-bodied members
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_methods = false:suggestion
# predefined types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# private fields naming
dotnet_naming_rule.private_fields_should_be_underscore_camel_case.severity = suggestion
dotnet_naming_rule.private_fields_should_be_underscore_camel_case.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_underscore_camel_case.style = underscore_camel_case
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.underscore_camel_case.required_prefix = _
dotnet_naming_style.underscore_camel_case.capitalization = camel_case
# interfaces naming
dotnet_naming_rule.interfaces_should_start_with_i.severity = warning
dotnet_naming_rule.interfaces_should_start_with_i.symbols = interfaces
dotnet_naming_rule.interfaces_should_start_with_i.style = prefix_i_pascal_case
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_style.prefix_i_pascal_case.required_prefix = I
dotnet_naming_style.prefix_i_pascal_case.capitalization = pascal_case
このサンプルをそのまま使うのではなく、チームの方針に合わせて調整してください。特にseverityは、最初はsuggestionにしておき、運用が安定してからwarningやerrorに上げると導入しやすくなります。
10. C#コーディング規約をチームに定着させる運用方法
C#コーディング規約は、作っただけでは定着しません。ドキュメント、ツール、自動チェック、レビュー、オンボーディングを組み合わせて、日常の開発フローに組み込む必要があります。
規約は一度作って終わりではなく、プロジェクトの成長に合わせて改善するものです。
10-1. 最初から細かく決めすぎない
コーディング規約を作るときに、最初から細かく決めすぎると運用が重くなります。
最初に決めるべき項目は、次のような基本ルールです。
命名規則
インデントと波括弧
privateフィールドの命名
varの使用方針nullの扱い
例外処理
非同期メソッドの命名
レビュー観点
自動整形の方法
詳細な設計ルールやAnalyzerの独自ルールは、運用しながら必要に応じて追加すれば十分です。
使われる規約にするには、完璧さよりも実行しやすさを優先しましょう。
10-2. 既存コードに規約を適用する手順
既存プロジェクトに新しいC#コーディング規約を適用する場合、いきなり全コードを修正すると差分が大きくなり、レビューやマージが難しくなります。
おすすめの手順は次のとおりです。
1. 現在のコードスタイルを確認する。
2. 既存コードに近い形で基本規約を作る。
3. .editorconfigを追加する。
4. 新規コードから規約を適用する。
5. 自動整形できる範囲だけ別PRで修正する。
6. 重要なルールから段階的にCIでチェックする。
7. 既存コードの大規模修正は機能変更と分ける。
機能変更とフォーマット変更を同じPRに混ぜると、レビューが難しくなります。既存コードへの規約適用は、できるだけ機械的な変更と機能変更を分離しましょう。
10-3. 規約変更時の合意形成とバージョン管理
コーディング規約は、プロジェクトの状況に合わせて変わります。規約を変更する場合は、合意形成と履歴管理が必要です。
規約変更の流れは次のようにします。
1. 課題を明確にする。
2. 変更案を作る。
3. チームで合意する。
4. 規約ドキュメントと.editorconfigを更新する。
5. 必要に応じて既存コードへの適用方針を決める。
6. 変更履歴を残す。
規約ドキュメントは、コードと同じリポジトリで管理するのがおすすめです。Pull Requestで変更すれば、なぜそのルールが追加されたのかを後から追いやすくなります。
規約にはバージョンを付けてもよいでしょう。
Version 1.0: 初版作成
Version 1.1: Nullable参照型の運用ルールを追加
Version 1.2: async/awaitのConfigureAwait方針を追加
10-4. 新メンバー向けオンボーディングに組み込む
C#コーディング規約は、新メンバーがプロジェクトに参加するときのオンボーディングにも役立ちます。
オンボーディングでは、単に規約ドキュメントを渡すだけでなく、次の内容をセットで説明すると効果的です。
なぜこの規約があるのか
どのルールが必須なのか
自動整形の実行方法
レビューでよく指摘されるポイント
既存コードで例外的な箇所
設計上の重要な方針
また、良いコード例と避けたいコード例を用意すると、新メンバーが判断しやすくなります。
新メンバーは開発開始前に.editorconfigを有効にし、サンプルPRを通じて規約とレビュー観点を確認する。
このように開発フローに組み込むことで、規約が単なる文書ではなく実際に使われる基準になります。
10-5. 定期的に見直して使われる規約に改善する
規約は定期的に見直しましょう。使われていないルール、曖昧なルール、レビューで混乱を生んでいるルールは改善が必要です。
見直しの観点は次のとおりです。
- レビューで同じ指摘が繰り返されていないか
- 自動化できる指摘が人手で行われていないか
- 規約が細かすぎて開発の妨げになっていないか
- 新しいC#の機能に対応できているか
- 実態と合わないルールが残っていないか
定期的な振り返りで、規約を現場に合った形に更新していくことが大切です。
11. C#コーディング規約でよくある質問
ここでは、C#コーディング規約を作るときによく出る疑問に答えます。
11-1. C#の命名規則はMicrosoft公式に合わせるべき?
基本的には、Microsoft公式のC#コーディング規則や.NETの一般的な命名慣習に合わせることをおすすめします。
理由は、新しく参加したメンバーが理解しやすく、IDEやAnalyzerとの相性もよく、一般的なC#コードと違和感が少ないからです。
ただし、プロジェクト固有の事情がある場合は、公式ルールに独自ルールを追加しても問題ありません。大切なのは、独自ルールを作る理由を明確にし、チームで合意することです。
11-2. privateフィールドはアンダースコア付きにすべき?
privateフィールドにアンダースコアを付けるかどうかは、チームで統一されていればどちらでも構いません。
実務では、次のように_camelCaseを使うスタイルがよく使われます。
C#private readonly IUserRepository _userRepository;
コンストラクター引数とフィールドを区別しやすいというメリットがあります。
C#public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
一方で、アンダースコアを使わずthis.userRepositoryで区別するスタイルもあります。どちらが絶対に正しいというより、プロジェクト全体で一貫していることが重要です。
11-3. varは使ってよい?
varは使って問題ありません。ただし、読みやすさを基準にすることが大切です。
右辺から型が明らかな場合は、varを使うと簡潔になります。
C#var user = new User();
var orders = new List<Order>();
一方で、戻り値の型が読み手にとって重要な場合は、明示的な型を書いたほうが分かりやすくなります。
C#IEnumerable<Order> orders = repository.GetActiveOrders();
規約では、varを全面禁止にするよりも、使用する場面と避ける場面を明確にするのがおすすめです。
11-4. コメントはどこまで書くべき?
コメントは、コードを読めば分かる内容ではなく、コードだけでは分からない意図、背景、制約を書くために使います。
悪い例です。
C#// userがnullなら例外を投げる
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
良い例です。
C#// 外部APIの制限により、1回のリクエストでは最大100件まで送信する。
private const int MaxBatchSize = 100;
コメントを書く前に、まず命名やメソッド分割で分かりやすくできないかを考えましょう。コメントはコードの補助であり、分かりにくいコードを正当化するためのものではありません。
11-5. 個人開発でもコーディング規約は必要?
個人開発でも、簡単なコーディング規約はあると便利です。
特に、長期間メンテナンスするアプリや、将来的に他の人が参加する可能性があるプロジェクトでは、命名、フォーマット、ディレクトリ構成、例外処理、テストの方針を決めておくと保守しやすくなります。
個人開発の場合は、詳細なドキュメントを作る必要はありません。.editorconfigを用意し、READMEに最低限のルールを書くだけでも効果があります。
- C#の標準的な命名規則に従う
- privateフィールドは_camelCase
- Nullable参照型を有効にする
- dotnet formatを実行してからコミットする
小さな規約でも、将来の自分がコードを読み返すときの助けになります。
まとめ
C#コーディング規約は、コードの見た目をそろえるだけのものではありません。チーム全体で読みやすく、変更しやすく、バグを見つけやすいコードを維持するための基準です。
まずはMicrosoft公式のC#コーディング規則や一般的な.NETの慣習をベースにし、プロジェクトの特性に合わせて必要なルールを追加しましょう。命名規則、フォーマット規約、設計ルール、実装ルール、テスト、レビュー、自動化を分けて整理すると、使いやすい規約になります。
特に重要なのは、次のポイントです。
- クラス、メソッド、プロパティはPascalCaseにする
- ローカル変数と引数はcamelCaseにする
- privateフィールドの命名を統一する
- フォーマットは.editorconfigとdotnet formatで自動化する
- クラスは1つの責務に絞る
- publicメンバーは必要最小限にする
- nullの扱いをNullable参照型で明確にする
- 例外を握りつぶさない
- async/awaitのルールを決める
- レビューで見る項目と自動チェックする項目を分ける
C#コーディング規約は、最初から完璧に作る必要はありません。まずは最低限のルールから始め、レビューで繰り返し出る指摘や、実際に起きた問題をもとに改善していくことが大切です。
使われる規約にするためには、ドキュメント化するだけでなく、.editorconfig、Analyzer、dotnet format、CI/CDを活用して、自動的に守れる仕組みを整えましょう。チームの開発フローに自然に組み込まれたコーディング規約こそ、C#プロジェクトの保守性を長期的に高める土台になります。

