♻️ 1장 매직 넘버를 기호 상수로 치환

Siri·2022년 8월 22일
0
post-thumbnail

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

소스코드에 특정한 숫자(매직 넘버 (magic number))를 직접 적는 건 좋지 않은 코딩 스타일

이유 1. 매직넘버의 의미를 알기 어렵다

100 : 매직 넘버(의미를 알기 어려움)
MAX_INPUT_LENGTH : 기호 상수(의미를 알기 쉬움)

이유 2. 매직 넘버는 수정하기 어려움

매직 넘버 사용

if (100 < input.length()){
	...
}

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

public class Something {
	public static final int MAX_INPUT_LENGTH = 100;
    ...
}
if (Something.MAX_INPUT_LENGTH < input.length()) {
	...
}

🌈 1.2.2 리팩토링 실행

1. 기호 상수 선언하기

(1) 기호 상수 선언

자바에서 기호 상수를 만드는 방법은 두가지이다.

  • public static final 클래스 필드를 이용하기
  • enum 사용하기

이번에는 public static final 클래스 필드를 사용하는 방법을 설명한다.
enum을 사용하는 방법은 '1.3 한걸음 더 나아가기'에서 설명한다.

public static final 키워드는 다음 목적으로 사용합니다.

  • public은 클래스 외부에서도 참조할 수 있음
  • static은 클래스 필드로 만듦
  • final은 잘못해서 할당하지 않도록 함

어떤 클래스 안에서만 사용할 기호 상수를 선언한다면 private ststic final도 가능.
명령어를 나타내는 기호 상수를 Robot 클래스 안에 선언하면 다음과 같다.

public static final int COMMAND_WALK = 0;
public static final int COMMAND_STOP = 1;
public static final int COMMAND_JUMP = 2;

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

Robot 클래스에서는 order 메서드를 수정합니다.

public void order(int command){
	//if (command == 0){
    if (command == COMMAND_WALK){
    	System.out.println(_name + " walks.");
    //} else if (command == 1){
    } else if (command == COMMAND_STOP){
    	System.out.println(_name + " stops.");
    //} else if (command == 2){
    } else if (command == COMMAND_JUMP){
    	System.out.println(_name + " jumps.");
    } else {
    	System.out.println("Command error. command = " + command);
    }
}

Main 클래스에서는 main 메서드를 변경하는데, Main 클래스에서 Robot 클래스 상수를 참조하려면 상수 앞에 Robot.을 붙여야 합니다.

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

(3) 기호 상수에 의존하는 다른 매직 넘버를 찾아서 기호 상수를 사용한 표현식으로 변환(중요)

이 단계에 대해서는 '상수 의존 관계' 칼럼을 참조.

상수 의존 관계

작업영역의 길이는 최대 입력 길이의 2배다.
의존 관계가 있는 매직 넘버를 이렇게 따로 따로 기호 상수로 만들면 안된다.

public static final int MAX_INPUT_LENGTH = 100;
public static final int WORK_AREA_LENGTH = 200;

그렇게 하면 두 상수 의존 관계를 표현할 수 없기 때문이다. 다음처럼 표현식으로 의존 관계를 표현하는게 올바르다.

public static final int MAX_INPUT_LENGTH = 100;
public static final int WORK_AREA_LENGTH = MAX_INPUT_LENGTH * 2;

(4) 컴파일

기호 상수 치환이 끝나면 컴파일한다.

2. 테스트

(1) 모든 기호 상수 치환이 끝나면 컴파일 해서 테스트

테스트 결과는 리팩토링 전과 같아야한다.

(2) 가능하다면 기호 상숫값을 변경한 후 컴파일해서 테스트(중요)

기호 상수의 값을 다른 값으로 변경한 후 테스트하면 빠트린 곳이 없는지 확인할 수 있다.

🌈 1.2.3 리팩토링 후

기호 상수가 충분한 정보를 제공하므로 더는 주석이 필요 없다

1.3 한걸음 더 나아가기

🌈 1.3 1 분류 코드를 클래스로 치환하기

예제에서 소개한 로봇 명령어는 이른바 분류 코드(type code)이다.
분류 코드는 타입 문제가 있다. (7장에서 소개 예정)
Robot.COMMAND_WALK 처럼 기호 상수로 만든다고 해도 실제로는 0이라는 int값이다.
따라서 프로그래머가 다음과 같이 매직 넘버 0을 직접 적어도 컴파일러는 아무런 경로를 출력하지 않는다.

// x 매직 넘버 0을 직접 썼습니다.
robot.order(0);

이러면 기호 상수를 도입했는데도 누군가가 실수할수도 있다.
따라서 분류 코드에 정수를 쓰지 말고 새 타입을 만들어 보자.
RobotCommand 클래스(코드 1-5)는 로봇 명령어를 나타내는 타입이다.

//코드 1-5
public class RobotCommand{
	private final String _name;
   public RobotCommand(String name) {
   	_name = name;
   }
   public String toString() {
   	return "[ RobotCommand: " + _name + " ]";
   }
}

COMMAND_WALK, COMMAND_STOP, COMMAND_JUMP는 여전히 Robot 클래스에서 선언한 public static final 기호 상수지만 int가 아닌 RobotCommand 타입이 된다. (코드 1-6).

public class Robot {
	private final String _name;
    public static final RobotCommand COMMAND_WALK = new RobotCommand("WALK");
    public static final RobotCommand COMMAND_STOP = new RobotCommand("STOP");
    public static final RobotCommand COMMAND_JUMP = new RobotCommand("JUMP");
    public Robot (String name) {
    	_name = name;
    }
    public void order(RobotCommand command) {
    	if (command == COMMAND_WALK) {
        	System.out.println(_name + " walks.");
        } else if (command == COMMAND_STOP) {
        	System.out.println(_name + " walks.");
        } else if (command == COMMAND_JUMP) {
        	System.out.println(_name + "jumps.");
        } else {
        	System.out.println("Command error. command = " + command);
        }
    }
}

Main 클래스 코드는 변화가 없다.
이렇게 기호 상수를 클래스로 치환 (여기서는 RobotCommand)하면, robot.order(0)처럼 매직 넘버를 사용해서 order 메서드를 호출할수 없게 된다.
만약 매직 넘버를 사용하면 컴파일 에러가 발생한다.

> javac Main.java
Main.java:4: error: incompatible types: int cannot be converted to RobotCommand
	robot.order(0);
    			^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output 1 error

🌈 1.3.2 enum

자바 5부터 enum으로 기호 상수를 표현할 수 있게 되었다.

public class Robot {
	private final String _name;
    //enum
	public enum Command {
    	WALK,
        STOP,
        JUMP,
    };
    public Robot (String name) {
    	_name = name;
    }
    public void order(RobotCommand command) {
    	if (command == Command.WALK) {
        	System.out.println(_name + " walks.");
        } else if (command == Command.STOP) {
        	System.out.println(_name + " walks.");
        } else if (command == Command.JUMP) {
        	System.out.println(_name + "jumps.");
        } else {
        	System.out.println("Command 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);
    }
}

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

기호 상수는 편리하지만 기호 상수를 쓰지 않는 게 좋을 때도 있다.
ex) for문에서 배열 길이를 나타낼 때 (java 배열에는 length 필드가 있기 때문)

잘 알려진 값에 기호 상수를 사용해도 소스 코드가 읽기 어려워진다.
ex) 배열 인덱스 최솟값을 MIN_INDEX라고 하는 경우 ( 최솟값은 0으로 정해져 있기 때문)

🌈 1.4.4 바이트 코드에 내장된 상수에 주의하기

필드 값을 변경하고 다른 클래스도 리컴파일을 해줘야 새로운 값이 넘어간다


출처

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

1개의 댓글

comment-user-thumbnail
2023년 9월 1일

이뻐요

답글 달기