본문으로 바로가기

매직 넘버 치환

  • 소스 코드에 특정한 숫자(매직 넘버(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!!"); 
        } 
        
     }
}

나쁜이유

  1. 매직 넘버의 의미를 알기 어렵다
    • 0과 1이 무슨 의미인가?
    • COMMAND_ATTACK과 같은 기호 상수를 쓰면 의미를 알기 쉽다
  2. 매직 넘버는 수정하기 어렵다
    • 0 또는 1 커맨드 숫자가 변경 될 경우 치환해야 하는데, 만약 많은 곳에서 동일한 커맨드를 쓴다면?
    • 손이 많이 가고, 틀리기 쉽고, 빼먹는 경우가 생길 수도 있다.

카탈로그

이름매직 넘버를 기호 상수로 치환 (Replace Magic Number with Symbolic Constant)

상황 상수를 사용하고 있음
문제 • 매직 넘버는 알기 어려움
• 매직 넘버가 여러 곳에 있으면 변경하기 어려움
해법 매직 넘버를 기호 상수로 치환함
결과 장점
• 상수의 의미를 알기 쉬워짐
• 기호 상수의 값을 변경하면 상수를 사용하는 모든 곳이 변경됨
단점/주의 • 이해하기 어려운 이름을 사용하면 오해가 생길 수 있음
방법
  1. 기호 상수 선언하기
    • 1. 기호 상수 선언
    • 2. 매직 넘버를 기호 상수로 치환
    • 3. 기호 상수에 의존하는 다른 매직 넘버를 찾아서 기호 상수를 사용한 표현식으로 변환
    • 4. 컴파일
  2. 2. 테스트
    • 1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트
    • 2. 가능하다면 기호 상숫값을 변경한 후 컴파일해서 테스트 |
관련 항목 분류 코드를 클래스로 치환
• 분류 코드를 상태/전략 패턴으로 치환

 

리팩토링

기호 상수 선언하기

  1. 기호 상수 선언
  • public static final 클래스 필드 사용
  • 또는 enum 사용
  • 어떤 클래스 안에서만 사용할 기호 상수를 선언할 경우 private 접근지시자를 사용할 수 있다
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;

 

  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);

 

  1. 기호 상수에 의존하는 다른 매직 넘버를 기호 상수를 사용한 표현식으로 변환
  • 상수 의존 관계는 상수 사이에 의존 관계가 있어 한 상수의 변경이 다른 상수에게도 영향을 미치는 경우를 말한다.
  • 이 때, 표현식으로 의존 관계를 표현해야 한다.
// 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;

테스트

  1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트
    • 테스트 결과는 리팩토링하기 전과 같아야 한다.
  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