c# コーディングガイドライン 2013/02/26

Post on 28-May-2015

6.471 Views

Category:

Technology

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

C# コーディングガイドラインに関する勉強会をやりました。 議事録は以下にまとめました。 http://blog.livedoor.jp/omssys/archives/52023811.html

TRANSCRIPT

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

全部を採用する必要はありません。

プロジェクトにあった選択を。

top related