.NET のクラスライブラリ設計

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

とっくに読み終わっていたんだけど、まとめる時間がなかったのでかなり時間が空いてしまった・・・
ということで基本的には「・・・ん?」って思ったところとかのまとめです。

アセンブリと名前空間

よく Java の package と C# の namespace を同じようなものとして扱っている人はいるけど、この本では、

アセンブリ
パッケージング及び配置の境界
名前空間
開発者に対する論理的なグループ

としている*1
Java の package はこの 2 つを合わせたもの*2だし、Java名前空間はファイルの配置という物理的なグループも作り出すので、まったく同じものというわけではない。


なので、Java の package と C# の namespace の使い方は違ったものであるべきなのに、どうもそう考えていない人は多いっぽい・・・
この話はまた機会があればまとめようかな。時間があれば、だけど。
Java の package については、

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

の 20 章を熟読すること。

一般的な型名を採用してもいいんじゃ?

Element、Node、Log、および Message のような一般的な型名を採用してはいけません。

一般的なシナリオにおいて、非常に高い可能性で型名の競合が誘発されます。汎用的な型名は修飾すべきです (FormElement、XmlNode、EventLog、SoapMessage)。

.NET のクラスライブラリ設計 (P.48)

うーん、こういうことを回避するために名前空間があるんじゃないの?
それぞれ、Form.Element、Xml.Node、Event.Log、Soap.Message な感じで名前空間で区切って、競合する場合は直前までを using すればいい話で、むしろ一般的な型名はどんどん付けるべき。

メソッド名の付け方

メソッドには動詞または動詞句の名前を付けます。

.NET のクラスライブラリ設計 (P.56)

ファクトリメソッドの名前は、"Create" という単語に作成される型の名前を連結したものにすることを検討します。

.NET のクラスライブラリ設計 (P.288)

これらはよくある規約だけど、もう古くなっていて、これだけじゃ足りないと思う。
例えば、Effective Java の第二版では、

valueOf
大ざっぱに言えば、パラメータと同じ値を持つインスタンスを返します。〜略
of
EnumSet (項目 32) で広まった、valueOf に対する簡潔な代替です。
getInstance
〜略
newInstance
getInstance に似ていますが、newInstance は返される個々のインスタンスはすべて別々のインスタンスである点が異なります。
getType
getInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に使用されます。Type はファクトリーメソッドから返されるオブジェクトの型を示しています。
newType
newInstance に似ていますが、ファクトリーメソッドが対象のクラスと異なるクラスにある場合に使用されます。Type はファクトリーメソッドから返されるオブジェクトの型を示しています。
Effective Java 第 2 版 項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する (P.10)

何らかの処理を行うメソッドは、一般に動詞あるいは (目的語を含む) 動詞句で命名されます。たとえば、append や drawImage です。
〜略〜
特別に述べておくべきメソッド名が多少あります。オブジェクトの型を変換し、別の型の無関係なオブジェクトを返すメソッドは、たいていは toType と呼ばれます。例えば、toString、toArray です。レシーバーオブジェクトの型と異なる型を持つビュー (view) (項目 5) を返すメソッドは、たいていは asType と呼ばれます。たとえば、asList です。メソッドが呼び出されたオブジェクトと同じ値を持つ基本データを返すメソッドは、たいていは typeValue と呼ばれます。たとえば、intValue です。static ファクトリーメソッドに対する共通の名前は、valueOf、of、getInstance、newInstance、getType、newType です (項目 1、10 項)。

Effective Java 第 2 版 項目 56 一般的に受け入れられている命名規約を守る (P.231 - 232)

などとある。
例えば、タプルを生成するメソッドが Tuple.CreateTuple とか、Tuple.Create とかはどうなんだ、みたいな。Tuple.Of の方が短いし分かりやすいと思うんだけど・・・


このほかにも、流れるようなインターフェイスを設計する場合なども古い規約では対応できない。

CanXxx

CanRead は Readable よりも理解しやすいものです。しかしながら、Created は実際に IsCreated よりも読みやすいものです。プレフィックスを持つことは多くの場合、特にコードエディタ内で IntelliSense に表示されるときに、冗長過ぎ、かつ不要です。
〜略〜

受動態よりも能動態を優先して選択すべきです。

if (stream.CanSeek) // こちらのほうが良い
if (stream.IsSeekable)
.NET のクラスライブラリ設計 (P.57 - 58)

個人的には、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

事前および事後のイベントを示すために、"Before" および "After" というプレフィックスおよびサフィックスを使用してはなりません。

.NET のクラスライブラリ設計 (P.58)

その代わりに現在進行形によって事前のイベントを、過去形によって事後のイベントを表せ、といっているんだけど・・・
Before/After の方が分かりやすくない?事前のイベントについては特に。

イベントハンドラアンチパターン

イベントハンドラに対して、"sender" および "e" という名前の 2 つのパラメータを使用します。

sender パラメータはイベントを発生させたオブジェクトを表します。sender パラメータは、より特定的な型を採用可能であったとしても、一般的に object 型になります。このパターンは .NET Framework 全体で一貫して採用されています。

.NET のクラスライブラリ設計 (P.59)

イベントハンドラの最初のパラメータには、型として object を使用し、名前を sender にします。

〜略〜

なぜ?みんないつもこう尋ねてきます。結局、これは単なるパターンなのです。

.NET のクラスライブラリ設計 (P.134)

これはアンチパターンなんじゃないの?過去の遺産があることは分かるけど、これをパターンと言い張るには無理があるんじゃないだろうか?
sender が object だと、いつもいつも sender の型の確認とキャストが必要になって、DRY に違反しまくり。
だからこれには従うべきじゃないんじゃないかな。

class と struct

ライブラリで頻繁に使用されることが予測される型をプロファイルし、実際に発生しないかもしれないような思い込みではなく実際のデータに基づいて、参照型を値型に変更することを推奨します。

.NET のクラスライブラリ設計 (P.70)

これはリファクタリング手順を確立させる必要があるかも。

参照型は参照によって渡され、逆に値型は値によって渡されます。

.NET のクラスライブラリ設計 (P.70)

とても誤解を生みそうな表現の気が・・・

スマートポインタ?

"スマートポインタ" な値型を作成することによって、それらが通常のオブジェクトであるかのように配列内の要素を参照することも可能です。これらの型は 2 つのフィールドを持ちます。最初のフィールドは巨大な配列に対する参照、2 番目のフィールドは配列のインデックスです。ユーザーの視点からは、これらの "スマートポインタ" の使用は通常の参照型の使用のように見えますが、メモリ使用量は大きく削減されます。

.NET のクラスライブラリ設計 (P.71)

スマートポインタ・・・用語の再利用はさけて欲しいんだけど・・・
スマートポインタというと普通は C++ のスマートポインタだよね。

構造体の設計

変更可能 (mutable) な値型を定義してはいけません。

.NET のクラスライブラリ設計 (P.83)

これは是非従うべき!

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 について

オプションのパラメータに対しては null を渡すことを認めます。

〜略〜

このガイドラインが、魔法の定数として null を使用することを開発者に推奨することを意図しているのではなく、むしろ先ほど説明したように明示的なチェックを避けることを意図していることに注意してください。実際に、API を呼び出すときにリテラルの null を使用する必要があるときはいつでも、コードにエラーがあるか、またはフレームワークが適切なオーバーロードを提供していないことを示します。

.NET のクラスライブラリ設計 (P.106)

あー、オプションのパラメータに 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 使って・・・ってのもできなくはないみたいだけど・・・使う側で意識させたくないよね・・・

演算子に対応する名前

各オーバーロードされた演算子に対応するフレンドリな名前のメソッドを提供することを検討します。

〜略〜

C#演算子のシンボル メタデータ上の名前 フレンドリな名前
... ... ...
+ (二項) op_Addition Add
- (二項) op_Subtraction Subtract
... ... ...
!= op_Inequality Equals
... ... ...
.NET のクラスライブラリ設計 (P.144 - 145)

+ に Add、- に Subtract ってのはどうだろう。単純に Plus/Minus で良くないか?
例えば

Java Puzzlers 罠、落とし穴、コーナーケース

Java Puzzlers 罠、落とし穴、コーナーケース

には、

Java の不変型のメソッド名には、誤解させるものがあります。add、subtract、negate などの名前は、これらのメソッドが呼び出されたインスタンスに対して変更を行うような印象を与えます。もっと良い名前は plus、minus、negation です。
そうすると、API 設計者に対する教訓は、不変型に対するメソッドの名前付けを行う場合には、動詞よりも前置詞と名詞を選ぶということです。

Java Puzzlers 罠、落とし穴、コーナーケース パズル 56: 大問題 (Big Problem) (P.132)

とあるし。
あと、!= が Equals になってるのは、!= 版は用意せずに == 版を ! しろ、ってことだろうけど・・・うーん。

PowerCollections

PowerCollections プロジェクトは System.Collections.Generic 名前空間を拡張するライブラリです。これはその名前空間に含まれる抽象に対する素晴らしいフィードバックおよび検証のソースです。

.NET のクラスライブラリ設計 (P.173)

こんなものあったのか!知らなかった!!
Deque、Set、Bag/MultiDictionary などに加え、Algoritms の充実っぷりも素晴らしい・・・

例外の再スロー

新しい例外をスローする時、実際に発生したエラーとは異なるエラーを報告しています。これも同様にアプリケーションをデバッグする能力を損ねます。したがって、常にスローするよりも再スローが望ましく、かつキャッチアンドスロー (および再スロー) はどちらも避けるようにしてください。

.NET のクラスライブラリ設計 (P.196)

例外をラップするときには内部例外を指定します。

throw new ConfigurationFileMissingException(..., e);

このことをどの程度注意深く考え抜く必要があるのかについて十分に強調されていない可能性があります。よくわからないときは、例外を他の例外にラップしてはいけません。CLR において、ラップ処理があらゆる種類のトラブルを引き起こすことが知られている例はリフレクションです。リフレクションを使用してメソッドを実行したとき、メソッドが例外をスローすると、CLR はそれをキャッチし、新しい TargetInvocationException をスローします。実際のメソッドおよびメソッド内の問題のある場所を隠ぺいするので、これは信じがたいほどに迷惑なものです。

.NET のクラスライブラリ設計 (P.198)

上のはちょっと後半よくわからない。
下のは、TargetInvocationException とかモロに Java の影響を受けてるよな・・・
Java と違って C# には検査例外がないんだから、例外をラップする意味はほとんどないはず*3。だから C# では基本的には例外をラップすべきではない。

DebuggerDisplay 属性

そんなものあったのね・・・(233 ページ)
EditorBrowsable 属性なんてのもはじめて知った (66 ページ)

用語

ファクトリには 2 つの主要なグループがあります。すなわち、ファクトリメソッド (factory method) とファクトリ型 (factory type) です。ファクトリ型は「アブストラクトファクトリ (abstract factory)」とも呼ばれます。

.NET のクラスライブラリ設計 (P.284)

ぎゃー!これはひでぇ!
これだと「Factory Method パターンはファクトリ型だからアブストラクトファクトリ」っつー意味不明な状況に・・・

結論

色々書いたけど、とてもいい本ですよ!
名前的には「.NET」なんて付いてるけど、他の言語を使っている場合でも参考になる部分は多々あるので、C# 使わないよ、って人でも読んでみるといいかも。


ただ、ちょっと高いのと、誤植的な物も結構あるのでまずは会社で買ってもらえばいいんじゃないかな。
間違い部分をメールしたら、増刷で修正されるみたいですし。
あと正誤表も用意されるようだから、値段がネックにならないなら是非買いましょう!
値段分の価値はあるかって?あるある。絶対にある。

*1:45 ページあたり

*2:更に可視性の境界でもある

*3:Java では検査例外があるので、あまりにも低レベルな検査例外は実装の詳細をさらけだすことになるので、高レベルな検査例外か、RuntimeException 系の例外でラップして適切な粒度の例外に変換することはよくあるし、すべき

More Effective C#

More Effective C#

More Effective C#

を読むに当たって、注意すべきかなー、と思ったところをだらだらと。

ICloneable は実装すべきではない

ICloneable を実装することはお勧めできません



†:『Framework Design Guideline : Conversions, Idioms, and Patterns for Reusable .NET Libraries』(著/ Krzysztof Cwalina, Brad Abrams 刊/ Addison-Wesley) を参照してください。

More Effective C# 項目 1 1.x フレームワーク API のクラスではなく、ジェネリッククラスを使え (P.9)

とあるので、みなさん

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

も買いましょう、ってことですね!
・・・何はともあれ、ICloneable 使うな!って本が割と読まれるであろうタイトルの日本語で読める本として出てきたのはありがたい限り。

もうちょっと標準ライブラリについて言及があってもいいんじゃ・・・

例えば 11 ページで、

// ある型のシーケンスを別の型のシーケンスに変換
IEnumerable<TOutput> Transform<TInput, TOutput>(IEnumerable<TInput> theCollection, Converter<TInput, TOutput> transformer);
// testを満足させるすべての要素を集める
IEnumerable<T> Test<T>(IEnumerable<T> theCollection, Predicate<T> test);

なメソッドを実装してるんだけど、標準ライブラリに用意された似たようなメソッドである Select や Where については言及していないという・・・
しかも、ここだけじゃなくて本全体を通して言えることなので更にがっかり・・・

IDisposable

public interface IEngine
{
    void DoWork();
}

public class EngineDriver<T> where T : IEngine, new()
{
    public void GetThingsDone()
    {
        T driver = new T();
        driver.DoWork();
    }
}

T が IDisposable を実装する場合、リソースリークが起きます。T 型のローカル変数を作るときには、T が IDisposable を実装しているかどうかをかならずチェックし、実装している場合には、適切に廃棄される (Dispose() 呼び出しが行われる) ようにする必要があります

public void GetThingsDone()
{
    T driver = new T();
    using (driver as IDisposable)
    {
        driver.DoWork();
    }
}
More Effective C# 項目 5 ジェネリッククラスは、IDisposable を実装する型パラメータのサポートを忘れるな (P.29)

あー、これは今まで意識してなかった!
これは T に IDisposable 制約を付ければいいということではなくて、例えそうだとしても、それならそれで IEngine 自体が IDisposable を継承するはず。
あと、この問題は new 制約に限らず、ローカル変数を作るときは常に意識しなければならないことに注意が必要。

IEnumerator

IEnumerator<T1> leftSequence = left.GetEnumerator();
IEnumerator<T2> rightSequence = right.GetEnumerator();
while (leftSequence.MoveNext() &;&; rightSequence.MoveNext())
{
    yield return generator(leftSequence.Current,
        rightSequence.Current);
}
More Effective C# 項目 6 型パラメータのメソッド制約の定義にはデリゲートを使え (P.34)

「&;&;」 ってのは勿論「&&」の typo かなにかで、多分訳したときのミスだとおもうけど・・・
IEnumerator は IDisposable を実装してるんですよね。
で、この本もここ以外ではちゃんと using 使ってるんですけど、なぜかここでは使っていない・・・これは原著のミスかな?
正しくはこう。

using (var itr1 = left.GetEnumerator())
using (var itr2 = right.GetEnumerator())
{
    while (itr1.MoveNext() && itr2.MoveNext())
    {
        yield return generator(itr1.Current, itr2.Current);
    }
}

ただ、この本の流儀としては、

using (var itr1 = left.GetEnumerator())
{
    using (var itr2 = right.GetEnumerator())
    {
        while (itr1.MoveNext() && itr2.MoveNext())
        {
            yield return generator(itr1.Current, itr2.Current);
        }
    }
}

こっちはネストが深くなる上、そんなに分かりやすくなるとも思えないんですけどねー。

ラムダ構文を使うまでもない

// 無名デリゲート構文を使ったForEach
myInts.ForEach(delegate(int collectionMember)
{
    Console.WriteLine(collectionMember);
});

// ラムダ構文を使ったForEach
myInts.ForEach((collectionMember) => Console.WriteLine(collectionMember));
More Effective C# 項目 18 アクション、述語、関数から反復処理を切り離せ (P.99)

とあるんだけど、まずラムダ式の引数名はスコープも短いし短い名前でもいいと思うんだよね。
あと、引数が一個なら丸括弧も省略できるんだから、省略してしまって、

myInts.ForEach(i => Console.WriteLine(i));

・・・でも待って欲しい。これ、ラムダ構文すら必要ないよね?

myInts.ForEach(Console.WriteLine);

こうでしょ。
なんか他でも無駄にラムダ構文使ってる部分があったような・・・?

れいがい!

More Effective C# 項目 25 例外はメソッドの約束ごとが守れなかったときに使え (P.127〜)

頻繁に「エラー処理」という言葉が出てくるけど、エラー処理が何を意味するか、までは出てこないので各自考える必要がある。
れいがいむずい・・・

プロパティ

プログラマは、プロパティアクセッサが大仕事をすることを予想していません。プロパティゲッターは、コストのかかる処理をしてはなりません。プロパティセッターも、ある程度のデータチェックは必要でしょうが、呼び出しにコストがかかるようであってはなりません。
クラスを使うプログラマたちは、なぜこのような予想のもとに動いているのでしょうか。それは、彼らがプロパティをデータとして見ているからです。

More Effective C# 項目 26 プロパティはデータらしくふるまうように作れ (P.131)

基本的には同意なんだけど、「プロパティをプロパティとして使わない」ことってあるんだよねー。流れるようなインターフェイスとか。
まぁ、ほとんどの場合はプロパティをプロパティとして使うんで、問題ないでしょうけど。

var を使うことによる問題

More Effective C# 項目 30 ローカル変数はできる限り暗黙の型付けに委ねよ (P.147〜)

出来る限り var 使った方がいいよ!ってのには同意なんだけど、微妙な問題もあるんだよねぇ・・・
例えばこんなの。

static List<int> CreateList() { return new List<int>() { 0, 1, 2, 3, 4 }; }

static void Main()
{
    var l = CreateList();
    var l2 = l.Reverse().Take(2);   // コンパイルエラー
}

IEnumerable の拡張メソッドとして、IEnumerable を返す Reverse メソッドはあるにもかかわらず、これはコンパイルエラーになってしまう。
なぜなら、List にも Reverse メソッドが存在し、しかもこっちは void、つまり何も返さないから。
これを解決するにはいくつか方法がある。

  • CreateList の戻り値を弱くする。つまり、IList とか IEnumerable にする。
  • 呼び出し後にキャストする。つまり、(IList)CreateList() とか CreateList() as IList とする。
  • var をやめて型を明示する。つまり、IList l = CreateList(); とかにする。

一番目は影響範囲が大きいという可能性があるから、普通は二番目か三番目を選ぶかな。
どちらを選ぶにしても、コメントを書いておかないと分かりにくいとおもうので注意。


えー、なので、戻り値が異なるオーバーロードとかは出来る限り避けるのは当然として、3.0 以降は拡張メソッドにも注意が必要、ということになる。
List の Reverse が this 返すようにしてくれてりゃなぁ・・・


追記:
コメントで第四、第五の方法を教えてもらいました!

  • 呼び出し後に AsEnumerable を呼び出す。つまり、CreateList().AsEnumerable() とする。
  • l は弄らず、l2 のために使うときに AsEnumerable を呼び出す。つまり、l.AsEnumerable().Reverse().Take(2) とする。

メソッドチェイン最高!

LINQ という名の流れるようなインターフェイス

単に this(と言うか自分自身と同じ型) を返せばいいってもんじゃないってのは前に書いたとおりだけど、LINQ も同じような場所があった。

ThenBy は、ソートされたシーケンスでなければ操作できません

More Effective C# 項目 36 クエリ式からメソッド呼び出しへの変換がどのように行われるかを理解せよ (P.178)

で、確認してみると、確かに IEnumerable の拡張メソッドとしては ThenBy は定義されておらず、OrderBy メソッドの戻り値型が IOrderedEnumerable になっていた。
なので、ThenBy を使うためには必ず直前に OrderBy メソッド (もしくは ThenBy メソッド) を使用しなければならないことが強制できる。

全体

えー、typo というか、誤植が結構あります。残念です。
でも内容としては、ためになる内容もあるのでいい本です。

追記

Effective C#

Effective C#

なんか無印も出るみたいですよ、っと。
4.0 版待ってるから More が先に出るのかな、と思ったんだけど、どうやら違ったみたい。
嬉しいような、残念なような・・・


ちなみに 4.0 版はこちら。

Effective C#  (Covers C# 4.0): 50 Specific Ways to Improve Your C# (Effective Software Development Series)

Effective C# (Covers C# 4.0): 50 Specific Ways to Improve Your C# (Effective Software Development Series)

今日ぽちった本

SQL/RDB オンリー

Joe Celko's Trees and Hierarchies in SQL for Smarties (The Morgan Kaufmann Series in Data Management Systems)

Joe Celko's Trees and Hierarchies in SQL for Smarties (The Morgan Kaufmann Series in Data Management Systems)


Joe Celko's Data, Measurements and Standards in SQL (Morgan Kaufmann Series in Data Management Systems)

Joe Celko's Data, Measurements and Standards in SQL (Morgan Kaufmann Series in Data Management Systems)


Joe Celko's Data and Databases: Concepts in Practice (The Morgan Kaufmann Series in Data Management Systems)

Joe Celko's Data and Databases: Concepts in Practice (The Morgan Kaufmann Series in Data Management Systems)


Joe Celko's SQL for Smarties, Third Edition: Advanced SQL Programming (The Morgan Kaufmann Series in Data Management Systems)

Joe Celko's SQL for Smarties, Third Edition: Advanced SQL Programming (The Morgan Kaufmann Series in Data Management Systems)


Joe Celko's Thinking in Sets: Auxiliary, Temporal, and Virtual Tables in SQL (The Morgan Kaufmann Series in Data Management Systems)

Joe Celko's Thinking in Sets: Auxiliary, Temporal, and Virtual Tables in SQL (The Morgan Kaufmann Series in Data Management Systems)


SQL Design Patterns: Expert Guide to SQL Programming (It in-Focus Series)

SQL Design Patterns: Expert Guide to SQL Programming (It in-Focus Series)

Re:TDD を理解するためのまとめ

TDDを理解するためのまとめ - Logic Dice
を読んで、いい機会だと思って

テスト駆動開発入門

テスト駆動開発入門

  • 作者: ケントベック,Kent Beck,長瀬嘉秀,テクノロジックアート
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2003/09
  • メディア: 単行本
  • 購入: 44人 クリック: 1,026回
  • この商品を含むブログ (156件) を見る

を買って、実際に「Part 1 Money オブジェクトの例」を写経してみた。
んで、感じたことなどを。
あ、あくまで今の時点での考えなので、変なとこあったら指摘してくだしあ!

Red から Green、Green から Red

ある意味では残念な事なのですが、大抵のTDDの説明に記述されている、「RedからGreenに変わることが気持ちいい」という、その感覚が分からないのです。

TDDを理解するためのまとめ - Logic Dice

この気持ちは分からなくもない。
たまには Red から Green になって「おっしゃ!」と思うことはあっても、それは「気持ちいい」というわけではないし・・・
ここら辺は個人差なのだろうと割り切っておいた方がいいかもしれない。セールストーク、リップサービスの類の可能性もあるし、もしかすると伝言ゲーム的なノリかもしれないし。


ただまぁ、Red から Green の流れより、Green からの意図しない Red にほっとする、というのはよくある。
あぁ、テストがあってよかった、みたいな*1


それに、実は Eclipse

  • 高速ビュー On
  • エラー/障害時にのみアクティブにする On

と設定してるから、Green とか目にしないんだよねぇ・・・
VisualStudio に至っては、TestDriven.NET を使ってるから、そもそも色がないし。

自明な実装

TDDの紹介では、大概の場合に置いて小さなメソッドのテストが行われます。そこで説明されている事項は、説明のためとは言え、明らかに易しすぎるのです。その程度の事なら、テストを書く前に実装をしたい、と思ってしまうのです。

TDDを理解するためのまとめ - Logic Dice

これはまぁまさに説明のためで・・・
重要なのは、「この手順で実装できる」という安心感で、実際に本に書いてある通りの回りくどい方法を毎回毎回取る必要はない。
ただし、テストに関してはほぼ常に先に書く方がいいのかな、なんて思ったりする。

後で書くテストと先に書くテスト

確かに、先にテストを書くことで以降のテストのためのコードは残ります。後からテストを書くのを「やっぱりやめた」と言って、テストコードを書かないことを防ぎます。そして、そのテストを残す事がテストファーストの利点と言われます。
しかし、そう言われても、何か釈然としなかったのです。


その理由の1つは、例ではテストを単一メソッドに対して行っており、そのテストからスケルトンコード(REDを出力する実装)を生成していたことです。
メソッドはクラスの一部であり、そのクラスの振る舞いを定義します。
しかし、メソッドは詳細です。ただ1つのメソッドでは、クラス全体の振る舞いを知ることは出来ません。
故に、クラスという大ではなく、メソッドという小に焦点を当てているにも関わらず、そこからクラス全体をテストするためのコードを生み出そうとしているように見えることが、釈然としませんでした。
(注:記事の投稿時に確かめてみると、おそらくテスト駆動開発入門ではこれとは違う方法で生成を行っています。この本のPart1は詳細に進めすぎていて、逆に全体が見えてこない印象を受けましたが…)


そのような小をテストすることが単体テストである、と言われたらそれまでなのですが……

TDDを理解するためのまとめ - Logic Dice

テストを残すことは利点ではあるけど、その理由付けが違うんじゃないかなぁ。
テストを先に書く、というのは、インターフェイス*2と主な振る舞いを決定する意味合いが強くて、テストを後に書く、というのは、書いたものに対しての単体テストという意味*3しか持たない。
だから、TDD で記述するテストを「単体テスト」とひとまとめにしてしまうのはちょっと違和感がある。
TDD で記述するテストは、網羅性は求めていなくて、YAGNI に則って最小限の入出力分のテストさえあればいいんだと思う。
もちろん、それだけだと単体テストにはならないから、後で単体テスト用のテストは追加しなければならないんだけど。


なんで、わんくまで言ってたような入出力を網羅したようなテストって、実は TDD 的ではないんじゃないかなぁ、というのが自分の考え。
入出力を網羅したようなテストって、ある程度中がどうなってるか分からないと書けないというか、最初からそこまでがっちがちに振る舞いを決定する必要はないというか・・・
例えば入出力を網羅したようなテストを最初に書いたとすると、その後可能なリファクタリングってどう考えても狭まる。
出来なくはないけど、リファクタリング作業がとても重いものになってしまう。
リファクタリングのためのテストなのに、リファクタリングを阻害してしまうのは本末転倒だと思う。


TDD によりテスト容易性は確保されるので、TDD せずに後で単体テストを作るより、TDD で開発してあとで単体テストも作る方がテストは楽になるような気がする。
や、もちろんテストを意識して設計することでもそれは達成できるんで、結局は使う人の腕次第・・・?

テストの寿命

仮に「入出力を網羅したようなテストはある程度実装を意識する必要がある」が正しいとすると、それらのテスト群は TDD により作成したテスト群よりも、寿命が短いと言うことになる。
なぜなら、実装を意識している以上、リファクタリング時 (つまり実装の変更時) にそれらのテスト群は一斉に変更が必要となる可能性が高いから (変更しなかった場合、一斉に Red となる)。
こういう寿命の短いテストは、やはりリファクタリングには邪魔にしかならないため、最初から作り込む必要はないだろう。


また、いつでも簡単に切り捨てることが出来るように、TDD により作成した一連のテストとは別の場所・別のファイルにしておくということも考えられる。
ルートディレクトリレベル、ディレクトリレベル、ファイルレベル、ファイル内での記述位置レベル、どの程度切り離すかは分からない。

シナリオベースのテスト:TDD によるテスト、メソッドベースのテスト:単体テスト

とか、そんなことを思ったりした。
いや、こんなりはっきり分かれるわけではないだろうけど、少なくともシナリオベースのテストが (このエントリで言っているような) 単体テストであることは少ないように思う。
メソッドベースのテストが TDD によって生み出されることもないとは言わないけど、比率としてはシナリオベースのテストの方が多くなると思うし、多くあるべき。
やっぱりメソッドベースのテストというのは、入出力に重きを置くテストのように感じるし、事実そうだと思う。

ナイトリー (デイリー) ビルド時の回帰テストとコミット時の回帰テスト (スモークテスト)

ナイトリービルド時には時間による制限がないため、メソッドベースのテスト、つまりこのエントリでの単体テストを含めて実行すればいいが、コミット時はフィードバックがすぐにあることが重要なため、それほど時間をかけることが出来ない。
ここで、実行するテストの取捨選択が必要になってくるが、このテストのために TDD によるテストを基準に選択するのはどうだろうか。
基本的には TDD によるテストをすべて使用するのだが、それでも遅くなってきた場合は正常系のみに絞る、とか。

TDD による設計

シナリオテストによるクラス設計

ある意味では驚くべきことですが、シナリオテストを十分に考慮してプログラミングをすると、設計の質が上がりやすい傾向があります。
これは、そのクラスが外部からどのように使われるかを十分に吟味するためです。
自分がその新しく作られるクラスを使う視点に立つことで、変な使い方をしていないかをチェックすることができます。

TDDを理解するためのまとめ - Logic Dice

TDD は設計手法だ、というのはよく言われているし、実際、その通りだと思う。
BDD (Behavior Driven Development:振る舞い駆動開発) というものも、その側面を前面に押し出すために作られたような言葉で、個人的にはぶっちゃけ TDD と違いはないと思っている。
TDD は「テスト」という言葉を使っているように、どうしても従来のテスト (このエントリ (ry 単体テスト) を思い浮かべてしまうようで、それからの脱却という意味もあるんじゃないかな > BDD


あー、あと、「クラス設計」という言葉はあまり好きじゃない。本題にはあんまり関係ないけど。
まぁ、その話はまたの機会ということで・・・

エントリ全体

TDD のテストとしてメソッドベースのテストも考えているという点以外ではおおむね同意できた。
おおむね良いんじゃないかな。

ここからは・・・

超簡易版で本自体の感想 (感想?) でっす。

  • 固定値による実装→ 2 つ目のテストで一般化、というのは恣意的すぎる気がする
  • TODO リストの項目がどう出てくるのかの説明が足りないような気がする
  • ドキュメンテーションコメントはいつ書く?
  • アサーションはいつ書く?
  • コミットのタイミング迷う
  • コミット、TODO リスト、チケットの関係もどうするか迷う
  • P.55 で TODO リストから消えた項目の説明がない

あと実際に写経してみて分かったんだけど、IDE と相性が良いという割には・・・って、これは Eclipse の問題ですね、確実に。

*1:まぁただ、一気に大量に Red になると逆に悲しくなるけど

*2:言語仕様としての interface ではなくて、要は使い方

*3:品質を保証するためのテスト

Touch of Class 届きました!

届きましたよ!
ハードカバーな上に、でかい。そして重い。
大きさが分かるように、Prolog の技芸と The Art of Multiprocessor Programming と並べてみました。


厚さはこちら。真ん中が Prolog の技芸で、一番下にしかれているのが Programming in Haskell です。


・・・これじゃ比較にならないですね。
デザインパターン本と比較したのがこの写真です。

デザインパターン本よりも縦も横も一回りくらい大きいです。厚さは、デザインパターン本の 1.5 倍程度でしょうか。
とにかくでかいです。

My Job Went To India

My Job Went To India オフショア時代のソフトウェア開発者サバイバルガイド

My Job Went To India オフショア時代のソフトウェア開発者サバイバルガイド

きむら (K) さんにすすめられて買ってはみたものの、それなりの間積んであった本を、盆に帰省した際の電車の中で読んだ。
・・・えーっと、何でもっと早くにこの本読まなかったんだ! > 自分


技術者のあり方とか、会社に関しての考え方とか、色々と考えさせられる本でした。
本屋で実物を見ても、きっとスルーするような本 (表紙とか薄さとかで) だったんで、本当すすめてくれてありがとうございました、としか。
やっぱネットっていいものですねー。


とりあえず、id:rf0444 と、id:a-hisameid:WK6 は読めば良いと思うよ!

My Job Went To India オフショア時代のソフトウェア開発者サバイバルガイド

My Job Went To India オフショア時代のソフトウェア開発者サバイバルガイド

その他っぽい本を積んでみた

内容はまた後で・・・?
取消線が入ってるのは「読むな危険」、タイトルしかないものは「他にいい本があるか古いか興味の対象外」、それ以外はそれ以外です。

一番左の山

Web っぽい本

左から 2 番目の山

ぺらい本
読み物その 2
作法とかセンスとかびゅーてぃほーとか
言語っぽい本たち

真ん中の山

読み物その 3
OS とかシェルスクリプトとかコンパイラとかそんな感じ

右から 2 番目の山

読み物その 4
数学ちっくな本
構成管理とか
アジャイルとかドメイン駆動とか達人とか

一番右の山

その他のその他
ねっとわーく・せきゅりてぃ
UI
入れるとこミスった・・・
オブジェクト指向とか、SICP とか CTMCP とか

撮り忘れた本

今手元にない本