DevLove仙台 リーンな開発

リーンなコードを書こう
2013年11月30日
有限会社システム設計 増田
アジェンダ
1.リーンな開発はリーンなコードから
2.コードをリーンにする
・コードの改善の準備
・コードの重複をなくす
・変更の副作用をなくす
・for文を扱いやすくする
・if文/switch文を部品に分解する
リーンな開発は
リーンなコードから
「リーン開発の現場」まえがき

継続的な改善
これが
リーンな開発の
すべてだ
開発のやり方の改善で
大きな成果をだすためには
コードの変更コストを下げる
変更コストが下がれば
やり方の改善効果は倍増する
リーン
すっきりした
ムダのないコードは
変更がやりやすい
変更がやりやすいと
改善スピードが加速する
メタボ
体脂肪率 X%
どろどろ血
こんなコードは
変更がたいへん
変更がたいへんなコード
•
•
•
•
•
•
•
•

どこで何をやっているかわからない
だらだら長い
コピペの臭い
変数名が微妙
コメントが意味不明
なおしたはずが、なおっていない
ちょっとした変更が、あちこちに飛び火
結局、全部やりなおし
コードの改善
コードを改善する機会
• 変更が必要になった時
– ビジネスルールの変更
– 機能の追加

• 変更がやりやすいように、コードを改善してから
変更する
• 小さな変更依頼が良いチャンス
– 変更の目的が明確
– なぜその変更がたいへんか、原因がわかりやすい
– 改善の before/after を確認しやすい

• 変更するたびに、小さな改善を累積していく
• 大きなブレークスルーが起きる
まずはコードの整理
• 今回の変更に関係するコードとそうでない
コードを分ける
– 機能の変更はしない
– コードを分ける基本手段はメソッド

• 今回の変更の対象となるデータとその操作だ
けを集めた部品(メソッド)を作る
– 「メソッドの抽出」リファクタリング
– IDE を使いこなす
– 「リファクタリング」で復習する

• 関係するクラス、すべてでこれを繰り返す
– 現在の機能が動いているか確認
メソッド抽出の候補
• 空行で区切られた数行のかたまり
• コメントで書かれた見出し
– ブロックの切れ目のはず

•
•
•
•

同じインデントレベルのコードのかたまり
if()内の条件式
if/switch で分岐した、それぞれのブロック
for文のボディ
簡単な例
//変更前
void usage( String name, int value)
{
System.out.println( “利用回数” );
System.out.println( new Date() );

}

//詳細表示
System.out.println( “氏名:” + name );
System.out.println( “回数” + value );

//変更後
void usage( String name, int value)
{
System.out.println( “利用回数” );
System.out.println( new Date() );
}

detail(name,value);

void detail( String name, int value )
{
System.out.println( “氏名:” + name );
System.out.println( “回数” + value );
}
メソッド分割の補足
• 分割しすぎると一連の流れが追いにくい
– Yes
– 一連の流れ(=手続き型プログラミング)だから、変更が大
変になっている
– ばらばらの部品に分けて、変更がやりやすいように組み立
てなおす
– 変更の視点で組み立てなおすと、コードがわかりやすくな
る味を覚えましょう

• 対象が計算式1行だけでも、メソッドにするの?
– Yes
– 他のコードと分離することが大切

• メソッドチェイン
– 1ステップずつ分割したほうが、整理がしやすい
メソッドで使う変数に注目
• 抽出したメソッドで参照・変更・生成する
データを徹底的に調べる
– どこで宣言されているか
– どこで初期化されているか
– どこで上書きされているか

• 可能な限りメソッドのローカル変数にする
– メソッド内で宣言・初期化する

• 引数が多ければ、一つのクラスにまとめる
– 最初はデータの入れものクラスで良い
クラスを作る
• 変更に関連するデータとコードを集めるため
の入れものとしてクラスを宣言する
– クラス名は、今回の仕様変更を記述した言葉に中
から見つかる
– 複数の引数をまためたクラスは有力候補

• 必要なデータをインスタンス変数で宣言
• データ初期化をコンストラクタで宣言
– 初期化のパターンごとのコンストラクタ

• メソッドをこのクラスに移動してくる
– 引数はなくせるはず
– インスタンス変数の参照だけで処理する
Detail クラス
class Detail
{
private String name;
private int value;
Detail( String name, int value )
{
this.name = name;
this.value = value;
}

}

void print()
{
System.out.println( “氏名:” + name );
System.out.println( “回数” + value );
}
クラスを使うように書き換える
• 移動前のメソッドを呼び出している箇所
を書き換える
– new でオブジェクトを生成し
– (移動した)メソッドを呼び出す

• 現在の機能通りに動いていることを確認
• これで、ようやく機能変更の準備が完了
Detail クラスを使う
//変更前
void usage( String name, int value)
{
System.out.println( “利用回数” );
System.out.println( new Date() );
}

//変更後
void usage( String name, int value)
{
System.out.println( “利用回数” );
System.out.println( new Date() );

detail(name,value);

void detail( String name, int value )
{
System.out.println( “氏名:” + name );
System.out.println( “回数” + value );
}

}

Detail detail = new detail(name,value);
detail.print();

詳細の印刷内容を変更する時、
Detail クラスに変更を局所化できる
特に効果が大きいのは、
・複数個所に重複している場合
・氏名と回数を使う他のロジックが
ある場合
コードの重複をなくす
• 基本のやり方は、Detailクラスの例のよう
に、変更の対象範囲をメソッドに分解し
て、クラスに移動する
• 現実のコードは、これほど単純ではない
– 入り組んだデータの初期化、参照、上書きが、
プログラムのあちこちに散らばっている
実践的なやり方
• フィールド変数、コンストラクタ、getterでクラスを宣言
– クラス名は、おそらく変更仕様書にでてくるキーワード
• 詳細表示形式の変更
• 電話番号の必須条件を解除

• データを設定している箇所を見つけ、このクラスのコンスト
ラクタに置き換える(オブジェクトを生成する)
• データを参照する箇所を見つけ、このクラスのgetter で書き
換える
• get した後のロジックを、このクラスに移動する

– getter が削除できるまで繰り返す
– どうしても削除できなかったら、メソッド名をもっと意味のあ
る名前に変える

重複したロジックがこのクラスに集まってくる
データクラスが、振る舞いを持ったクラスに進化する
クラスの粒度
• 今回の変更に関連するデータは、String ひ
とつだけです
Stringのフィールドが一つだけのクラスを
作るんですか?
• Yes
– 変更単位が、その粒度なんだから、クラスと
して独立させる
– 変更すべきコードを局所に閉じ込める
– 変更の影響箇所は型(クラス名)で特定する
小さなクラスたち
• 基本データ型 ( Integer, String, Date )を1つか2つ
持つ小さなクラスが、変更の対象になることが多
い
• クラス名は、業務要件の基本語彙になる
• 基本語彙クラスがそろってくると、コードは要件
定義書に近づいていく
– 語彙
– 構造

パッケージ

• ドキュメントの改善
– 業務要件をコードに変換するためのプログラム仕様
書は不要になる
– 業務仕様書の重複が、プログラムから発見できる
数値系のクラスの例
•
•
•
•
•

Quantity
Unit
Amount
Money
Currency

数量と単位
単位と換算機能
金額、千円単位表示
金額と通貨
通貨
日付系のクラスの例
•
•
•
•
•
•
•

Days
日数
Hours
時間数
Period
期間(開始日+終了日)
DueDate
予定日
YearAndMonth
年月
DateOfRecord
記録日
DateOfOccurrence 発生日
テキスト系のクラスの例
•
•
•
•
•
•

Telephone/Email/Url, …
Line
一行のテキスト
Description 説明(複数行のテキスト)
Note
メモ(作成日時と作成者)
Definition
見出し語と説明のペア
Definitions 複数の「見出し語+説明」
変更の副作用をなくす
原因はデータの上書き
• じゃあ、データの上書きをやめればよい
– setter は使わない
– データは、コンストラクタでのみ設定可能
– データを変えたい時は、別のオブジェクトを
生成する
– それぞれのオブジェクトを、いつ、どこで参
照しても、同じ内容を保障する
• 処理の順番を入れ替えても副作用が起きない
不変(immutable)オブジェクトが副作用をなくす
原因はデータの複製
• 同じデータをあちこちの変数で保持
こっちも直しても、あっちがなおらない
• じゃあ、複製を持つのやめればいい
–
–
–
–
–
–

データ宣言とその操作を一つのクラスに集める
ひとつの値は、ひとつのオブジェクトが保持
別の値は、別のオブジェクトが保持する
引数やリターン値で「データ」をやりとりしない
オブジェクトを渡す
オブジェクトに仕事をさせる
for 文を扱いやすくする
for 文とコレクション
• バグと副作用の巣窟です
– コレクションのグローバル参照をやめる
– コレクションを渡すことをやめる

• 変更の対象データがコレクション
– そのコレクションだけを持つクラスを宣言
– そのクラスにロジックを集める
– そのクラスはコレクションを返さない
コレクションを特別扱いにする
ファーストクラスコレクション
ファーストクラスコレクション
基本パターンは、単数形クラスと複数形クラスのペア
class Book
{
// 書名、ジャンル、価格、人気
}
class Books
{
List<Book> books;
}

// コレクション操作のメソッド群

class Customer
{
...
}
class Customers
{
List<Customer> customers;
…
}
ファーストクラスコレクション
• プログラムのあちこちに散らばっていたロジックを集
約するだけでも、コードは安定する
• コレクションへの要素の追加や削除、要素の変更専用
のメソッドを用意する
– 外部からできることの限定
– 不整合チェックなどのガードを強化

• ループ処理の記法
– 安全で楽に書ける方向に進化している
– 安全でシンプルに書ければ、ファーストクラスコレクショ
ンはいらなくなる?
– No!
• 楽に書けると、ますます、あちこちにコードが重複する
• 変更コストを下げるには、ファーストクラスコレクション
if文/switch文を
部品に分解する
変更コストは条件分岐との戦い
• スパゲッティコード goto文
– if/switch は、goto文の特殊形態
– if/switch の分岐後のブロックが長くなれば、
goto文使ったスパゲティといっしょ

• 今までのやり方の応用編
– 分岐ごとにメソッドにまとめる
– あちこちで同じ条件分岐したメソッドを集め
るためのクラスを作る
分岐ごとにクラスにわける
…
Amount fee(String guestType)
{
if( guestType.equals( "adult“ ) )
return adultFee();
else
return childFee() ; //子供
}
…
private Amount adultFee()
{
return Yen(100);
}
private Amount childFee()
{
return new Yen(50);
}

class Adult
{
Amount fee()
{
return new Yen(100);
}
}
class Child
{
Amount fee()
{
return new Yen(50);
}
}
別のクラスを同じに扱う
異なるクラスのオブジェクトも、同じ「型」で扱える
interface Guest
{
Amount fee();
}

class Usage
{
Guest guest;
Usage(guest)
{
this.guest = guest;
}

class Adult implements Guest
{
…
}
class Child implements Guest
{
…
}

}

Amount fee()
{
return guest.fee();
}

幼児料金、シニア料金を追加する場合、if/switchより、ずっと安全・簡単
Java の 列挙型ならもっと簡単
enum Guest
{
Adult(100), Child(50), Baby(0),Senior(80);
Amount fee ;
Guest(int fee)
{
this.fee = new Amount(fee);
}

}

Amount fee()
{
return this.fee;
}

Javaの列挙型:
Interface 宣言 +
実装クラス宣言 + singleton
型ごとに、振る舞いを
持てる
区分や種別のコードの整理の
特効薬
状態遷移もシンプルに
enum State
{
opened,closed
EnumMap<State,EnumSet<State>> transitions; // 状態遷移先
EnumMap<State,EnumSet<Event>> events; // 状態ごとの可能イベント
State canTransitTo(State state)
{
Set<State> states = transitions.get(this);
return states.contains(state);
}

}

Set<Event> possibleEvents()
{
return events.get(this);
}

enum Event
{
//イベント種類
open,close
}

状態/イベントの追加、遷移ルールの変更が簡単・安全
パッケージ名
クラス名
メソッド名
名前づけで楽をする
(英語で)
楽をしましょう

問題領域の入門書(たくさんある)
・目次はそのままパッケージ設計
・説明はクラス仕様書(情報名、扱い方)
・索引で影響範囲をチェック

英単語のイメージを共有する
make/create/build/construct …
all/every/each …
for/to/of/with …
大きなコード改善
業務システムの基本構成
画面
アプリケーション

連携
アプリケーション

帳票
アプリケーション

データベース
アプリケーションの中核部品
画面
アプリケーション

連携
アプリケーション

ドメイン
モデル
帳票
アプリケーション

ここに
業務ロジックを
集める

データベース
ドメインモデルをみんなで使う
画面
アプリケーション

連携/バッチ
アプリケーション

氏名

連絡
手段

住所

連絡
履歴

帳票
アプリケーション

業務で使う
データ+ロジックを
ここに集約

データベース
ドメインモデルは
コードの重複を減らす
• あちこちに分散しがちな業務ロジックを
データ中心に整理し、集約する
• いやな臭い
– データを要求する getter メソッド
• そのデータを使うロジックが、別のクラスに分散
• あちこちのクラスに重複して書かれている危険な
兆候
コード量が減れば
•
•
•
•

テストが減る
ドキュメントが減る
バグ調査が楽になる
変更の影響が見通しやすい
ドメインモデル

ほんとうの価値

• 業務の知識の浸透
– ビジネス目標/システムの価値の理解
– 業務の全体像/基本の仕組みの理解
– 業務の現場にある肌感覚、現場の知恵

• 言語表現を超えた暗黙知が育つ
– 業務アプリケーションの設計のコツ、かんどころ、
おとしどころ、…
– 個人に内面化された価値観、方向感
– チームで共有化された価値観、方向感

• 「あたり」がつくようになる
– 的外れの作業/とんでもない勘違い/手戻りが減る
– 進むべき方向がわかりやすくなる
ドメインモデルがリーン開発を加速する
今日のまとめ
コードをリーンにする
実践ノウハウ
変更が必要になったら
• メソッドでコードを整理する
• データに注目してクラスを宣言
そのクラスにロジックを集める
• オブジェクトを不変(immutable)にする
• コレクションは特別扱いする
• 条件分岐はクラスに分ける
型を同じにする
• (Javaなら) 区分・種別は Enumで
• ドメインモデルで全体を大きく改善する
• ブレークスルーのために、小さな改善を続ける
ありがとうございました

リーンなコードを書こう:実践的なオブジェクト指向設計