Appearance
Metanaka .NET Coding
Rule Levels
[Required]: 違反は必須修正として扱う。[Recommended]: 原則として採用する。合理的な理由がある逸脱は許容する。[Contextual]: 記載した条件が成立するときだけ適用する。根拠が不足する場合は違反ではなく確認事項として扱う。
レビュー判定に使う規則はlevel tag付きの箇条書きだけとする。tagのない文章とcode sampleは背景、理由、例を示す。
Scope
[Required]このskillはproduction codeとtest codeのC#表現を扱う。[Required]配置、依存方向、HTTP/Application境界、永続化設計はmetanaka-dotnet-architectureへ委ねる。[Required]testの構造、命名、検証内容、test doubleはmetanaka-dotnet-testingへ委ねる。[Required]observable production behaviorを変更する実行順序はmetanaka-dotnet-tddへ委ねる。[Contextual]namespace、nullable、collection expression、primary constructorなど、このskillに規則がなくroot.editorconfigにも設定がない表現は、近傍のC#とrepositoryのbuild/analyzer結果に合わせる。未定義の好みをレビュー違反にしない。
複数規則を組み合わせた形を確認するときだけsamples/expression-and-control-flow/README.mdを読む。sampleは命名やdomain設計の規則を追加しない。
Formatting and Expression Bodies
[Required]if、for、foreach、while、do、using、lockは、本文が1 statementでも波括弧を省略しない。[Required]波括弧はAllman styleとし、開始・終了の波括弧をそれぞれ独立した行に置く。[Required]1行には1 statementだけを書く。[Recommended]単一式で表現でき、member名と式から意図を直接読めるmethod、constructor、operator、property、indexer、accessor、lambda、local functionはexpression body (=>)を使う。[Contextual]expression bodyは物理的な1行を強制しない。式や引数が長い場合は、=>と式を読みやすい位置で複数行に分ける。[Contextual]breakpointを置く必要がある逐次処理、条件分岐を式へ埋め込む処理、複数段のLINQで中間値の意味が重要な処理はblock bodyを使う。近傍codeがblock bodyで統一されているだけでは必須理由にせず、意図やdebug時の観測点が明確になるかで判断する。[Required]local variable、複数statement、条件分岐、loop、複数の副作用を含むmemberはblock bodyを使う。[Contextual]local variableを宣言せず、単一のLINQ queryをそのまま返すmemberはexpression bodyを使ってよい。複数のLINQ stageでも1つのqueryとして自然に読める場合は改行したexpression bodyを許容し、途中結果へ業務上の名前を付ける必要がある場合はblock bodyとlocal variableを使う。[Recommended]local variableの型をコンパイラが推論できる場合は、組み込み型を含めてvarを使う。field、property、parameter、return typeなど契約の型は省略しない。[Required]使用していないusingdirectiveは残さない。IDE0005をwarningとして扱う。
Control Flow
[Recommended]guard clauseや早期終了によって、条件分岐のnestingを浅くする。[Required]複数の相互排他的な条件から値または戻り値を選ぶ場合は、独立したifを連ねずswitch式を使う。[Recommended]副作用を伴う複数分岐や、各caseで複数statementを実行する場合はswitch文を使う。値を選ぶだけの分岐をswitch文へ広げない。[Contextual]単純な二値の選択で条件演算子の方が意図を直接読める場合は?:を使ってよい。条件演算子をnestして複数分岐を表現せずswitch式を使う。
csharp
if (!customer.IsActive)
{
return CustomerErrors.Inactive();
}
return customer.Plan switch
{
CustomerPlan.Free => Limits.Free,
CustomerPlan.Standard => Limits.Standard,
CustomerPlan.Premium => Limits.Premium,
_ => throw new ArgumentOutOfRangeException(nameof(customer.Plan)),
};Tuple Deconstruction
[Recommended]named tupleの複数要素を後続処理で使う場合は、業務上の値へ名前を付けるため分解代入する。一部だけ使う場合はdiscard (_)を使う。[Contextual]tuple要素をmethod argumentとして一度だけ使う場合は、memberを直接参照してよい。[Contextual]discardが複数必要になるほど大きいtupleや、値の組み合わせ自体に業務上の意味があるtupleは、意味のあるresult typeへ置き換える。result typeの責務と配置はmetanaka-dotnet-architectureに従う。
csharp
var (room, round) = hostRound.Value;一部だけ使う別の例:
csharp
var (_, round) = playerRound.Value;EditorConfig Source of Truth
[Required]formatterやanalyzerの設定値を追加・変更・判定するときはroot.editorconfigを正とする。このskillへ設定値を複製せず、規則と設定が食い違う場合は差異を報告する。[Required]root.editorconfigでerrorに設定された規則はbuildとCIで機械的に強制する。機械的に表現できない規則は、このskillを使う実装・レビュー時に判定する。
Verification
[Required]C# codeを変更したら、作業範囲に最も近いprojectまたはsolutionのdotnet buildを実行し、compiler errorとanalyzer warningがないことを確認する。[Contextual]test codeも変更した場合、またはproduction behaviorへ影響し得る変更の場合は、関連するdotnet testも実行する。TDD対象の実行順序はmetanaka-dotnet-tddを正とする。[Required]metanaka-dotnet-tdd適用中は、同workflowのRED/GREEN test実行と最後のbuildでこのVerificationを共同充足する。各中間編集のたびに別のbuildやtestを重複実行しない。
Expected C# Shape
csharp
public bool IsActive
=> Status == WordPairStatus.Active;
public Task SaveAsync(
WordPair wordPair,
CancellationToken ct)
=> repository.SaveAsync(wordPair, ct);
public IReadOnlyList<Player> ActivePlayers
=> players
.Where(player => player.IsActive)
.OrderBy(player => player.JoinedAt)
.ToList();
if (!category.Exists)
{
return WordErrors.CategoryNotFound();
}branchやlocal variableを必要とするmemberは、expression bodyへ押し込まずblock bodyにする。
csharp
public async Task<Room?> GetRoomAsync(RoomId roomId, CancellationToken ct)
{
var snapshot = await repository.GetAsync(roomId, ct);
if (!snapshot.Exists)
{
return null;
}
return snapshot.ToDomain();
}