複雑さと戦う : 手続きに注目したリファクタリング

ドメイン(問題領域)の知識が増えてくるとソフトウェアが複雑になる。
ビジネスルールを記述するため、if 文が増え、区分コードや状態フラグが増え、ひとつのメソッドに延々とコードがならぶ。 

・メソッド内でインデント(段付け)が必要になる
・3行以上、空白行や {} の区切りがないコードが続く
・ && や || を使う

私のルールでは全部アンチパターンです。

void method()
{
input();
doSomething();
output();
}

このくらいがメソッドとしてちょうど良い感じ。これ以上複雑なメソッドは、リファクタリング(設計の改良)の候補です。

ファウラーの「リファクタリング」で、複雑になりはじめたコードを改良するパターンをいろいろ説明している。

メッソッドの抽出

コードをひとまとめにして、別のメソッドとして抽出する。
そのひとかたまりに名前(メソッド名)をつける。

データが並んだときと同じで、コードがならんだら、意味のある区切りに空白行を挿入する。 そのかたまりを別メソッドにする。

本の例では、2行の System.out.println() 文を まとめて printDetails() メソッドに抽出しています。

条件記述の分解

(リファクタリング前)

if( date.before(SUMMER_START) || date.after(SUMMER_END) )
{
charge = qauntity * _winterRate + _winterServiceCharge ;
}
else
{
charge = quantity * _summerRate ;
}

リファクタリング後

if( isSummer( date ) )
charge = summerCharge( quantity ) ;
else
charge = winterCharge( quantity ) ;

boolean isSummer( Date date ) { ; }
dobule summerCharge( int quantity ) { ; }
dobule winterCharge( int quantity ) { ; }

というように、評価式、計算式をそれぞれ別メッソドとして抽出する。 コードの意味がわかりやすくなったでしょう。
適切なメソッド名を使えば、コメント文での説明も不要。

ガード節で if-else の入れ子を置き換える

if( 条件A )
else
{
if( 条件B )
else
{
if( 条件C )
else
{
return normalResult ;
}
}
}
というようなコードを、

if( 条件A ) return getA() ;
if( 条件B ) return getB() ;
if( 条件C ) return getC() ;

return normalResult ;

というように return 文を使って、 else 文を無くす。
複雑な条件文が見違えるほど、わかりやすくなりますね。

メソッド名の変更

getinvcdtlmt()

getInvoiceableCreditLimit()

ロジックをたくさんの小さなメソッドに分割した時、メソッドの名前をわかりやすくすることが大切。
わかりやすいメソッド名はそれだけで、メソッドの内容を説明する。コメントの補足は不要です。

問合せと更新の分離

Object getSomethingAndSetAnotherThing()

Object getSomething();
void setSomething();

get と set を同時に行っているメソッドを見つけたら、とにかく分解しましょう。

改良した設計のコードはこんな感じになる。
・どのメソッドも短い (3行くらい)
・メソッドの名前がわかりやすい (名前は長め)
・if 文のネストが消える
・else 文が消える

あと、コレクションフレームワークやアプリケーションフレームワークを使えば、for 文も激減します。

for 文や if 文を駆使するより、 もっとシンプルに書きましょう。
そのほうが、読みやすく、安心して変更できるソフトウェアになりますね。

複雑さと戦う : ちいさなちいさなリファクタリング

わたしは、一番小さなリファクタリングは、空白行の挿入だと思う。

String firstName ;
String lastName ;
int customerID ;
String telephoneNumber ;
String mobilePhone ;
String faxNumber ;
String eMailAddress ;

というコードの断片があったら、

String firstName ;
String lastName ;

int customerID ;

String telephoneNumber ;
String mobilePhone ;
String faxNumber ;

String eMailAddress ;

という感じで、空白行を三ついれる。

フィールドを7つひとかまたりに並べたコードを、空白行を使って四つのグループに分けている。ほんのちょっとした作業だけど、設計が改善された。

分けたことで、関連が強いフィールドごとのグループになった、意味が分かりやすくなった。 フィールドの追加や変更も、改善後のほうがやりやすい。

まず、ずらっとならんだフィールドは意味を考えると、いくつかのグループに分けたほうが分かりやすいという感覚。これがリファクタリングの第一歩だと思う。

ここで、firstName と lastName のグループに「名前」を付ける。 例えば PersonName 。
「リファクタリング」の「クラスの抽出」ですね。 

かたまりを空白行でグループに分割する。
それぞれのグループに名前をつける。

これだけのことだけど、ソースコード(設計)が明らかに改善された。

人の名前は姓+名である、という誰でも知っている知識を、ようやくソフトウェアも持つことができた。
表示の時、姓と名の間に空白を入れる、なんていう知識も追加してあげると、ちょっと洗練されるかな。

複雑さと戦う : 基本データ型 vs 小さなオブジェクト

マーチンファウラーは「リファクタリング」の中で、

・基本データ型をそのまま操作する
・小さなオブジェクトにカプセル化する

の感覚の違いを書いている。 

たとえば、基本データ型で

Date start ;
Date end ;
getDuration( start, end )

と書くか、
「期間」をあらわすオブジェクトを作るか

Range range = new Range( start, end ) ;
range.getDuration();

という違い。

あるいは、String 型で

String telephoneNumber = "03-1234-5678" ;
String mailAddress = "name@domain.co.jp" ;

と書くか

それぞれをドメイン(問題領域)のオブジェクトとして

TelephoneNumber telephone = new TelephoneNumber( "03-1234-5678" );
MailAddress address = new MailAddresss( "name@domain.co.jp" ) ;

と書くかの違い。

・基本データ型の操作が分かりやすい
・小さなオブジェクトにカプセル化すると扱いやすい

あなたの感覚はどちら?

前者の感覚の人は、たぶん「リファクタリング」は読んでもぴんとこない。
後者の感覚の人は、すでに「リファクタリング」は実践済みかな?

複雑さと戦う : データに注目したリファクタリング

ドメインの知識・ノウハウをソフトウェアに盛り込み始めると、ソフトウェアは複雑になってきて、ソースコードが破綻しそうになる。

破綻する前に、日々設計を改良しようというのがリファクタリング。

リファクタリングにもいろいろありますが、まずデータに注目したリファクタリングの例。

ドメインの知識が増えてくると、扱うべき情報・状態・区分が膨らんでくる。
ソースコード中に、String , int , Date など基本的なデータ型がずらっと並びはじめたら、さっそくリファクタリングしよう。

適用できる主なリファクタリングのパターン。

クラスの抽出

ひとつのクラスにたくさんの基本データ型が宣言されていたら、ある部分を別のクラスとして定義する。
私の感覚では、5つもデータフィールドが並んでいたら、十分リファクタリングの対象です。

ひとつのクラスで5つのデータフィールドを扱うのは、恐ろしく複雑な状態の組み合わせを扱う必要がある、ということ。

ファウラー本では、 Person クラスから TelephoneNumber クラスを抽出する例で説明している。

オブジェクトによる配列の置き換え

String の配列で、第1要素が氏名で第2要素が住所、というような約束事。
name フィールド と address フィールドを持ったクラスに設計を改良する(リファクタリングする)

ファウラー本では、配列で「チーム名、勝、負」をあらわしているのを、Performance クラスにリファクタリングする例で説明している。

コレクションのカプセル化

コレクションをそのまま返すのではなく、クラスにカプセル化して、コレクションに対する操作を限定する。
コレクションという汎用的なデータ構造ではいろいろなことができるけど、本来は、ある意味を持った操作以外でやる必要がない。
そういう用途が明確なクラス(のインタフェース)を用意することで、コレクションをあちこちからいじりまくる、プログラムの複雑化を防ぐ。

ファウラー本では、講座( Course ) を、Set として返すのでは、 Courses クラスとしてカプセル化したオブジェクトを新規に設計している。

データクラスによるレコードの置き換え

レコード形式(構造体形式)の情報を扱うために、データ項目をそのまま並べたクラスを設計する。データのかたまりを見つけたら、とりあえずクラスにしておきましょうシリーズのその1です。

ファウラー本では例はありませんが、意図は明確。 このようなデータだけのオブジェクトが、次のリファクタリングの足がかりになる点がポイント。

引数オブジェクトの導入

複数の引数が一組で使われることを発見したら、クラスにする。
データのかたまりを見つけたら、とりあえずクラスにしておきましょうシリーズのその2です。

ファウラー本では、引数に ( Date start, Date end ) が繰り返しでてきたら、DateRange クラスを作る例で説明している。

・データのかたまりを見つけたら、とりあえずクラスにまとめることを考える
・基本型がならんだら、クラスとしてカプセル化することを考える

calendar
   1234
567891011
12131415161718
19202122232425
262728293031 
<< March 2017 >>
システム設計日記を検索
プロフィール
リンク
システム開発日記(実装編)
有限会社 システム設計
twitter @masuda220
selected entries
recent comment
recent trackback
categories
archives
others
mobile
qrcode
powered
無料ブログ作成サービス JUGEM