매직 넘버 치환
- 소스 코드에 특정한 숫자(매직 넘버(magic number))를 직접 작성하는 것은 나쁜 코딩 스타일
나쁜 예
public class MagicNumber {
public static void main(String[] args) {
MagicNumber magicNumber = new MagicNumber();
MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
// 0은 공격
player.action(0);
// 1은 방어
player.action(1);
}
public class Player {
private String name;
public Player() {
}
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void action(int command) {
if (command == 0) {
attack();
} else if (command == 1) {
defence();
}
}
private void attack() {
System.out.println(this.name + " is going to Attack!!");
}
private void defence() {
System.out.println(this.name + " is going to Defence!!");
}
}
}
나쁜이유
- 매직 넘버의 의미를 알기 어렵다
- 0과 1이 무슨 의미인가?
- COMMAND_ATTACK과 같은 기호 상수를 쓰면 의미를 알기 쉽다
- 매직 넘버는 수정하기 어렵다
- 0 또는 1 커맨드 숫자가 변경 될 경우 치환해야 하는데, 만약 많은 곳에서 동일한 커맨드를 쓴다면?
- 손이 많이 가고, 틀리기 쉽고, 빼먹는 경우가 생길 수도 있다.
카탈로그
이름 | 매직 넘버를 기호 상수로 치환 (Replace Magic Number with Symbolic Constant) |
---|---|
상황 | 상수를 사용하고 있음 |
문제 |
• 매직 넘버는 알기 어려움 • 매직 넘버가 여러 곳에 있으면 변경하기 어려움 |
해법 | 매직 넘버를 기호 상수로 치환함 |
결과 |
장점 • 상수의 의미를 알기 쉬워짐 • 기호 상수의 값을 변경하면 상수를 사용하는 모든 곳이 변경됨 단점/주의 • 이해하기 어려운 이름을 사용하면 오해가 생길 수 있음 |
방법 |
|
관련 항목 |
• 분류 코드를 클래스로 치환 • 분류 코드를 상태/전략 패턴으로 치환 |
리팩토링
기호 상수 선언하기
- 기호 상수 선언
- public static final 클래스 필드 사용
- 또는 enum 사용
- 어떤 클래스 안에서만 사용할 기호 상수를 선언할 경우 private 접근지시자를 사용할 수 있다
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;
- 매직 넘버를 기호 상수로 치환
- 0, 1과 같은 매직 넘버를 기호 상수로 치환
// if (command == 0) {
if (command == COMMAND_ATTACK) {
// else if (command == 1) {
} else if (command == COMMAND_DEFENCE) {
// player.action(0)
// player.aciton(1)
player.action(Player.COMMAND_ATTACK);
player.action(Player.COMMAND_DEFENCE);
- 기호 상수에 의존하는 다른 매직 넘버를 기호 상수를 사용한 표현식으로 변환
- 상수 의존 관계는 상수 사이에 의존 관계가 있어 한 상수의 변경이 다른 상수에게도 영향을 미치는 경우를 말한다.
- 이 때, 표현식으로 의존 관계를 표현해야 한다.
// public static final int MAX_INPUT_LENGTH = 100;
// public static final int WOR_ARE_LENGTH = 100 * 2;
public static final int MAX_INPUT_LENGTH = 100;
public static final int WOR_ARE_LENGTH = MAX_INPUT_LENGTH * 2;
테스트
-
모든 기호 상수 치환이 끝나면 컴파일해서 테스트
- 테스트 결과는 리팩토링하기 전과 같아야 한다.
-
가능하다면 기호 상숫값을 변경한 후 컴팡리해서 테스트
- 기호 상수의 값을 다른 값으로 변경한 후 테스트하면 빠드린 곳이 없는지 확인할 수 있다.
- COMMAND_ATTACK 값을 0에서 1000으로 변경해도, 매직 넘버가 없다면 테스트가 성공하지만 매직 넘버가 있다면 테스트가 실패한다.
리팩토링 후
public class MagicNumber {
public static void main(String[] args) {
MagicNumber magicNumber = new MagicNumber();
MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
player.action(Player.COMMAND_ATTACK);
player.action(Player.COMMAND_DEFENCE);
}
public class Player {
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;
private String name;
public Player() {
}
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void action(int command) {
if (command == COMMAND_ATTACK) {
attack();
} else if (command == COMMAND_DEFENCE) {
defence();
}
}
private void attack() {
System.out.println(this.name + " is going to Attack!!");
}
private void defence() {
System.out.println(this.name + " is going to Defence!!");
}
}
}
- 기호 상수가 충분한 정보를 제공하므로 주석이 필요 없다.
더 나은 리팩토링
- 기호 상수로 만든다고 해도 실제로는 상수 값이므로 매직 넘버를 직접 적어도 문제없이 컴파일 된다.
- 실수가 생길 수 있다.
- 커맨드 클래스 객체를 활용하거나 enum을 활용한다.
enum 활용
- 자바 5부터 enum을 사용할 수 있다.
public class EnumMagicNumber {
public static void main(String[] args) {
EnumMagicNumber magicNumber = new EnumMagicNumber();
Player player = new Player("Hoonmaro");
player.action(Player.Command.ATTACK);
player.action(Player.Command.DEFENCE);
}
}
// Player 클래스 분리
public class Player {
public enum Command {
ATTACK,
DEFENCE
}
private String name;
public Player() {
}
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void action(Command command) {
if (command == Command.ATTACK) {
attack();
} else if (command == Command.DEFENCE) {
defence();
}
}
private void attack() {
System.out.println(this.name + " is going to Attack!!");
}
private void defence() {
System.out.println(this.name + " is going to Defence!!");
}
}
- 기존 Player Inner 클래스를 분리하였다.
- 증첩 enum 은 암묵적으로 static 클래스 이기 때문에 Inner 클래스 안에 선언할 수 없기 때문이다.
- Java Language Spec 8.9 Enums 참고
- enum을 활용하여 직관적으로 의미를 전달 할 수 있다.
- 상수가 아니므로 잘못 사용된 곳에서 컴파일에서 경고가 발생하여 에러를 사전에 방지할 수 있다.
기호 상수가 적합하지 않은 경우
- 배열 길이
- 배열 길이는 배열 객체에 length라는 필드가 있다.
- 기호 상수가 언제나 올바른 배열길이가 아닐 수도 있다.
- 잘 알려진 값을 대체하는 기호 상수 (오히려 가독성이 떨어짐)
바이트 코드에 내장된 상수에 주의
- 필드값이 컴파일할 때 정해지는 상수일 경우 리컴파일시 변경된 값으로 바뀌겠지만,
- 이를 사용하는 다른 클래스에서는 리컴파일 하기 전에 이전 상수 값을 가지고 있으므로 문제가 발생한다.
- 매직 넘버를 치환한 모든 소스코드를 리컴파일 해야 정상 작동 된다.
'프로그래밍 > Java' 카테고리의 다른 글
마로의 Java(자바) 정리 - 클래스/객체/인스턴스의 모든 것 (836) | 2018.07.05 |
---|---|
마로의 Java(자바) 정리 - 9. 참조 타입(3): 열거(enum) 타입 (853) | 2016.06.28 |
마로의 Java(자바) 정리 - 9. 참조 타입(2): 배열(Array) (1625) | 2016.02.16 |
마로의 Java(자바) 정리 - 9. 참조 타입(1): 참조 변수, String (1249) | 2016.01.12 |
마로의 Java(자바) 정리 - 8. 자바 메모리 구조 (2752) | 2015.11.23 |