リファクタリング

refactoring

リファクタリング

機能そのものの振る舞いは変化させず、ソースコードを読みやすく拡張しやすい形に整理する

メリット

  • ソースコードを読みやすくなる
  • 機能追加しやすくなる
  • バグをみつけやすくする

不吉な匂い

ソースコードに以下のような特徴が見つかったら、リファクタリングをする余地がある

  • if文,switch文が多い
  • 同じような構文が登場する
  • 双方向リンクがある
  • クラス・メソッドが長い
  • 意味のわからない変数がある
  • 一時変数が邪魔
  • 引数が多い
  • if文内で正常系がわかりにくい

メソッドの抽出

長すぎるメソッドは汎用的な機能を抽出して、再利用性を高める

リファクタリング前

public void printLog(){
  System.out.println("----------------------------");
  System.out.println("ぶとうかのこうげき!");
  System.out.println("スライムに16のダメージ!");
  System.out.println("----------------------------");
  System.out.println("ゆうしゃのこうげき!");
  System.out.println("スライムに32のダメージ!");
  System.out.println("----------------------------");
  System.out.println("そうりょはバギクロスをとなえた!");
  System.out.println("ドラキーAに64のダメージ!");
  System.out.println("ドラキーBに64のダメージ!");
  System.out.println("ドラキーCに64のダメージ!");
  System.out.println("----------------------------");
}

リファクタリング後

public void printLog(){
  printLine();
  System.out.println("ぶとうかのこうげき!");
  printDamage("スライム", 16);
  printLine();
  System.out.println("ゆうしゃのこうげき!");
  printDamage("スライム", 32);
  printLine();
  System.out.println("そうりょはバギクロスをとなえた!");
  printDamage("ドラキーA", 64);
  printDamage("ドラキーB", 64);
  printDamage("ドラキーC", 64);
  printLine();
}
public void printLine(){
  System.out.println("----------------------------");
}
public void printDamage(String name, int damage){
  System.out.printf("%sに%dのダメージ!\n", name, dmage);
}

クラスの抽出

長すぎるクラスは役割を分担して、クラスの機能を明確にする

リファクタリング前

public class Player{
  public void readBook(){}
  public void writeBook(){}
  public void buyItem(){}
  public void saleItem(){}
}

リファクタリング後

public class Player{
  public void book(){}
}
public class Book{
  public void read(){}
  public void write(){}
}
public class Item{
  public void buy(){}
  public void sale(){}
}

引数オブジェクトの導入

複数にわたる引数は順番などが複雑になるため、頻繁に一緒に受け渡しされる値はオブジェクトにまとめる

リファクタリング前

public calc(int x1,int  y1,int w1,int h1, int x2,int  y2,int w2,int h2){
  
}

リファクタリング後

public calc(Rectangle rect1, Rectangle rect2){
  
}

マジックナンバーの削除

プログラミングで使われる意味のない分類のためだけの数値を、マジックナンバーと呼ぶ。

ソースコード中に含まれる数値は単体では意味がわかりにくいため、シンボリック定数にして、意味がわかりやすいようにする

リファクタリング前

area = r * r * 3.14;
total = price * 1.05;

リファクタリング後

static final float PI = 3.14;
static final float SHOUHI_ZEI = 1.05;

area = r * r * PI;
total = price * SHOUHI_ZEI;

制御構文中のフラグを削除する

for文,if文で生まれたフラグをつくると、後にフラグを変化させない場合にわかりにくい

breakやreturnでループを抜けることで、その後の記述を読まずとも理解しやすくなる

リファクタリング前

public boolean isFlag(){
  booblean flag = false;
  for(){
    if(j == 5){
      flag = true;
    } else {

    }
  }
  return flag;
}
return flag;

リファクタリング後

public boolean isFlag(){
  for(){
    if(j == 5){
      return true;
    } else {

    }
  }
  return false;
}

タイプコードの置き換え

リファクタリング後

enum ItemType{
  BOOK, DVD, SOFTWARE;
}

class Purchase{
  private final ItemType _itemType;
  private final String _itemName;

  public Purchase (ItemType itemType, String itemName){
    _itemType = itemType;
    _itemName = itemName;
  }

  public String buyArticle{
    return _itemType+"の"+_itemName+"を買った\n";
  }
}

State/Strategy

public class Logger {
    private enum State {
        STOPPED {
            @Override public void start() {
                System.out.println("** START LOGGING **");
            }
            @Override public void stop() {
                /* Do nothing */
            }
            @Override public void log(String info) {
                System.out.println("Ignoring: " + info);
            }
        },

        LOGGING {
            @Override public void start() {
                /* Do nothing */
            }
            @Override public void stop() {
                System.out.println("** STOP LOGGING **");
            }
            @Override public void log(String info) {
                System.out.println("Logging: " + info);
            }
        };

        public abstract void start();
        public abstract void stop();
        public abstract void log(String info);
    }

    private State _state;
    public Logger() {
        setState(State.STOPPED);
    }
    public void setState(State state) {
        _state = state;
    }
    public void start() {
        _state.start();
        setState(State.LOGGING);
    }
    public void stop() {
        _state.stop();
        setState(State.STOPPED);
    }
    public void log(String info) {
        _state.log(info);
    }
}

public class Main {
    public static void main(String[] args) {
        Logger logger = new Logger();
        logger.log("information #1");

        logger.start();
        logger.log("information #2");

        logger.start();
        logger.log("information #3");

        logger.stop();
        logger.log("information #4");

        logger.stop();
        logger.log("information #5");
    }
}

アサーション

「この時点でこの変数はこうなっているはず」という前提を記述することで、自動チェックを行い、バグを発見しやすくする

アサーションの実行

java -ea Main

リファクタリング後

sq = w * w;
assert isAbsolute(sq);

private isAbsolute(int sq){
  if(sq >= 0){
    return true;
  }
  return false;
}