매직 넘버 치환
- 소스 코드에 특정한 숫자(매직 넘버(magic number))를 직접 작성하는 것은 나쁜 코딩 스타일
나쁜 예
public class MagicNumber {
public static void main(String[] args) {
MagicNumber magicNumber = new MagicNumber();
MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
player.action(0); // 0은 공격
player.action(1); // 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 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 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(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!!");
}
}
}
- 기존 Player Inner 클래스를 분리하였다.
- 증첩 enum 은 암묵적으로 static 클래스 이기 때문에 Inner 클래스 안에 선언할 수 없기 때문이다.
- Java Language Spec 8.9 Enums 참고
- enum을 활용하여 직관적으로 의미를 전달 할 수 있다.
- 상수가 아니므로 잘못 사용된 곳에서 컴파일에서 경고가 발생하여 에러를 사전에 방지할 수 있다.
기호 상수가 적합하지 않은 경우
- 배열 길이
- 배열 길이는 배열 객체에 length라는 필드가 있다.
- 기호 상수가 언제나 올바른 배열길이가 아닐 수도 있다.
- 잘 알려진 값을 대체하는 기호 상수 (오히려 가독성이 떨어짐)
바이트 코드에 내장된 상수에 주의
- 필드값이 컴파일할 때 정해지는 상수일 경우 리컴파일시 변경된 값으로 바뀌겠지만,
- 이를 사용하는 다른 클래스에서는 리컴파일 하기 전에 이전 상수 값을 가지고 있으므로 문제가 발생한다.
- 매직 넘버를 치환한 모든 소스코드를 리컴파일 해야 정상 작동 된다.
참조 사이트 : hoonmaro.tistory.com/44
'JAVA > 기본 상식' 카테고리의 다른 글
[JAVA] 추상 클래스(abstract class) (0) | 2021.05.11 |
---|---|
[JAVA] 객체지향 생활 체조 원칙 9가지 (0) | 2021.04.23 |
[JAVA] 인스턴스 생성과 힙 메모리 (heap memory) (0) | 2021.02.27 |
[Java] Runnable과 Callable 차이점 (0) | 2021.02.26 |
Integer.parseint & Integer.valueof 차이 (0) | 2021.02.26 |