[Java Refactoring] 매직 넘버를 기호 상수로 치환

728x90
반응형
SMALL

자바로 배우는 리팩토링 입문

  • Java Refactoring For Beginner
  • 건강한 코드로 소프트웨어 체질을 개선하자!
  • 유키 히로시 지금
  • 길벗 출판사
  • 2017.10.31

 

리팩토링을 위한 스터디 내용을 정리하자.

 

 

 

 

 


 

매직 넘버를 기호 상수로 치환

소스 코드에 특정한 숫자(매직 넘버)를 사용하는 것은 좋지 않은 코딩 스타일이다.

 

이유

  • 의미를 파악하기 어렵다.
    • 소스 코드에 100이라고 적혀 있으면 무엇을 의미하는지 바로 파악하기 어렵다.
    • 대신 MAX_INPUT_LENGTH라는 기호 상수를 사용하면 의미를 바로 파악할 수 있다.

 

  • 수정하기 어렵다.
    • 요구사항이 변경되어 최대 입력 문자 길이가 200으로 수정되는 경우, 100이라는 숫자가 이곳저곳에 박혀 있어 수정이 어렵다.
    • 모든 100을 200으로 수정할 수도 없다.

Before

 
public class Robot {
    private final String _name;
    
    public Robot(String name) {
        _name = name;
    }
 
    public void order(int command) {
        if (command == 0) {
            sout(_name + "walk");
        } else if (command == 1) {
            sout(_name + "stop");
        } else if (command == 2) {
            sout(_name + "jump");
        } else {
            sout("error command : " + command);
        }
    }
}
 
public class Main {
    public static void main(String[] args) {
        Robot robot = new Robot("Andrew");
        robot.order(0); // walk
        robot.order(1); // stop
        robot.order(2); // jump
    }
}

 

After

 
public class Robot {
    public static final int COMMAND_WALK = 0;
    public static final int COMMNAD_STOP = 1;
    public static final int COMMAND_JIUMP = 2;
 
    private final String _name;
    
    public Robot(String name) {
        _name = name;
 
    }
 
    public void order(int command) {
        if (command == COMMAND_WALK) {
            sout(_name + "walk");
        } else if (command == COMMAND_STOP) {
            sout(_name + "stop");
        } else if (command == COMMAND_JUMP) {
            sout(_name + "jump");
 
        } else {
            sout("error command : " + command);
        }
    }
 
}
 

public class Main {
    public static void main(String[] args) {
        Robot robot = new Robot("Andrew");
 
        robot.order(Robot.COMMAND_WALK);
        robot.order(Robot.COMMAND_STOP);
        robot.order(Robot.COMMAND_JUMP);
    }
} 

 

추가 리팩토링

  • COMMAND_WALK, COMMAND_STOP처럼 매직 넘버를 기호 상수로 만든다고 해도 실제로는 0, 1이라는 int 값이다.
  • 그렇기에, 프로그래머가 robot.order(0); 매직 넘버를 직접 적어도 컴파일 에러가 발생하지 않는다.
  • 프로그래머의 실수를 컴파일러가 검출할 수 있게 분류 코드를 클래스로 치환하는 것이 좋다.
  • 여기서는 int가 아닌 RobotCommand 타입으로 치환했다.
 
public class RobotCommnad {
    private final String _name;
    
    public RobotCommand(String name) {
        _name = name;
    }
 
    public String toString() {
        return "[RobotCommand: " + _name + "]";
    }
}
 
public class Robot {
    public static final int COMMAND_WALK = new RobotCommand("WALK");
    public static final int COMMNAD_STOP = new RobotCommand("STOP");
    public static final int COMMAND_JIUMP = new RobotCommand("JUMP");
 
    private final String _name;
 
 	public Robot(String name) {
        _name = name;
 
    }
 
    public void order(int command) {
        if (command == COMMAND_WALK) {
            sout(_name + "walk");
        } else if (command == COMMAND_STOP) {
            sout(_name + "stop");
        } else if (command == COMMAND_JUMP) {
            sout(_name + "jump");
 
        } else {
            sout("error command : " + command);
        }
    }
 
}
 
public class Main {
    public static void main(String[] args) {
        Robot robot = new Robot("Andrew");
 
        robot.order(Robot.COMMAND_WALK);
        robot.order(Robot.COMMAND_STOP);
        robot.order(Robot.COMMAND_JUMP);
    }
}

 

추가 리팩토링

  • Java 5부터 ENUM으로 기호 상수를 표현할 수 있게 되었다.
  • WALK, STOP, JUMP 기호 상수를 ENUM으로 선언하여 리팩토링을 진행한다.
  • 일반적으로는 별도의 ENUM 클래스를 생성해서 공통으로 사용한다.
 
public class RobotCommnad {
    private final String _name;
    
    public RobotCommand(String name) {
        _name = name;
    }
 
    public String toString() {
        return "[RobotCommand: " + _name + "]";
    }
}
 
public class Robot {
    
    public enum Commnad {
    	WALK,
        STOP,
        JUMP
    }
 
    private final String _name;
 
 	public Robot(String name) {
        _name = name;
 
    }
 
    public void order(int command) {
        if (command == COMMAND_WALK) {
            sout(_name + "walk");
        } else if (command == COMMAND_STOP) {
            sout(_name + "stop");
        } else if (command == COMMAND_JUMP) {
            sout(_name + "jump");
 
        } else {
            sout("error command : " + command);
        }
    }
 
}
 
public class Main {
    public static void main(String[] args) {
        Robot robot = new Robot("Andrew");
 
        robot.order(Robot.COMMAND_WALK);
        robot.order(Robot.COMMAND_STOP);
        robot.order(Robot.COMMAND_JUMP);
    }
}

 

기호 상수가 적합하지 않은 경우

// 나쁨 
for (int i = 0; i < BUFFER_SIZE; i++) {
	...
}

// 좋음
for (int i = 0; i < buffer.length; i++) {
	...
}

배열에는 배열 길이를 나타내는 length 필드가 존재하기에 굳이 기호 상수를 사용할 필요가 없다.

 

괜히 기호 상수로 치환했다가 올바른 배열 길이가 아닌 값이 들어가는 휴먼 에러가 발생할 수 있다.

 

연습 문제

1-1. O, X

  • 매직 넘버를 코드에 직접 쓰는 것보다 의미를 알기 쉬운 이름을 붙인 기호 상수를 사용하는 게 낫다. (O)
    • 매직 넘버보다 기호 상수가 의미를 알기 쉽다.

 

  • 사양이 변할 때 한꺼번에 치환할 수 있도록 매직 넘버를 사용하는 게 좋다. (X)
    • 매직 넘버인 경우, 의도치 않은 값까지 함께 수정될 위험이 있어서 사용하지 않는 게 좋다.

 

  • 매직 넘버 사용은 피해야 하므로 배열 인덱스 최솟값 0을 MIN_INDEX라고 선언하는 게 좋다. (X)
    • 배열 인덱스 최솟값이 0이라는 건 자바 언어 규칙으로 정해져 있으므로 일부러 기호 상수를 선언하는 건 의미가 없다.

 

1-2. 다음 코드의 문제점을 지적하고 개선하시오.

public static double degreeToRadian(double degree) {
    return degree / 180.0 * 3.1459265358979323846;
}

3.14592~는 원주율 매직 넘버이다. 매직 넘버를 Math 클래스의 상수 PI로 치환하자.

public static double degreeToRadian(double degree) {
    return degree / 180.0 * Math.PI;
}

아직 180.0이라는 매직 넘버가 남아있다. 이것도 치환이 필요하다.

 

하지만, 원주율을 뜻 하는 표준 클래스 라이브러리가 있기에 Math.toRadians() 메서드를 쓰는 것도 방법이다.

public static double degreeToRadian(double degree) {
    return Math.toRadians(degree);
}

 

728x90
반응형
LIST