.NET のクラスライブラリ設計
.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)
- 作者: Krzysztof Cwalina,Bard Abrams,藤原雄介
- 出版社/メーカー: 日経BPソフトプレス
- 発売日: 2009/12/24
- メディア: 大型本
- 購入: 9人 クリック: 543回
- この商品を含むブログ (32件) を見る
とっくに読み終わっていたんだけど、まとめる時間がなかったのでかなり時間が空いてしまった・・・
ということで基本的には「・・・ん?」って思ったところとかのまとめです。
アセンブリと名前空間
よく Java の package と C# の namespace を同じようなものとして扱っている人はいるけど、この本では、
- アセンブリ
- パッケージング及び配置の境界
- 名前空間
- 開発者に対する論理的なグループ
としている*1。
Java の package はこの 2 つを合わせたもの*2だし、Java の名前空間はファイルの配置という物理的なグループも作り出すので、まったく同じものというわけではない。
なので、Java の package と C# の namespace の使い方は違ったものであるべきなのに、どうもそう考えていない人は多いっぽい・・・
この話はまた機会があればまとめようかな。時間があれば、だけど。
Java の package については、
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技
- 作者: ロバート・C・マーチン,瀬谷啓介
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2008/07/01
- メディア: 大型本
- 購入: 17人 クリック: 561回
- この商品を含むブログ (67件) を見る
の 20 章を熟読すること。
一般的な型名を採用してもいいんじゃ?
.NET のクラスライブラリ設計 (P.48)Element、Node、Log、および Message のような一般的な型名を採用してはいけません。
一般的なシナリオにおいて、非常に高い可能性で型名の競合が誘発されます。汎用的な型名は修飾すべきです (FormElement、XmlNode、EventLog、SoapMessage)。
うーん、こういうことを回避するために名前空間があるんじゃないの?
それぞれ、Form.Element、Xml.Node、Event.Log、Soap.Message な感じで名前空間で区切って、競合する場合は直前までを using すればいい話で、むしろ一般的な型名はどんどん付けるべき。
メソッド名の付け方
.NET のクラスライブラリ設計 (P.56)メソッドには動詞または動詞句の名前を付けます。
.NET のクラスライブラリ設計 (P.288)ファクトリメソッドの名前は、"Create" という単語に作成される型の名前を連結したものにすることを検討します。
これらはよくある規約だけど、もう古くなっていて、これだけじゃ足りないと思う。
例えば、Effective Java の第二版では、
Effective Java 第 2 版 項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する (P.10)
- valueOf
- 大ざっぱに言えば、パラメータと同じ値を持つインスタンスを返します。〜略
- of
- EnumSet (項目 32) で広まった、valueOf に対する簡潔な代替です。
- getInstance
- 〜略
- newInstance
- getInstance に似ていますが、newInstance は返される個々のインスタンスはすべて別々のインスタンスである点が異なります。
- getType
- getInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に使用されます。Type はファクトリーメソッドから返されるオブジェクトの型を示しています。
- newType
- newInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に使用されます。Type はファクトリーメソッドから返されるオブジェクトの型を示しています。
何らかの処理を行うメソッドは、一般に動詞あるいは (目的語を含む) 動詞句で命名されます。たとえば、append や drawImage です。
Effective Java 第 2 版 項目 56 一般的に受け入れられている命名規約を守る (P.231 - 232)
〜略〜
特別に述べておくべきメソッド名が多少あります。オブジェクトの型を変換し、別の型の無関係なオブジェクトを返すメソッドは、たいていは toType と呼ばれます。例えば、toString、toArray です。レシーバーオブジェクトの型と異なる型を持つビュー (view) (項目 5) を返すメソッドは、たいていは asType と呼ばれます。たとえば、asList です。メソッドが呼び出されたオブジェクトと同じ値を持つ基本データを返すメソッドは、たいていは typeValue と呼ばれます。たとえば、intValue です。static ファクトリーメソッドに対する共通の名前は、valueOf、of、getInstance、newInstance、getType、newType です (項目 1、10 項)。
などとある。
例えば、タプルを生成するメソッドが Tuple.CreateTuple とか、Tuple.Create とかはどうなんだ、みたいな。Tuple.Of の方が短いし分かりやすいと思うんだけど・・・
このほかにも、流れるようなインターフェイスを設計する場合なども古い規約では対応できない。
CanXxx
CanRead は Readable よりも理解しやすいものです。しかしながら、Created は実際に IsCreated よりも読みやすいものです。プレフィックスを持つことは多くの場合、特にコードエディタ内で IntelliSense に表示されるときに、冗長過ぎ、かつ不要です。
〜略〜.NET のクラスライブラリ設計 (P.57 - 58)受動態よりも能動態を優先して選択すべきです。
if (stream.CanSeek) // こちらのほうが良い if (stream.IsSeekable)
個人的には、IntelliSense で bool を返すプロパティがまとまっていた方が分かりやすいので、Is プレフィックス推奨派なんだけど・・・
それと、C# では not が「!」なので視認性が悪いため、プロパティで実装せずにメソッドとして実装して拡張メソッドとして Not 版を用意するというのもありだと思う。
public interface ISomeInterface { bool IsHoge(); } public static class ISomeInterfaceExtension { public static bool IsNotHoge(this ISomeInterface self) { return !self.IsHoge(); } }
・・・拡張プロパティが欲しいところ。
Before/After
.NET のクラスライブラリ設計 (P.58)事前および事後のイベントを示すために、"Before" および "After" というプレフィックスおよびサフィックスを使用してはなりません。
その代わりに現在進行形によって事前のイベントを、過去形によって事後のイベントを表せ、といっているんだけど・・・
Before/After の方が分かりやすくない?事前のイベントについては特に。
イベントハンドラのアンチパターン
.NET のクラスライブラリ設計 (P.59)イベントハンドラに対して、"sender" および "e" という名前の 2 つのパラメータを使用します。
sender パラメータはイベントを発生させたオブジェクトを表します。sender パラメータは、より特定的な型を採用可能であったとしても、一般的に object 型になります。このパターンは .NET Framework 全体で一貫して採用されています。
.NET のクラスライブラリ設計 (P.134)
これはアンチパターンなんじゃないの?過去の遺産があることは分かるけど、これをパターンと言い張るには無理があるんじゃないだろうか?
sender が object だと、いつもいつも sender の型の確認とキャストが必要になって、DRY に違反しまくり。
だからこれには従うべきじゃないんじゃないかな。
class と struct
ライブラリで頻繁に使用されることが予測される型をプロファイルし、実際に発生しないかもしれないような思い込みではなく実際のデータに基づいて、参照型を値型に変更することを推奨します。
.NET のクラスライブラリ設計 (P.70)
これはリファクタリング手順を確立させる必要があるかも。
参照型は参照によって渡され、逆に値型は値によって渡されます。
.NET のクラスライブラリ設計 (P.70)
とても誤解を生みそうな表現の気が・・・
スマートポインタ?
"スマートポインタ" な値型を作成することによって、それらが通常のオブジェクトであるかのように配列内の要素を参照することも可能です。これらの型は 2 つのフィールドを持ちます。最初のフィールドは巨大な配列に対する参照、2 番目のフィールドは配列のインデックスです。ユーザーの視点からは、これらの "スマートポインタ" の使用は通常の参照型の使用のように見えますが、メモリ使用量は大きく削減されます。
.NET のクラスライブラリ設計 (P.71)
スマートポインタ・・・用語の再利用はさけて欲しいんだけど・・・
スマートポインタというと普通は C++ のスマートポインタだよね。
構造体の設計
.NET のクラスライブラリ設計 (P.83)変更可能 (mutable) な値型を定義してはいけません。
これは是非従うべき!
public struct Point { public int X { get; set; } public int Y { get; set; } // 以下略 } class Program { static void Hoge(Point p) { p.X = 10; } static void Main() { var p = new Point() { X = 1, Y = 1 }; Hoge(p); Console.WriteLine(p.X); } }
とか、罠過ぎる。
コンストラクタが常に最善とは限らない
System.DateTime はいくつかのコンストラクタオーバーロードを持ちます。最も強力でありながら、同時に最も複雑なオーバーロードは、8 つのパラメータを取ります。ありがたいことに、コンストラクタのオーバーロードによって、この型は 3 つのシンプルなパラメータ、すなわち hour、minute および second を取る、より短いコンストラクタもサポートします。
public struct DateTime { public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar) { ... } public DateTime(int hour, int minute, int second) { ... } }.NET のクラスライブラリ設計 (P.102)
何もありがたくないよ!いや、確かに下のコンストラクタがないと困るけど、そうじゃないだろ。
コンストラクタのオーバーロードでは、year, month, day を引数に取るバージョンは作れない。
なんで、ここは素直にファクトリにすべき場所だと思うんだけどなぁ。
.NET のクラスライブラリ設計 (P.285)特別な構築メカニズムと比較すると、一般的にはコンストラクタのほうがユーザビリティ、一貫性、利便性に優れているので、ファクトリよりもコンストラクタを優先して使用します。
それはない。DateTime のコンストラクタのユーザビリティとかもうね・・・
常にファクトリを用意しろ、って言うつもりはないけど、ユーザビリティとリーダビリティは一般的にはファクトリのほうが上じゃないかな?
ただまぁ、ファクトリに対してもうちょっとサポートが欲しいのは確か。
戻り値型が異なるオーバーロードについての言及はないのか・・・
できれば避けて欲しいんだけど。
たしか標準ライブラリの中にこれがあってはまったことがあるんだけど、なんだったか忘れてしまった・・・
一番近いのは、105 ページの「異なるセマンティクスを持つにもかかわらず同じ位置に似たような型があるパラメータを持つオーバーロードを定義してはいけません。」かな。
null について
.NET のクラスライブラリ設計 (P.106)オプションのパラメータに対しては null を渡すことを認めます。
〜略〜
このガイドラインが、魔法の定数として null を使用することを開発者に推奨することを意図しているのではなく、むしろ先ほど説明したように明示的なチェックを避けることを意図していることに注意してください。実際に、API を呼び出すときにリテラルの null を使用する必要があるときはいつでも、コードにエラーがあるか、またはフレームワークが適切なオーバーロードを提供していないことを示します。
あー、オプションのパラメータに null を渡すようなシナリオがあるなら、それはそのパラメータを削ったオーバーロード版を提供すべき、って考え方は面白いな。
null を生成するメソッド・プロパティを極力避ける、という項目も欲しかったけど、どうも見つからない。
メソッドかプロパティか
経験則として言えるのは、メソッドは動作を表現すべきであり、そしてプロパティはデータを表現すべきであるということです。
.NET のクラスライブラリ設計 (P.112)
流れるようなインターフェイス・・・
イニシャライザ前提のプロパティ
.NET のクラスライブラリ設計 (P.116)設定専用のプロパティ、またはゲッタよりも幅広いアクセス可能性を持つセッタを持つプロパティを提供してはいけません。
ゲッタよりも可視性の広いセッタは、イニシャライザで使うことを前提としてます、って意志表明に使えないかな・・・
public class Hoge { public int X { private get; set; } }
として、
var h = new Hoge() { X = 10 };
みたいな!
拡張メソッド
.NET のクラスライブラリ設計 (P.138)拡張メソッドの使用は、以下のシナリオのいずれかで検討します。
- インターフェイスのすべての実装に関係するヘルパ機能がコアインターフェイスの観点から書くことが可能である場合に、そのようなヘルパ機能を提供するため。
- ある型に依存するインスタンスメソッドを導入するとき、そのような依存性が依存性の管理ルールを破壊することになる場合。
個人的には拡張メソッドによって null 安全なメソッドが作れる、というのも大きいのだけれど・・・
極力 null は排除したいんだけど、現実はそうあまくないよね、という。
あと次のページで知ったんだけど、VB って System.Object の拡張メソッド呼び出せないんだ・・・
演算子のオーバーロード
.NET のクラスライブラリ設計 (P.144)
あ、C++ を Dis ってますか?
・・・というのはおいといて、C# にも Boost.Xpressive 的なものが欲しいなぁ、なんて思ってたり。
あと拡張メソッド的に演算子オーバーロードを定義したいなぁ、とか。
4.0 では dynamic 使って・・・ってのもできなくはないみたいだけど・・・使う側で意識させたくないよね・・・
演算子に対応する名前
.NET のクラスライブラリ設計 (P.144 - 145)各オーバーロードされた演算子に対応するフレンドリな名前のメソッドを提供することを検討します。
〜略〜
C# の演算子のシンボル メタデータ上の名前 フレンドリな名前 ... ... ... + (二項) op_Addition Add - (二項) op_Subtraction Subtract ... ... ... != op_Inequality Equals ... ... ...
+ に Add、- に Subtract ってのはどうだろう。単純に Plus/Minus で良くないか?
例えば
- 作者: ジョシュア・ブロック,ニール・ガフター,柴田芳樹
- 出版社/メーカー: ピアソン・エデュケーション
- 発売日: 2005/11/14
- メディア: 大型本
- 購入: 3人 クリック: 83回
- この商品を含むブログ (56件) を見る
には、
Java の不変型のメソッド名には、誤解させるものがあります。add、subtract、negate などの名前は、これらのメソッドが呼び出されたインスタンスに対して変更を行うような印象を与えます。もっと良い名前は plus、minus、negation です。
Java Puzzlers 罠、落とし穴、コーナーケース パズル 56: 大問題 (Big Problem) (P.132)
そうすると、API 設計者に対する教訓は、不変型に対するメソッドの名前付けを行う場合には、動詞よりも前置詞と名詞を選ぶということです。
とあるし。
あと、!= が Equals になってるのは、!= 版は用意せずに == 版を ! しろ、ってことだろうけど・・・うーん。
PowerCollections
PowerCollections プロジェクトは System.Collections.Generic 名前空間を拡張するライブラリです。これはその名前空間に含まれる抽象に対する素晴らしいフィードバックおよび検証のソースです。
.NET のクラスライブラリ設計 (P.173)
こんなものあったのか!知らなかった!!
Deque、Set、Bag/MultiDictionary などに加え、Algoritms の充実っぷりも素晴らしい・・・
例外の再スロー
新しい例外をスローする時、実際に発生したエラーとは異なるエラーを報告しています。これも同様にアプリケーションをデバッグする能力を損ねます。したがって、常にスローするよりも再スローが望ましく、かつキャッチアンドスロー (および再スロー) はどちらも避けるようにしてください。
.NET のクラスライブラリ設計 (P.196)
.NET のクラスライブラリ設計 (P.198)例外をラップするときには内部例外を指定します。
throw new ConfigurationFileMissingException(..., e);このことをどの程度注意深く考え抜く必要があるのかについて十分に強調されていない可能性があります。よくわからないときは、例外を他の例外にラップしてはいけません。CLR において、ラップ処理があらゆる種類のトラブルを引き起こすことが知られている例はリフレクションです。リフレクションを使用してメソッドを実行したとき、メソッドが例外をスローすると、CLR はそれをキャッチし、新しい TargetInvocationException をスローします。実際のメソッドおよびメソッド内の問題のある場所を隠ぺいするので、これは信じがたいほどに迷惑なものです。
上のはちょっと後半よくわからない。
下のは、TargetInvocationException とかモロに Java の影響を受けてるよな・・・
Java と違って C# には検査例外がないんだから、例外をラップする意味はほとんどないはず*3。だから C# では基本的には例外をラップすべきではない。
DebuggerDisplay 属性
そんなものあったのね・・・(233 ページ)
EditorBrowsable 属性なんてのもはじめて知った (66 ページ)
用語
ファクトリには 2 つの主要なグループがあります。すなわち、ファクトリメソッド (factory method) とファクトリ型 (factory type) です。ファクトリ型は「アブストラクトファクトリ (abstract factory)」とも呼ばれます。
.NET のクラスライブラリ設計 (P.284)
ぎゃー!これはひでぇ!
これだと「Factory Method パターンはファクトリ型だからアブストラクトファクトリ」っつー意味不明な状況に・・・
結論
色々書いたけど、とてもいい本ですよ!
名前的には「.NET」なんて付いてるけど、他の言語を使っている場合でも参考になる部分は多々あるので、C# 使わないよ、って人でも読んでみるといいかも。
ただ、ちょっと高いのと、誤植的な物も結構あるのでまずは会社で買ってもらえばいいんじゃないかな。
間違い部分をメールしたら、増刷で修正されるみたいですし。
あと正誤表も用意されるようだから、値段がネックにならないなら是非買いましょう!
値段分の価値はあるかって?あるある。絶対にある。