1
リファクタリング
概要
• リファクタリングとは何かを学習します。
• きれいなコードを書くための考え方を学習します。
• 読みやすいコードを書くための具体的な手法を学習します。
• 演習を通してリファクタリングの技術を学習します。
※プログラミング言語はJavaを使用しています。
2
目次
• リファクタリングとは
• きれいなコードを書く~考え方編~
• きれいなコードを書く~実践編~
• リファクタリングの手順と演習
3
リファクタリングとは
4
リファクタリングとは
• リファクタリングとは
• ざっくりいうと、ぐちゃぐちゃな(汚い)コードをきれいに整えるこ
と。
5
そもそもの話
そもそも
現状動いてるシステムに対して
汚いソースコードは修正した方が良い?
6
そもそもの話
どんなに汚いコードでも
動いているコードは修正せずに
そっとしておこう…
というのがというのが一般的に正しいとされていた。
7
そもそもの話
ところが
いや、外から見た動作を変更せずに、
コードを洗練させていこう!!
という考え方出てきた。
8
そもそもの話
それが
「リファクタリング」
という手法
9
きれいなコードを書く
~考え方編~
10
きれいなコードを書く
そもそも
リファクタリングをする・しない以前に、
最初からきれいで読みやすいコードを書けるのであれば、
それに越したことはありません。
まずはきれいなコードを書くための考え方を身につけましょう。
11
きれいなコードを書く
• コードは必ず変更される
• プログラミングのソースコードは一度書いたらもう終わりということ
はほとんどありません。
• 必ず変更されると心得ておきましょう。
• そのためには、変更に強いコードを書くことを意識する必要がありま
す。
12
きれいなコードを書く
• コードは読まれる時間の方が長い
• ソースコードは、一般的に書いている時間よりも読まれる時間の方が
長いです。
• そのため、プログラミングをするときは「書きやすさ」よりも「読み
やすさ」を優先する必要があります。
13
きれいなコードを書く
• KISSの原則
• Keep It Simple, Stupid.または Keep It Short and Simple. の略です。
• 直訳すると「シンプルにしておけ、愚か者よ」。または「簡潔かつ単
純にしておけ」。
• 「将来に備えて」などと考えて本当は必要のない余計なコードを書い
てしまうと、コードはどんどん複雑になっていきます。
• 余計なことはせず、シンプルなコードを保つように心掛けましょう。
14
きれいなコードを書く
• DRY原則
• Don't Repeat Yourself. の略です。
• 直訳すると「繰り返してはいけない。」ということ。
• コードをコピペして重複させてはいけないということです。
• 重複は、バグ修正、機能追加の際のリスクになります。
• 処理だけでなく、定数やリテラルも重複しないように注意しましょう。
• また、コードをそのまま説明しているコメントも重複と同じです。
15
きれいなコードを書く
• 名前重要
• プログラミングは、コードの命名が最重要課題だとも言われています。
• また、プログラミングは名前を付けるところから始まります。
• 変数名、メソッド名、クラス名、などプログラミングでは必ず名前を
付ける必要があります。
• 名前が適切かどうかを常に意識しましょう。
16
きれいなコードを書く
• コメントの書き方
• コメントの目的は、書き手の意図を知らせること。
• 読み手がコードの理解に役に立つことを書く。
• コードからすぐにわかることは、コメントには書かない。
17
きれいなコードを書く
• コメントを多く書かない理由
• コードが修正されたときに、コメントも同時に修正されるとは限らな
い。
• だからコードを読めばわかることや、処理の説明はコメントには書か
ないようにする。
18
きれいなコードを書く
• まとめ
• ソースコードは、読まれる時間が長く、必ず変更されるものと心得る。
• 重複を排除して読みやすいソースコードを書く。そして余計なソース
は記述しない。
• 分かりやすい名前をつける。
• コメントは必要最低限
19
きれいなコードを書く
~実践編~
20
読みやすいコード
• 読みやすいコード
• ここからは読みやすいコードの書き方を紹介していきます。
• コードのサンプルにはJavaを用いてます。
21
メソッドの行数
• メソッドの行数
• 一つのメソッドは何行以内に収めたほうが良いか?
22
メソッドの行数
• メソッドの行数
• Java 1.6 の標準パッケージで4000のメソッドを調べたところ
9割のメソッドは20行以下とこのこと。
• 一概には言えないが、20行というのが一つの目安になる。
23
名前の付け方
• 名前の付け方
• 先にも書きましたが、プログラミングの置いて名前は重要です。
• 名前を見ただけで何を表しているかが分かる名前を付けましょう。
• 言語によって命名規約に慣習がある場合も少なくありません。言語の
慣習に従って名前を付けるようにしましょう。
24
名前の付け方
• 変数名の付け方
• スコープの大きさによって名前の長さを変える。
• スコープの短い変数は、短い変数名でも良い。
• スコープの大きい変数ほど具体的で分かりやすい名前にする。
25
名前の付け方
• 変数名の付け方
• スコープの範囲が大きいとき
• 抽象的な名前よりも具体的な名前を付ける。
• IDEの補完機能によって入力補完ができる場合が多いので、変数
名の長さは気にしなくも問題ありません。
• 汎用的な名前「tmp」「ret」「val」などを使用しない。
26
名前の付け方
• 変数名の付け方2
• 限界値を含めるときは、「max」と「min」を使う。
• 範囲を指定するときは、「first」と「last」を使う。
• 包含/排他的な範囲のときは「begin」「end」を使う。
27
名前の付け方
• フラグの変数の名前
• trueとfalseのどちらかを表す変数のこと(ブール値)。
• 頭に「is」「has」「can」「should」などを付ける。
• trueの時に何を意味するかを表す名前を付けるとよい。
• 単に「flag」という名前は付けないようにする。
28
名前の付け方
• メソッド名
• メソッドの名前は「動詞 + 名詞」にするものが多いです。
• 何をするか(what)が分かる名前にする。
• 例
• getXXX
• setXXX
• isXXX
• addXXX など
29
if文の書き方
• if文の書き方
• 条件式の書き方は人によって様々。読みやすい書き方を意識しましょう。
30
// パターン1
if (a == b) {
// 処理1
} else {
// 処理2
}
// パターン2
if (a != b) {
// 処理2
} else {
// 処理1
}
どっちが読みやすい?
if文の書き方
• if文の書き方
• 変数と固定値を左右どこに書くか
31
// パターン1
if (age >= 20) {
// 処理
}
// パターン2
if (20 <= age) {
// 処理
}
どっちが読みやすい?
if文の書き方
• 三項演算子
• 条件 ? a : b
• 条件が真の時はaが評価され、偽の時はbが評価される。
• 一般的には読みにくいため使用しないことが多いが…
32
// パターン1
sql = "select * from user ";
sql += (!delCheck) ? "where del_flg = 0 " : "";
sql += "order by user_id";
// パターン2
sql = "select * from user ";
if (!delCheck) {
sql += "where del_flg = 0 ";
}
sql += "order by user_id";
どっちが読みやすい?
if文の書き方
• まとめ
• 条件式は否定形より肯定形を書く。
• 調査対象の式(変化する式)は左
• 比較対象の式(あまり変化しない式)は右。
• 三項演算子は、使いどころによってはプログラムがスマートに書ける。
33
コードの並び
• コードの並び
34
// パターン1
details = request.getParameter("details");
location = request.getParameter("location");
phone = request.getParameter("phone");
email = request.getParameter("email");
url = request.getParameter("url");
// パターン2
details = request.getParameter("details");
location = request.getParameter("location");
phone = request.getParameter("phone");
email = request.getParameter("email");
url = request.getParameter("url");
どっちが読みやすい?
SQLの書き方
• SQLを読みやすく書く
• 予約語は大文字、テーブル名やカラム名は小文字、とする書き方が多
い。
• いずれにせよ、予約語とそうでない名前で大文字と小文字を分ける。
• DBMSの方言をなるべく使用しない。
• 結合では「JOIN」のキーワードを使用する。
35
SELECT t1.col1
, t1.col2
, t2.col3
, t2.col4
FROM table1 AS t1
INNER JOIN table2 AS t2
ON t1.col1 = t2.col1
リファクタリング
36
リファクタリングの定義
• リファクタリングの定義
• リファクタリングの定義は「外部から見たプログラムの振る舞いを変
えずに、プログラムの内部構造を改善すること」
• ※バグを修正することはリファクタリングとはならないので注意して
ください。
37
リファクタリングの目的
• リファクタリングの目的
• バグの発見がしやすくなる
• 機能の追加が行いやすくなる
38
リファクタリングの注意点
• リファクタリングの注意点
• リファクタリングと機能追加を同時に行ってはいけない。
• テストが用意されているか確認する。
• できるかぎり頻繁にテストを行う。
• 作業を小さな単位にまとめて、慎重に作業を行う。
39
リファクタリングの対象
• リファクタリングの対象
• 二重化しているコードを発見した(DRY原則に反している)
• 関係ないもの同士が影響し合っている。
• 時代遅れの技術が使われている。
• パフォーマンスが悪い
40
リファクタリングの対象
• 不吉な匂い
「Java言語で学ぶリファクタリング入門」の中で紹介されているもので
す。
• コードが重複している
• メソッドが長すぎる
• クラスの持つフィールドやメソッドが多すぎる
• メソッドの引数が多すぎる
• 仕様変更の修正箇所があちこちに散らばっている
• あるクラスを修正すると、他のクラスも修正しなければならない
• 他のクラスの中身をいじっているクラスがある
• まとめて扱うべきデータが1つのクラスにまとまっていない
• クラスを使わず、int型のような基本データ型ばかり使っている
• switch文やif文を使って振る舞いを分けている
• サブクラスを作ると、別のところにもサブクラスを作らなければなら
ない
41
リファクタリングの対象
• 不吉な匂い
• クラスが大して仕事をしていない
• 拡張するだろうと期待して、クラスが一般化しすぎている
• 一時的にしか使わないフィールドがある
• メソッド呼び出しの連鎖が多すぎる
• 委譲ばかりしていて、自分では仕事をしていないクラスがある
• 必要のない双方向リンクや、IS-A関係の成り立たない継承がある
• クラスのインターフェースが不適切
• 既存のクラスライブラリが使いにくい
• フィールドとアクセッサしかもっていないクラスがある
• 継承しているメソッドなのに、それを呼ぶと問題が起きる
• コードの不備を補うために、詳しいコメントがある
42
リファクタリングの対象
• 不吉な匂い
全部で22個が紹介されています。かなりたくさんありますが、
ざっくりまとめると
• 理解しにくい
• 修正しにくい
• 拡張しにくい
と感じる部分がリファクタリングの対象になります。
43
リファクタリングの手順
• リファクタリングの手順
1. テストを実施してテストを通ることを確認
テストが用意されていない場合は、テストの作成から行う。
テストがないコードに関しては、リファクタリングはするべきではない。
2. コードの修正を実施
3. テストを実施してテストが通ることを確認
4. 2と3の手順を繰り返す。
たくさんのコードを一気に修正しないように注意する。
リファクタリングは「小さな修正 ⇒ テスト」のサイクルを多く繰り返し、
少しずつ実施していく。
44
演習
45
演習
• 演習問題①
• 「Shain.java」のファイルをリファクタリングしてください。
• クラスやメソッドは自由に追加可能です。
• 「ShainTest.java」の内容も修正してかまいません。ただし、
assertEqualsメソッドを実行している行は変更しないようにしてくださ
い。
• Shainクラスの仕様は以下の通りです。
• Shainクラスの仕様
• 社員を表すクラスです。
• フィールドに役職(position)を持ちます。
• フィールドに資格の保有数(shikaku)を持ちます。
• calcSalaryメソッドは、自身の給料を計算します。
46
演習
• calcSaralyメソッドの仕様
• 基本給を20万とします。
• 役職によって手当てがつきます。
• 平社員は手当なし
• 主任はプラス10000
• 係長はプラス30000
• 資格によって手当てがつきます。
• 資格一つにつきプラス5000
47
演習
• 演習問題②
• 機能の追加を行ってください。仕様は以下の通りです。
• 役職に「課長」を追加します。
• 課長は役職手当が50000になります。
• 社員クラスに、残業時間のフィールドを追加します。(int型)
• 給与計算に残業時間を加味します。
• 平社員は、残業時間 × 1000
• 主任は、残業時間 × 1200
• 係長は、残業時間 × 1500
• 課長は残業代は出ない(かわいそう(´・ω・`))
48
解説
演習①
49
演習①
• 改善点
• switchの処理で似たような計算が行われている。共通部分を一か所に
まとめることで、もっと簡潔にできそう。
• 役職(position)のフィールドがint型になっているため、分かりづらい、
かつ、意図しない値がセットされてしまう可能性がある。
50
switch文の改善
• switch文の処理を改善しよう
• switchの処理で似たような計算が行われている。
• 計算の中で異なるのは役職手当の部分だけなので、switchでは役職手
当を求める処理だけをして、計算自体は一つにまとめることで簡潔な
処理になる。
51
switch文の改善
• switch文の処理を改善しよう
• コードは一例です。
52
switch (position) {
case 1: // 平社員
salary = 200000 + shikaku * 5000;
break;
case 2: // 主任
salary = 200000 + 10000 + shikaku * 5000;
break;
case 3: // 係長
salary = 200000 + 30000 + shikaku * 5000;
break;
default:
salary = 0;
}
int teate = 0;
switch (position) {
case 2: // 主任
teate = 10000;
break;
case 3: // 係長
teate = 30000;
break;
}
return 20000 + teate
+ shikaku * 5000;
リファクタリング前 リファクタリング後
役職(position)の改善
• 役職(position)を改善しよう
• 改善策1:定数の導入
• 改善策2:enumの導入
• 改善策3:クラスやインターフェースの導入
• その他
53
役職(position)の改善
• 定数の導入
• position用の定数を用意します。
54
public class Shain {
public static final int HIRA = 1; // 平社員
public static final int SYUNIN = 2; // 主任
public static final int KAKARITYOU = 3; // 係長
…
}
定数を追加
役職(position)の改善
• 定数の導入
• プログラムの中で数字を指定していた個所を定数に変更します。
• これにより、コメントが不要になり、このクラスを使用する際のミスの可能
性を減らすことができます。
55
switch (position) {
case HIRA:
…
break;
…
}
リファクタリング前
switch (position) {
case 1: // 平社員
…
break;
…
}
リファクタリング後
役職(position)の改善
• enumの導入
• enumでPositionを定義します。
• positionの型をintからPosition型に変更します。
56
public class Shain {
private int position;
}
public class Shain {
public enum Position {
HIRA,
SYUNIN,
KAKARITYOU
}
private Position position;
…
}
リファクタリング前 リファクタリング後
役職(position)の改善
• enumの導入
• 型の変更に伴って、コンパイルエラーが発生するはずです。
• アクセッサメソッドや、コンストラクタに記載しているpositionの型を修正し
ていきます。
• caseのラベルもenumを使用するように修正します。
57
public Shain(Position position) {
this.position = position;
}
…
public int calcSalary() {
switch (position) {
case HIRA:
…
break;
…
}
役職(position)の改善
• enumの導入のメリット
• enumを導入することで、Shainクラスを使用する側のミスをなくすことがで
きます。
• 定数を導入した場合、コードは分かりやすくなりますが、フィールドの方が
int型のため、意図しない値をセットされる可能性があります。
• 型をenumにすれば、用意した以外の値を設定できなくなるので、利用者のミ
スを減らすことができます。
58
役職(position)の改善
• クラスやインターフェースの導入
• 今後、役職による振る舞いの違いが増える可能性がある。
• 今後、役職の種類が増える可能性がある。
• このような可能性を考慮すると、インターフェースやクラスを導入して役職
を表すほうが柔軟に対応できます。
59
役職(position)の改善
• クラスやインターフェースの導入
• クラス図イメージはこんな感じ(分かりやすく日本語にしてます。)
60
役職(position)の改善
• クラスやインターフェースの導入
• インターフェースでは、役職手当を取得する抽象メソッドを定義します。
• 実際の役職のクラスで役職手当の取得メソッドを実装します。
61
public class Syunin implements Position{
@Override
public int getTeate() {
return 10000;
}
}
インターフェース
public interface Position {
int getTeate();
}
実装クラス
役職(position)の改善
• クラスやインターフェースの導入
• 社員クラスでは、役職をPosition型に変更します。
• 給与計算のメソッドでは、役職手当をPositionのメソッドで取得できるため、
swtch文をなくすことができます。
62
public class Shain{
private Position position; // 役職
…
// 給与計算メソッド
public int calcSalary() {
return 200000 + position.getTeate() + shikaku * 5000;
}
…
}
社員クラス
役職(position)の改善
• クラスやインターフェースを導入する利点
• 役職をインターフェースにして、社員クラスと分けておくことで、
後々、役職の種類が増えたとしても、社員クラスは修正する必要がありませ
ん。
63
役職(position)の改善
• その他
• 解答はあくまで一例です。
• 他にも、分かりやすく、かつ変更に強いコードにする書き方がないかを探し
てみてください。
64
解説
演習②
65
演習②
• 演習①をクラスやインターフェースを導入したリファクタリングを
実施している場合は、機能拡張は容易にできます。
• 社員クラスのフィールドに残業時間を追加。
• 残業時間を設定するコンストラクタやアクセッサメソッドを定義。
• 役職インターフェースを実装した課長クラスを用意する。
• 役職インターフェースに残業手当を計算する抽象メソッドを定義する。
• 役職を実装したクラスで残業手当のメソッドを実装する。
• 社員クラスの給与計算メソッドで、残業手当の取得の処理を追加。
66
まとめ
67
全体のまとめ
• リファクタリングは必ずしも実施したほうが良いわけではありませ
ん。状況に応じて適切な判断を下しましょう。
• リファクタリングの必要がない、読みやすい、かつ拡張しやすい
コードを書けるように心がけましょう。
• 時間と心に余裕があればリファクタリング積極的にリファクタリン
グしていきましょう。
• リファクタリングは繰り返しテストしながら小さく進めましょう。
68
参考文献
• リーダブルコード
• Java言語で学ぶリファクタリング入門
• 達人プログラマー
• きれいなコードを書くための鉄則
69

2018年度 若手技術者向け講座 リファクタリング