c# コーディングガイドライン 2013/02/26
DESCRIPTION
C# コーディングガイドラインに関する勉強会をやりました。 議事録は以下にまとめました。 http://blog.livedoor.jp/omssys/archives/52023811.htmlTRANSCRIPT
Coding Guidelines for C# 3.0, 4.0 and 5.0 のご紹介
尾崎 義尚 @yoshioms
2013.2.25
C# コーディング ガイドライン日本語版
aviva SolutionsのDennis Doomenさんが公開しているコーディングガイドライン
aviva Solutions
オランダにあるコンサルティング会社
ソフトウェア開発のコンサルティングもやってるらしい
http://csharpguidelines.codeplex.com/releases/view/98254
翻訳することになった経緯
C# コーディングガイドラインの一例 1/2
もう10年以上前のもので内容が古くなっている
欲しい情報が不足している
不要な情報が含まれている
C# コーディングガイドラインの一例 2/2
そもそも情報が少なすぎる
4ページしかない
っていうか、命名規約しかない。
https://docs.google.com/presentation/d/1ajbucKh09cSD_N3m5iUV9jBi7Ji34QczxD6xnjwfcIo/edit
時代に合わせた
コーディング標準が
必要!
公開までのスケジュール
連絡・交渉 翻訳
翻訳 レビュー 公開
連絡・交渉
Dennis さんに日本語訳を打診• 将来、更新したらすぐに翻訳すること。
• そのまま訳す(literal translation)ならCodePlexでホストします。
• もし変更して独自のものを作るのであれば、インターネットに公開してはいけません。
ただし明らかに日本の文化に合わないものがあれば(if you
think that makes sense) 若干の変更してもかまいません。
レビュー
小島富治雄さん
原敬一さん
かるあくんさん
岩永伸之さん
青木淳夫さん
赤澤光世さん
長沢隆さん
小山康晴さん
特に岩永さんのレビューがすごかった!
さまざまな反響
Kokudoriさんの読評がすごい!
http://d.hatena.ne.jp/Kokudori/20130214/1360868503
文書の基本構造
重要度に応じて必要な規約を選択する
❶絶対に守るべきガイドライン
❷強くお薦めするガイドライン
❸状況に応じては推奨するガイドライン
全部を採用する必要はありません。
プロジェクトにあった選択を。
クラスやインターフェイスはひとつの役割だけを持つようにする
クラスやインターフェイスはそれが参加しているシステムの中で単一の目的を持つべきである。
一般的にクラスは以下の2つに分類できる。
emailやISBNのようなプリミティブ型、業務を抽象化したもの、プレーンなデータ構造
もしくは、複数のクラス間での対話によるオーケストレーションを行う責任があるもの
それらが組み合わさることはない。このルールは、SOLID原則のひとつである単一責務の原則として知られている。
Tipクラスの意図を伝えるためにデザインパターンを使用する。もしひとつのデザインパターンを割り当てることができない場合、ひとつ以上のことをしようとしている可能性がある。
AV1000
継承メンバーを newキーワードで隠すべきではない
new キーワードは、ポリモーフィズムを壊す
サブクラスを理解することがより難しくなる。
AV1010
public class Book
{
public virtual void Print()
{
Console.WriteLine("Printing Book");
}
}
public class PocketBook : Book
{
public new void Print()
{
Console.WriteLine("Printing PocketBook");
}
}
PocketBook pocketBook = new PocketBook();
pocketBook.Print(); // "Printing PocketBook "と出力される((Book)pocketBook).Print(); // "Printing Book"と出力される
ベースクラスから派生クラスを参照しない
ベースクラスがサブクラスに依存すると、
適切なオブジェクト指向設計に反する
新しい派生クラスを追加することを阻む
AV1013
双方向の依存を避ける
2つのクラスがお互いに依存し合っている
どちらかの変更がもう一方に影響を与える
依存性をなくすには:一方のクラスのインターフェイスに依存性注入を使用する
例外ドメイン駆動設計で定義されたドメインモデル
実生活の関係性によって双方向の関係になることがある。
本当に必要かどうかを確認して、それでも必要な場合はそれを維持する。
AV1020
クラスは状態と振る舞いを持つべきである
振る舞いだけを持つ(静的な)クラス がある場合、それが適用されるデータの近くにロジックを移動する。
例外
データ転送オブジェクト(Data Transfer Objects)
メソッドのパラメータをラップするクラス
AV1025
プロパティはあらゆる順番でセットできるようにする
プロパティは、他のプロパティに対してステートレスでなくてはならない。
DataSource プロパティの後でDataMemberをセットした場合とその逆で差があってはいけない。
AV1100
相互排他なプロパティを使用しない
1つの型に2つの相反するコンセプトを持っている
ドメインモデルでよく見られる
AV1110
文字列やコレクションのプロパティ、メソッド、引数はnullであってはならない
nullではなく、空のコレクションや空文字列を返すようにする
AV1135
汎用的な例外キャッチでエラーを飲み込まない
Exception、SystemExceptionなどでキャッチしない
トップレベルのコードでは、きちんとキャッチしてログをはいて、終了する。
AV1210
常にイベントハンドラのnullをチェックする
イベントのサブスクライバが存在しない場合nullになるため、呼び出す前にデリゲートリストであるイベント変数がnullでないことを確認する
AV1220
event EventHandler<NotifyEventArgs> Notify;
void RaiseNotifyEvent(NotifyEventArgs args)
{
EventHandler<NotifyEventArgs> handlers = Notify;
if (handlers != null)
{
handlers(this, args);
}
}
イベントを発生させる時sender 引数としてnullを渡すべきではない
イベントハンドラーのsender引数は、イベントソースを識別するために使用される
通常はthisを渡せばよい
イベントデータがない場合は、EventArgs.Emptyを渡す
例外静的イベントでは、sender引数はnullでなくてはならない。
AV1235
LINQ式を戻り値として返す前に評価する
以下のコードスニペットを考えてみよう
qは上記のクエリを表現する式木を返す
代わりにToList()やToArray()などのメソッドを使ってLINQクエリの結果を明示的に評価する。
AV1250
public IEnumerable<GoldMember> GetGoldMemberCustomers()
{
const decimal GoldMemberThresholdInEuro = 1000000;
var q = from customer in db.Customers
where customer.Balance > GoldMemberThresholdInEuro
select new GoldMember(customer.Name, customer.Balance);
return q;
}
メソッドは7ステートメントを超えるべきではない
7個より多いステートメントを持つメソッドは、多くのことをし過ぎているか、多くの責任を持ちすぎている。
コードが何をしているかを理解するために人が正確にステートメントを分析する必要があります。
内容を説明した名前を持つ、複数の小さく、フォーカスされたメソッドに分割して、それでも高いレベルでアルゴリズムがまだ明確であることを確認する。
AV1500
デフォルトでは、すべてのメンバーをprivateに、型をinternalにする
最初はスコープを可能な限り狭める。
その後慎重になにをpublicメンバーや型として公開するかを決める。
AV1501
“マジック” ナンバーを使ってはいけない
記号定数以外に数値、文字列のどちらにもリテラル値を使用してはいけない。例えば:
ログやトレースのための文字列はこのルールから除外する。文脈から意味が明確な場合と将来変更される可能性がない場合にはリテラルを許可する。例えば:
ある定数が他に依存している場合は、コード内で明示するようにする。
Note 記号定数を明らかにするために列挙型を使用することができるAV1515
public class Whatever
{
public static readonly Color PapayaWhip = new Color(0xFFEFD5);
public const int MaxNumberOfWheels = 18;
}
mean = (a + b) / 2; // OKWaitMilliseconds(waitTimeInSeconds * 1000); // 十分明確である
public class SomeSpecialContainer
{
public const int MaxItems = 32;
public const int HighWaterMark = 3 * MaxItems / 4; // at 75%
}
型が確実に明確なときにだけvarを使用する
LINQクエリの結果やステートメントからの型が確実に明確なときには、varを使うことによって可読性が向上する。そのため、以下のようには使用しない。
varは、以下のように使用する。
上記3つの例では、期待している型が明確である。より詳細なvarによるメリット・デメリットは、Eric Lippert氏の 型推論を使うかどうかを参照して欲しい。
AV1520
var i = 3; // 型はなに? int? uint? float?
var myfoo = MyFactoryMethod.Create("arg"); // なにを期待しているのかが明確じゃない。// ベースクラス? インターフェイス?
// また、クラスを探すことができないと// リファクタリングが難しくなる
var q = from order in orders where order.Items > 10 and order.TotalValue > 1000;
var repository = new RepositoryFactory.Get<IOrderRepository>();
var list = new ReadOnlyCollection<string>();
true やfalse を明示的に比較しない
通常はbool型の式でtrue かfalseかを比較するためことはよくないスタイルである。例えば:
AV1525
while (condition == false) // 間違えた、よくないスタイルwhile (condition != true) // これも誤りwhile (((condition == true) == true) == true) // どこまでいくの?
while (condition) // OK
常にswitch ステートメントの最後のcase の後にdefaultブロックを追加する
Defaultブロックが空の場合は説明するコメントを追加する
到達することがない場合は、InvalidOperationExceptionをスローする
AV1536
void Foo(string answer)
{
switch (answer)
{
case "no":
Console.WriteLine("You answered with No");
break;
case "yes":
Console.WriteLine("You answered with Yes");
break;
default:// ここで終わることはない。
throw new InvalidOperationException("Unexpected answer " + answer);
}
}
メソッドやプロパティの複雑な表現をカプセル化する
以下の例について考えてみよう
この表現を理解するためには、詳細とすべての結果で、なにをしているのかを分析する必要がある。明らかに、上にコメントを追加することができるが、それよりも明確な名前のメソッド名でこの複雑な表現を起きえる方がよいだろう:
AV1547
if (member.HidesBaseClassMember && (member.NodeType != NodeType.InstanceInitializer))
{// なにかをする
}
if (NonConstructorMemberUsesNewKeyword(member))
{// なにかをする
}
private bool NonConstructorMemberUsesNewKeyword(Member member)
{
return
(member.HidesBaseClassMember &&
(member.NodeType != NodeType.InstanceInitializer)
}
オプショナル引数は、オーバーロードを置き換えるためにだけ使用する
C# 4.0でオプショナル引数を使用する唯一の妥当な理由は、ひとつのメソッドで以下のようにルールAV1551の例を置き換えることである:
オプショナルパラメータが参照型の場合、デフォルト値はnullしか与えられない。しかしここで、string、リスト、コレクションの場合は、ルールAV1235によりnullであってはならないため、代わりにオーバーロードされたメソッドを使用する必要がある。
Note オプショナルパラメータのデフォルト値は、呼び出し側に保存されている。呼び出し側のコードを再コンパイルしないでデフォルト値を変更すると、新しいデフォルト値プロパティは適用されない。
Noteインターフェイスメソッドがオプショナルパラメータを定義している場合、インターフェイスの参照を通じてクラスの実態を呼び出さない限り、デフォルト値はオーバーロードの解決時に考慮されない。詳細は、Eric Lippert氏のこの投稿を参照。
AV1553
public virtual int IndexOf(string phrase, int startIndex = 0, int count = 0)
{
return someText.IndexOf(phrase, startIndex, count);
}
名前付き引数の使用を避ける
C# 4.0の名前付き引数は、大量のオプショナルパラメータを提供するCOMコンポーネントの呼び出しを簡単にするために導入された。
メソッド呼び出しの可読性を改善するために名前付き引数が必要なら、そのメソッドはおそらく多くのことをやり過ぎているか、リファクタリングすべきである。
名前付き引数で可読性が改善する唯一の例外は、有効なオブジェクトを生み出すコンストラクターで以下のように呼ばれたときだ:
AV1555
Person person = new Person
(
firstName: "John",
lastName: "Smith",
dateOfBirth: new DateTime(1970, 1, 1)
);
3つより多いパラメータを受け取るメソッドやコンストラクターを許可しない
メソッドが3つより多いパラメータを必要とする場合、Specificationデザインパターンで説明されているように、複数の引数を渡すための構造体かクラスを使用する。一般的にパラメータ数が少ないほうがメソッドを理解しやすい。さらに、多くのパラメータが必要なメソッドのユニットテストには多くのシナリオが必要になる。
AV1561
ref やout パラメータを使用しない
これらはコードを理解しづらくして、バグを引き起こすことがある。代わりに複合オブジェクトを返すようにする。
AV1562
常に as 操作の結果をチェックする
オブジェクトからインターフェイスの参照を取得するためにasを使っている場合、その操作がnullを返さないかを常に確認する。
オブジェクトがインターフェイスを実装していないときにこれを怠ると、ずっと後の段階でNullReferenceException
が発生することがある。
AV1570
コードをコメントアウトしない
コメントアウトされたコードをチェックインしない
やるべき作業を管理できる作業アイテムをトラックできるシステムを使用する
コメントアウトされたコードをみつけたときになにをするべきかを誰もわからない
テストのために一時的に無効化している?サンプルをコピーした?そんなものは削除するべきだ。
AV1575
アメリカ英語を使用する
英語を使用する場合、すべての型メンバー、パラメータ、変数はアメリカ英語の単語を使用するべきである。
簡単で読みやすく、文法として正しい名前を選択する。例えば、HorizontalAlignmentはAlignmentHorizontalよりも読みやすい
短さよりも読みやすさを選ぶ。CanScrollHorizontallyというプロパティ名はScrollableX よりも優れている(X軸はあいまいな参照である)
プログラム言語で広く使われているキーワードとの衝突を避ける
例外 ほとんどのプロジェクトでは、ドメインや企業に固有の単語やフレーズを使用する。Visual Studioの静的コード分析は、すべてのコードにスペルチェックを実行するため、カスタムコード分析辞書に用語を追加する必要がある。
AV1701
言語要素に適したケースを使用する
言語要素 ケース 例
クラス、構造体 Pascal AppDomain
インターフェイス Pascal IBusinessService
列挙型 Pascal ErrorLevel
列挙値 Pascal FatalError
イベント Pascal Click
Privateフィールド Camel listItem
Protectedフィールド Pascal MainPanel
Constフィールド Pascal MaximumItems
Const変数 Camel maximumItems
Read-only静的フィールド Pascal RedValue
変数 Camel listOfValues
メソッド Pascal ToString
名前空間 Pascal System.Drawing
パラメータ Camel typeName
型パラメータ Pascal TView
プロパティ Pascal BackColor AV1702
フィールドにプレフィックスを使用しない
例えば、静的フィールドかどうかを区別するためにg_ やs_ を使用してはいけない
一般的にローカル変数かメンバー変数かの区別がつかない場合、大きすぎることが多い
正しくない識別子名には次のようなものがある:
_currentUser, mUserName, m_loginTime
AV1705
クラスや列挙型のメンバーは、名前を繰り返えさない
AV1710
class Employee
{// 誤り!
static GetEmployee() {}
DeleteEmployee() {}
// 正しい
static Get() {...}
Delete() {...}
// これも正しい
AddNewJob() {...}
RegisterForMeeting() {...}
}
短い名前や他の名前と間違えられることがある名前は避ける
以下のステートメントは、技術的には正しいが混乱する可能性が非常に高い
AV1712
bool b001 = (lo == l0) ? (I1 == 11) : (lOl != 101);
System名前空間の型ではなく、C#の型エイリアスを使用する
例えば、Objectの代わりにobject、Stringの代わりにstring、Int32の代わりにint
これらのエイリアスはプリミティブ型としてC#言語の一級市民として提供されているため、これらを使用するべきである
例外これらの型の静的メンバーを参照するときは、完全なCLS名を使用する。例えば、int.Parse()の代わりに
Int32.Parse()
AV2201
最も高い警告レベルでビルドする
開発環境で、C#コンパイラの警告レベル 4で構成し、警告をエラーとして扱うオプションを有効にする
これはコンパイラに可能な限り最高のコード品質を強要することができる。
AV2210
dynamicキーワードは動的オブジェクトを受け取るときだけ使用する
dynamicキーワードは、動的言語で作業するために導入された。コンパイラがいくつかの複雑なリフレクションコードを生成する必要があるため、これを使うことで深刻なパフォーマンスのボトルネックになる可能性がある。
これは、(Activatorを使って)動的に生成されたインスタンスのメソッドやメンバーを呼び出すときだけ、Type.GetProperty() 、Type.GetMethod()、COM互換型として操作する代わりに使用する
AV2230
コメントとドキュメントをアメリカ英語で記述する
AV2301
複雑なアルゴリズムや決定を説明するコメントのみを記述する
コメントには「どのように(how)」ではなく、コードブロックの「なぜ(Why)」と「なに(What)」にフォーカスする。ステートメントを言葉で説明するのを避け、なぜ(Why)そのソリューションやアルゴリズムを選択して、なに(What)を達成しようとしたのかを読み手が理解することを助けよ。明確なソリューションで問題が発生したなどの理由で代替手段を選んだのであれば、そのことについて触れること。
AV2316
一般的なレイアウトを使用する
1行を130文字以内に抑える
4文字の空白インデントを使用して、タブを使用しない
ifと式の間のようにキーワード間に空白をひとつ入れるが、if (condition == null) のようにその前後には空白を追加しない
+、-、==など、演算子の周りに空白を追加する
言語的に必要なかったとしても、if、 else、 do、 while、for、 foreachなどに開始と終了の括弧を置いて、常にキーワードを成功させる。
開始と終了の括弧は常に新しい行に置く
AV2400
一般的なレイアウトを使用する
オブジェクト初期化子と各プロパティの初期化は、以下のようにインデントせず、新しい行に書く:
ブロックを持つラムダ式は、インデントせず、以下のように書く:
AV2400
var dto = new ConsumerDto()
{
Id = 123,
Name = “Microsoft”,
PartnerShip = PartnerShip.Gold,
}
methodThatTakesAnAction.Do(x =>
{// なにかする
}
一般的なレイアウトを使用する
クエリ式は、以下のように全体を1行にするか、各キーワードを同じインデントにする:
または
LINQステートメントは必ず、from 句から始めて、where句でそれを混ぜ込まない
すべての比較条件の周りに括弧を追加するが、単数の条件の周りには括弧を追加しない。例えば、
if (!string.IsNullOrEmpty(str) && (str != “new”))
複数行のステートメントの間、メンバーの間、閉じ括弧の後、無関係のコードブロック、#regionキーワードの周り、異なる会社のusingステートメントの間には空行を追加する。
AV2400
var query = from product in products where product.Price > 10 select product;
var query =
from product in products
where product.Price > 10
select product;
if (!string.IsNullOrEmpty(str) && (str != “new”))
メンバーを適切に定義された並び順にする
共通の並び順を維持することで、他のチームメンバーが簡単にコードで目的のものを見つけることができる。一般的にソースファイルは、本を読むように上から下に読むことができるべきである。これは読者がコードファイルで上下に行き来しなければいけない状況を防ぐ。
privateフィールドと(領域の)定数
public定数
public readonly static フィールド
ファクトリメソッド
コンストラクターとファイナライザー
イベント
publicプロパティ
呼び出す順で、その他のメソッドとprivateプロパティ
AV2406
#regionを控える
Regionは便利なときもあるが、クラスの主な目的を隠すこともできてしまう。従って#regionは、以下の目的でのみ使用する:
privateフィールドと定数(できればprivate定義region)
ネストされたクラス
インターフェイスの実装(インターフェイスがそのクラスの主な目的でない場合のみ)
AV2407
全部を採用する必要はありません。
プロジェクトにあった選択を。