y_megane.log

日々の勉強や改善ネタの備忘。

C/C++で作られたDLLをC#から利用する際の引数の渡し方色々

C言語で書かれたDLLをC#から扱う際に引数の受け渡しが分からなくて色々調べたので、その備忘。

intを引数にとる関数

//C++
//intを受け取って表示し、1加えて返す
int __stdcall MyFuncA(int a) {
    printf("C++ : int a = %d\n", a);
    return a + 1;
}
//C#
[DllImport("MyDll.dll")]
private static extern int MyFuncA(int a);

int ans = MyFuncA(1);
Console.WriteLine("C# : ans = {0]",ans);
出力結果
C++ : int a = 1
C# : ans = 2

int*を引数にとる関数

C#からDLLに値を渡すことはできるが、DLL側での操作がC#の変数に反映できてない。
私の用途ではコレで困らなかったが、C#側にDLL側の操作を反映するにはこれではダメ。要調査。

//C++
//intのポインタを受け取って値を変更。前後の値を出力する。
void __stdcall MyFuncG(int* a) {
    printf("C++ : a = %d\n", *a);
    int x = 100;
    a = &x;
    printf("C++ : a = %d\n", *a);
}
//C#側
[DllImport("MyDll.dll")]
private static extern void MyFuncG(ref int a); //refで参照渡し

int a = 77;
Console.WriteLine($"C# : a = {a} -- before");
MyFuncG(ref a);
Console.WriteLine($"C# : a = {a} -- after");
出力結果
C# : a = 77 -- before
C++ : a = 77           //値を渡すことはできている
C++ : a = 100
C# : a = 77 -- after   //DLL側での操作が反映できていない

cahr*を引数にとる関数(入力のみ)

//C++
void __stdcall MyFuncB(char* str) {
    printf("C++ : %s\n", str);
}
//C#
[DllImport("MyDll.dll")]
private static extern void MyFuncB(string str);

MyFuncB("call MyFuncB from C#");
出力結果
C++ : call MyFuncB from C#

char*を引数にとる関数(文字列を操作する)

//C++
void __stdcall MyFuncC(char* str) {
    printf("C++ : before : %s\n", str);
    sprintf_s(str, 256, "Set By MyFuncC");
    printf("C++ : after  : %s\n",str);
//C##
[DllImport("MyDll.dll")]
//文字列を操作する関数に対してStringBuilderを渡す
private static extern void MyFuncC(StringBuilder str);

StringBuilder sb = new StringBuilder("Before Str",256);
MyFuncC(sb);
Console.WriteLine("C# : " + sb);
出力結果
C++ : before : Before Str
C++ : after  : Set By MyFuncC
C# : Set By MyFuncC            //DLL内での変更が反映されている

構造体を引数にとる関数

//C++側
typedef struct _SampleStruct {
    int index;
    char name[128];
    int data[50];
}SampleStruct

void __stdcall MyFuncD(SampleStruct st) {
    printf("C++ : index = %d\n", st.index);
    printf("C++ : name = %s\n", st.name);
    printf("C++ : data = {%d, %d, %d}\n",
            st.data[0], st.data[1], st.data[2]);
}
//C#
[DllImport("MyDll.dll")]
private static extern void MyFuncD(SampleStruct st);

//構造体定義
//構造体の型、大きさ、順序を揃える。
[StructLayout(LayoutKind.Sequential)]
private struct SampleStruct
{
    public int index;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] //固定長文字列配列
    public string name;

    //固定長配列
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)] //固定長配列
    public int[] data;
}

//構造体の初期化
var st = new SampleStruct()
{
    index = 4,
    name = "構造体サンプル",
    data = new int[50],
};
st.data[0] = 10;
st.data[1] = 20;
st.data[2] = 30;
MyFuncD(st);
出力結果
C++ : index = 4
C++ : name = 構造体サンプル
C++ : data = {10, 20, 30}

構造体のポインタを引数にとる関数

//C++
//構造体は上と同じものを利用
void __stdcall MyFuncE(SampleStruct* pst) {
    (*pst).index = 55;
    sprintf_s((*pst).name, "構造体ポインタサンプル");
    (*pst).data[0] = 51;
    (*pst).data[1] = 52;
    (*pst).data[2] = 53;
}
//C#
//構造体は上と同じものを利用

[DllImport("MyDll.dll")]
//SampleStruct*に対してIntPtrを渡す
private static extern void MyFuncE(IntPtr pst);

var pst = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SampleStruct)));
try
{
    MyFuncE(pst);
    
    var st_rtn = (SampleStruct)Marshal.PtrToStructure(pst, typeof(SampleStruct));
    Console.WriteLine($"C# : index = {st_rtn.index}");
    Console.WriteLine($"C# : name = {st_rtn.name}");
    Console.WriteLine($"C# : data = [{st_rtn.data[0]},{st_rtn.data[1]},{st_rtn.data[2]}]");
}
catch (Exception e)
{
    System.Diagnostics.Debug.WriteLine(e);
}
finally
{
    //必ずメモリ解放
    Marshal.FreeHGlobal(pst);
}
出力結果
C# : index = 55                //DLL側の操作が反映されている
C# : name = 構造体ポインタサンプル
C# : data = [51,52,53]

構造体のポインタのポインタを引数にとる関数

//C++
//ポインタのポインタを引数にとるが処理内容は上と同じ
void __stdcall MyFuncI(SampleStruct** ppst) {
    (**ppst).index = 70;
    sprintf_s((**ppst).name, "構造体ポインタのポインタのサンプル");
    (**ppst).data[0] = 11;
    (**ppst).data[1] = 22;
    (**ppst).data[2] = 33;
}
//C#
//構造体は上と同じものを利用

[DllImport("MyDll.dll")]
//構造体SampleStructのポインタのポインタを渡す
unsafe private static extern void MyFuncI(ref IntPtr ppst);

//ref IntPtrを渡していること以外は構造体のポインタを渡した場合と同様
var pst = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SampleStruct)));
try
{
    MyFuncI(ref pst); // ref IntPtrとして構造体を指すポインタの参照を渡す
    
    var st_rtn = (SampleStruct)Marshal.PtrToStructure(pst, typeof(SampleStruct));
    Console.WriteLine($"C# : index = {st_rtn.index}");
    Console.WriteLine($"C# : name = {st_rtn.name}");
    Console.WriteLine($"C# : data = [{st_rtn.data[0]},{st_rtn.data[1]},{st_rtn.data[2]}]");
}
catch (Exception e)
{
    System.Diagnostics.Debug.WriteLine(e);
}
finally
{
    //必ずメモリ解放
    Marshal.FreeHGlobal(pst);
}
出力結果
C# : index = 70                          //DLL側の操作が反映されている
C# : name = 構造体ポインタのポインタのサンプル
C# : data = [11,22,33]

おわり

C、C++,C#いずれも全然習熟していない状態なので、もっとスマートな実装方法があるかもしれない。マサカリお待ちしてます。
ただポインタのポインタを受け渡す例などは調べても全然出てこなかったので、ニッチな困りごとの一助になれば幸いです。

参考

STS(Spring Tool Suits4)にLombak導入

STSLombok入れようとした際に困った点の備忘。

環境

Eclipse : Spring Tool Suits 4(Version: 4.3.1.RELEASE)
Lombok : v1.18.8
(2019/07/28時点)

導入手順

  • 公式からjarファイルをダウンロード
  • ダブルクリックで実行し、導入するIDEを選択する

だけのはずが、私の環境ではIDE候補が表示されなかった。 f:id:ymegane88:20190728143533p:plain 「Specify Location」を選んで手動でIDEを指定するようにとあるが、SpringToolSuits4.appやそれっぽいフォルダを選択できないので、どうしていいか分からず。

結論として、IDEの設定ファイル(ini)を指定する必要があった。

Applications > SpringToolSuits4.app > Contents > Eclipse > SpringToolSuits4.ini

を選択して「Install/Update」を押して実行するとSTS4にLombokが導入される。

Eclipse(STS)の補完設定(mac)

久しぶりにEclipseを触ったら補完周りが使いづらくて仕方ないので、最低限の設定方法の備忘。

環境

OS : macOS Mojava
Eclipse : SpringToolSuits4
(2019/07/28時点)

やること

  • 補完が常に効くようにする
  • Enter以外で補完が実行されないようにする

Javaに関してのみ設定するが、HTMLなど他のエディタでも同様。

設定方法

メニューバーより
SpringToolSuits > 環境設定 > Java > Editor > ContentAssist
赤線部分を下記の通り変更する
f:id:ymegane88:20190728140845p:plain

補完が常に出るようにする

Eclipseの初期設定では.を入力した場合のみ補完候補が表示される。
また補完候補の表示まで遅延がある。
Eclipseのバージョンによって初期設定が多少異なるらしい?)

補完候補が開くまでの遅延時間をなくす

Auto Activation > Auto activation delay(ms)
の値を0に設定する。
これで補完が効く状況では補完候補表示がすぐ表示される。

何を入力でも補完候補が開くようにする

Auto Activation > Auto activation triggers for Java
デフォルトでは.が設定されているので、これを以下のように変更する。
._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
これで.以外のキー入力でも補完候補が表示される。

Enter以外で補完が実行されないようにする

補完候補が表示されている場合、デフォルトではEnter以外のキーでも補完が実行される。
困る例として、

String hoge = "hogehoge";

と入力したい場合、hogeを入力した時点でhogeStringなどの候補が表示され、次のスペース入力で補完が実行されてしまう。
上の通り入力するにはhoge esc スペース・・・のように入力する必要があり面倒くさい。

Insertion > Disable insertion triggers except 'Enter
のチェックボックをオンにする。
これでEnterキー以外では補完が実行されなくなる。

書籍「教える技術」

行動科学を使ってできる人が育つ! 教える技術

行動科学を使ってできる人が育つ! 教える技術

数年前に後輩への指導方法に悩んで買った本。
ふと返し読みしたので、簡単に振り返り。

感想

性格や考え方ではなく「行動」にフォーカスした内容。
具体的にどういう行動が出来てないのか分解して分析し、数件ずつ指摘する。
できたら褒めて、行動を強化。いつでもできるよう習慣付ける。

「行動に着目しろ」という本だけあって、この本を読んだ教える側にも具体的な行動を提示してくれる。 後輩の指導、先輩の指導の受け方、自分の行動の修正など、実際に使える手法や考え方が多い。

後輩や同僚の指導、指摘をする機会は多いが、やり方を間違えると相手を辞めさせたり精神的にダウンさせかねない。 自分や周囲の指導に不安を感じたらぜひ読んでみて欲しい本。

ざっくり要約

概要

「できない」状態とは相手が自分の期待通りの行動をしない、という状態。
自分が期待する行動、理想的な行動を「できてる」人を観察して細かく分解する。
どの行動が「できてない」か分析し、直して欲しい行動を数件指摘する。
「やらなくていいこと」を伝えることも有効。正すべき行動のみに注力させ、評価する。
そして、次の行動で指摘した部分ができたならちゃんと褒める。
行動の修正と評価を繰り返し、できない>できる>いつでもできる という成長を実現する。

ABCモデル

  • A:Antecedent=先行条件
  • B:Behavior=行動
  • C:Consequence=結果

状況に対して行動を起こし、ハッピーな結果を得る
⇛ 行動が強化される(身につく)

「行動」の粒度・指標

MORSの法則

  • M:Measured=計測できる
  • O:Observable=観察できる
  • R:Reliable=信頼できる
  • S:Specific=明確化されている

信頼されるべし

教える相手から教える側への信頼や安心感がないと、そもそも「褒める」が機能しない。
(嫌いな相手、信頼できない相手に褒め言葉を言われても嬉しくない)
そうなると正しい行動を強化できず、教えること自体が成り立たない。

直接的な教える技術ではないが、日頃からコミュニケーションをとる、ネガティブな態度を見せないなど、 何でも話せる・挑戦させてくれる、という安心感を相手に与えられるように振る舞う必要がある。

書籍「みんなが自分で考え始める「15分ミーティング」のすごい効果」

みんなが自分で考えはじめる 「15分ミーティング」のすごい効果

みんなが自分で考えはじめる 「15分ミーティング」のすごい効果

感想

「Yahooの1on1」などのミーティング関連本と言っていることは概ね同じ。
ただ違うのは「できる人・職場を磨くための本」というより「出来ない職場で苦労してきた本」という感じ。
ミーティングやコーチングの技術を、筆者流にできるだけ簡単で明確な方法にして実際のミーティングに落とし込んだ、という印象。
理論本ではなく実践アイデアのひとつ。そのため職場に導入したり、人に教えるためのハードルが低く感じられる。
ターゲット層の合わせてか、文量も少なく1〜2時間で読み切れるコンパクトさも、困ったちゃん職場的に◎

ざっくり要約

  • ミーティングの時間は15分を目安にする
  • 提案、整理、計画立てを各5分程度
  • 提案は全員が付箋に決めた数のアイデアを書いて発表
    • 簡潔に意見をまとめる訓練。
    • 「私も同じ」を防ぐ。同じでも表に出てくる。
  • 声の小さい人から順に発表。
    • 強い意見に委縮しないため
  • 付箋に書いた事以外は話さない。
    • 発表されてない意見が採用されると、無駄な会議と思われる。
  • 結論を誘導しない
    • 誘導は雰囲気で伝わる。無駄な議論だと思われる。
    • 誘導するくらいなら最初から意見をプレゼンする。
  • 他人の意見を裁かない
    • その場で否定、批判、意見しない。意見の多様性を喜ぶ。
  • 代案なき否定はNG
    • 繊細な議題でないなら、代案なき否定はNG。
      否定するのは簡単。改善案考えるのは難しく、訓練が必要。
      案を出し実際に取り組むことに価値がある。PDCAでいうとDoまで実現させる
  • 過去に向かって意見しない
    • 「なぜこうなったのか」 ⇒ 「どうしたら改善するか」
  • 賛同なきチャレンジも必要。
    • みんながやりたくないことこそ、必要なことかもしれない。
  • 採用された意見に全員で取り組む。
    • 選手は全員ベストを尽くす。「監督の指示が悪い」という選手はいない。
  • タスクごとに責任者を立てる
    • 実施担当者ではない。他者に依頼するなどすればいいが、最終的な責任を負う。
  • 最初の一歩をその場(アイデア採用時点)で実施する
    • ラフスケッチ作る、次の会議日程決める
  • 期限でなく実行日を決める。「いつやるか?」

書籍「リーダブルコード」

前々から読もうと思っていた本が会社に届いたので読んでみた。
テスト駆動開発と同じく、手元に置いておきたい本なので結局購入しそう・・・

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)


感想

全体を通して良いコード(≒可読性・保守性が高いコード)の書き方についての本。
普段から「良いコード」を意識している人には目新しい内容はないかも。
(例えば命名ルール、ネストを浅くするには、など)
ただ日頃あまり明文化しない勘・コツの類がコンパクトに文章化されているので、知識の整理や振り返り、人に教える教材としてとても良い。

特にチームで開発する場合、「経験に基づく勘・コツ」は三者三様だし文章化できていない暗黙知の場合が多い。あるべき姿や言葉遣いを統一する拠り所として、全員で一読すると捗りそう。
図や例を多様して200ページという軽さも、サラッと読んで勧められるので良ポイント。

学ぶためにも復習のためにも、ぜひ一度読んで欲しい良書。
良いコード・良い設計のための本ということで、テスト駆動開発も合わせてオススメ。

ymegane88.hatenablog.com


書籍の概要

項目

コンセプト

  • 他人が理解しやすいように書く
  • 他人とは6ヶ月後の自分かもしれない
  • 名前や順番に意味を持たせる
  • 小さいことはいいこと(スコープ、関数の大きさ、記述量)

初めて知った手法・考え方

以下、本書で初めて知った方法や意識したことがなかった方法を抜粋。

命名規則

名前に単位や属性を付加する

  • より明確な単語を選ぶ。
    例曖昧なGetHogeではなく具体的な動作を示すDownloadHoge, FetchHogeとする。
    単語の選択に困ったら類語辞典で近い意味の英単語を探す。
    Qiitaやブログに頻出英単語まとめ記事がたくさんあるので、参照する。
  • size_mb, delay_secsのように単位をつける。
  • plaintext_passwordのように平文であることを明示する。
  • 書式の事例紹介:クラスのメンバ変数の末尾に_をつける(例:size_, name_)

「良いコード」の最も基礎的な部分だけど、要するに「知ってるか知らないか」なのでレベルを上げるには時間と根気と良いコードを読む機会が必要そう。

コメントの書き方

  • 名前付き引数風コメント
    名前付き引数が使えない言語で引数の意味を明示する方法の案。
Connect(timeout = 10, use_encryption = False) # 名前付き引数
Connect(/*timeout =*/10, /*use_encryption =*/false) //名前付き引数風コメント
  • 引数に関するコメントの書き方例
// Connect(timeout, use_encryption)
//          [secs]   [boolean]
con = Connect(10, false)
  • 自分の考えを記録する
    例:// ◯◯という理由で〜〜というロジックを採用した
  • 定数にコメントをつける
    なぜその固定値を選んだか根拠を記す。
  • 挙動を厳密かつ完結に記す
    △://ファイルの行数を数える
      ・・・空ファイルは?hello\n\r cruel\n world\rは何行?
    ◯://ファイルに含まれる'\n'を数える
  • 入力のコーナーケースの実例を記す

上記のようなコメントを規約としてある程度強制すると、命名や設計を明確・簡潔に作るよう促せそう。
簡潔・明快な日本語を書く能力=国語力なので、命名と同じく結局は教養的なものが求められるので 一朝一夕では鍛えられない。根気よくコードレビューしながら育てるしかない?

制御フロー・ロジックの単純化

  • ネストを浅くする
  • ループや関数を早く抜ける(if, elseを浅くする)
  • スコープは極力小さくする
  • 三項演算子、Do Whileなどパッと見で分かりづらい処理は必要性を考える

変数やコメントの章と比べると技法的な話は少なめで、一般的なお作法という感じ。
読み手に対してなにかを解釈したり覚えたりという負荷をかけないこと。

リファクタリング

  • 関数を小さく分割して汎用化する
    • 複数の機能を個別機能に分割する
    • 上位の機能を支える下位の機能を抽出して分割する

こちらも一般的なお話。
小さく分割されていて、かつ処理フローがシンプルだと読みやすい上にテストしやすい。

その他

  • 意図が分かるテストを書く
    ソースコードと同様に意味のある名前をつけ、意図が分かる値をテストする。
  • 標準ライブラリを知る
    標準ライブラリを知っておけば余計なコードやテスト、ドキュメントを書かずに済む.

まとめ

上記以外のことや上記の詳細も書かれているが、基本的には下2つが基本軸。

  • いい名前を付けること
  • 短いコードを書くこと

これらを実現するためのアイデア・ベストプラクティス集という感じ。
本書をすんなり理解できるチームなら本書を参考に開発チーム全体で「良いコード」の認識を共有すると捗りそう。

あるいはこれから学ぶという人も、最初のチュートリアルや簡単なモノづくりが一段落したら早い内に読んで、その後本書の内容を意識しながらコーディングすると良いコードを書く「良い習慣」が身につくと思う。

最初に書いたとおり、サラッと読めるのに学びと再発見の多い良い本でした。

書籍「テスト駆動開発」

古風な環境で働いているため、テストコードを書くという文化が未だにない。
今更ながらテスト駆動開発について学ぶべく、コレを読みました。

テスト駆動開発

テスト駆動開発

書籍の概要

テスト駆動開発」の本だが本書中で書かれているように、品質保証のソフトウェアテストではなく、設計・リファクタリングを正しく導くことが目的。
1部、2部の内容はテストコードの追加、機能実装、リファクタリングの繰り返しで進む。オブジェクト志向、デザパタなど駆使して「キレイな動作するコード」を作っていくので、それらの実践練習としてとても良い。オブジェクト志向やデザパタについて細かく解説されているわけではないが、他の書籍などで学んだ状態から理解度を深めるのに役立つと思う。

あまり書籍の詳細を書くのも良くないと思うので、以下はキーワードと簡単な解説の羅列、所感など。


テスト駆動について

レッド・グリーン・リファクタリング

テスト駆動開発の基本サイクル。
期待する挙動のテストを書き(テストファースト)、動く実装をして、リファクタリングする。
テスト、実装、リファクタリングを常に1歩ずつ進めるべし。
複数機能をまとめて実装しない。まとめてリファクタリングしない。書いたらテストを走らせる。
というのが原則だが、本書中でもステップの小ささは人や状況に寄るので加減してね、とのこと。
確認するまでもないような実装は、そのまま進めればいい。不安があればテストする。

f:id:ymegane88:20190414222746p:plain

テスト駆動を実現するために

仮実装と明白な実装

  • 仮実装:べた書きの値を使ってとにかく動く実装をする
  • 明白な実装:頭の中の実装をコードに落とす

テストを通る実装をする際、明確な答えがすぐ浮かぶならそれで良い(明白な実装)。
そうでないなら、とにかく動く実装(仮実装)をして、リファクタリングする。

三角測量

リファクタリング時に一般化方法が浮かばない場合、2つ以上のテストを書いて一般化のヒントとする。
(例:2つのテスト($5 == $5$5 != $6)からequalsを一般化する方法を考える)

テストケースの書き方

  • 「こうなったらいい」と思うケースを書き、一歩で動かせるところまで後退させる
  • 一般化は末端の実装から始め、テストケースまで到達させる
  • 「何をテストしたいか」後の読み手に分かりやすいケースを書く
  • アサートファースト:最終的に期待することを最初に書く
  • 明示的なテストコード:文脈が分かるように書く
    例として、100円の商品の税込み価格を確認するなら
    △:assertEquals(108, amount)
    ◯:assertEquals(100 * 1.08, amount)

実装の進め方

  • 実装に詰まったらテストが通った時点まで戻して再変更、再テスト。
  • 複雑な仕様を実装するために途中段階のための簡単なテストを作る。
    ($5 + 10CHF = $10を実装するために$5 + $5 = $10を実装する等)
  • テストを通す最低限の実装をして、その時点で不要なものはToDoリストに入れる。

TDDの普及のために

誰かがTDDを行っているなら、不具合が少なく設計がシンプルなことがチームに伝わるはず。
TDDを強制するべきではない(品質保証の仕組ではなく、個人の設計ツールなので強制力はないはず)
要するにまず自分で実践して、結果で魅力を示すべし。

途中からTDDに乗り換えるには

テストを考えずに作られたコードは、そもそも処理が独立しておらずテストが書けない。
テストなきリファクタリングは失敗を招く。テストとリファクタリングデッドロック。鶏と卵問題。

変えなくていいなら変えない。お金を生まないならコストを掛けるべきではない。
必要な時・箇所に対して、ペアプロや機能単位でのテストを行いながら慎重にリファクタリングする。
それを繰り返す内に、頻繁に変更が発生する箇所はTDDに近づいていくはず。

デザインパターン

デザパタは、様々な問題に対してプログラマが共有している一般的な解法、としている。
頻出問題の解き方のパターンを全員が知っていれば、誰が解いても同じ解答になるよね、ということ。
本書ではデザパタを網羅的に説明しているわけではないので本記事では割愛。

まとめと所感

上記の他にもリファクタリングのノウハウや考え方がサンプルや第1部、2部に沿って解説されている。
テスト駆動開発のノウハウや考え方の他に、本書と付録を通して、

  • テスト駆動開発の「テスト」は品質保証のためのソフトウェアテストではない。
  • 設計を導く手段であり、言い換えるならBehavior駆動開発(BDD)
  • BDDに基づいて作られたフレームワークRSpec, Cucumberなど(用語の置換等)
  • Mockなどテストの道具も全体的に当初の意味・用途から外れて用いられている
  • TDDは組織としてのソフトウェアテストの仕組ではなく、個人のコーディングを導くツール
    明日からでも導入できる(環境を制約されていなければ・・・)

など、「テスト駆動開発という開発ツールの(当初の)使い所」が繰り返し書かれている。
特に付録の訳者解説ではテスト駆動開発の起こりから現在までの変遷がまとめられていて自分や周りの人が「テスト駆動やろう」と言い出したときに、何を目的とするかか考えるキッカケになりそう。

テスト駆動開発自体は、総じて求められるレベルが高い。
個人ツールとして導入するのは良いが、チームで取り組む場合はレベルの高い現場でないと厳しい。
ただ、TDD導入までいかなくとも、第1部の写経だけでもすると全体のレベルが上がりそう。
「テストできる実装」を意識するようになると、実装も設計もだいぶ質が上がると思う。

TDDの理論だけでなくリファクタリングの練習問題集としてとても良い。
TDDへの興味の有無に関わらず、オブジェクト指向言語で開発しているのであれば読んで損はない良書。
会社の本を借りて読んでたけど、手元に欲しいので買いましたw