본문 바로가기
Today I learned

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

by soheemon 2019. 7. 13.

※자바로 배우는 리팩토링 입문 책을 보고 작성하였습니다.

 

제어플래그 삭제

제어플래그 - 상태를 기록하고 처리 흐름을 제어하기위한 boolean타입 변수를 의미.

제어플래그를 '지나치게' 사용하면 프로그램 처리 흐름을 파악하기 어려워진다. 

제어플래그 대신에 break, continue, return등을 써서 처리 흐름을 제어하는 리팩토링이다.

 

리팩토링 카탈로그

이름: 제어 플래그 삭제

상황: 처리 흐름을 제어한다.

문제: 처리 흐름을 제어하는 플래그 때문에 코드가 복잡해진다.

해법: 제어 플래그를 삭제하고 break, continue, return을 사용한다.

결과: 조건 의미와 제어 흐름이 명확해짐(but, 단순 반복에도 무리하게 적용하면 코드가 부자연스러워짐)

방법:

break, continue를 사용하는경우 -

(1) 제어 플래그로 제어하는 반복문 찾기 (2) 제어 플래그 할당을 break나 continue로 치환 (3) 컴파일 후 테스트

return을 사용하는 경우 -

(1)제어 플래그로 제어하는 반복문 찾기 (2) 해당 반복문을 새로운 메서드로 추출 (3)제어 플래그 할당을 return으로 치환 (4) 컴파일 해서 테스트

 

//Before
flag = true;
while (flag) {
	...
    if (A) {
    	flag = false;
    } else {
    	...
    }
}

//After
while (true){
	...
    if (A) {
    	break;
    } else {
    	...
    }
}

 

제어플래그 삭제 예제 프로그램

FindInt는 int배열에 특정 요소가 포함되어있는지 확인하는 예제이다.

 

//리팩토링전 코드. 특정 배열에 내가 찾고자하는 숫자가 있는지 찾는다.
public class FindInt{
	public static boolean find(int[] data, int target){
    	boolean flag = false;
        for(int i = 0; i < data.length && !flag; i++){
        	if (data[i] == target){
            	flag = true;
            }
            return flag;
        }
    }
}

//이를 호출하는 Main클래스
public class Main {
	public static void main(String[] args){
    	int[] data = {
        	1, 9, 0, 2, 8, 5, 6, 3, 4, 7
        };
        if ( FindInt.find(data, 5)) {
        	System.out.println("Found!");
        } else {
        	System.out.println("Not FOund...");
        }
    }
}

Break를 사용하여 리팩토리 실행

변수명을 true일때 어떤것을 의미하는지에 대한 정보를 포함하도록 변경한다. flag -> found

//플래그를 만족할때 break하여 반복문을 빠져나가도록 리팩토링.
public class FindInt{
	public static boolean find(int[] data, int target){
    	boolean found = false;
        for(int i = 0; i < data.length; i++){
        	if (data[i] == target){
            	found = true;
                break;
            }
            return found;
        }
    }
}

 

리팩토링 실행 - return 사용

return을 사용하여 제어플래그를 삭제한다.

public class FindInt{
	public static boolean find(int[] data, int target){
        for(int i = 0; i < data.length; i++){
        	if (data[i] == target){
            	return true;
            }
            return false;
        }
    }
}

 

예제프로그램 2 - SimpleDatabase

메일주소와 사용자명 대응표를 만드는 간단한 데이터베이스 예제.

- 제어플래그와 코드에서 사용하는 변수에 부적절한 이름이 붙어있어서 코드를 파악하기가 어렵다는 문제가 있다.

//프로그램이 읽을 텍스트 파일 예제
test@example.com=soheemon1
test1@example.com=soheemon2

//리팩토링 전
import java.io.*;
import java.util.*;

public vlass SimpleDatabase {
	private Map<String, String> _map = new HashMap<String, String>();
    public SimpleDatabase(Reader r1) throus IOException{
    	BuffueredReader r2 = new BufferedReader(r1);
        boolean flag = false;
        String tmp;
        while (!flag){
        	tmp = r2.readLine;
            if(tmp = null) {
            	flag = true;
            } else {
            	boolean flag2 = true;
                StringBuffer s1 = new StringBuffer();
                StringBuffer s2 = new StringBuffer();
                
                for ( int i = 0; i < tmp.length(); i++){
                	char tmp2 = tmp.chatAt(i);
                    if(flag2){
                    	if (tmp2 == '='){
                        	flag2 = false;
                        } else {
                        	s1.append(tmp2);
                        }
                    } else {
                    	s2.append(tmp2);
                    }
                }
                String ss1 = s1.toString();
                String ss2 = s2.toString();
                _map.put(ss1, ss2);
            }
        }
    }
    public void putValue(String key, String value){
    	_map.put(key, value);
    }
    
    public String getValue(String key) {
    	return _map.get(key);
    }
    
    public Iterator<String> iterator() {
    	return _map.keySet().iterator();
    }
}

//SimpleDatabase 클래스를 사용하는 Main 클래스

public class Main {
	public static void main(String[] args){
    	try {
        	SimpleDatabase db = new SimpleDatabase(new FileReader("dbfile.txt"));
            Iterator<String> it = db.iterator();
            while (it.hasNext()){
            	String key = it.next();
                System.out.println("KEY: \"" + key + "\"");
                System.out.println("VALUE: \"" + db.getValue(key) + "\"");
                System.out.println();
            }
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
}

 

'나는 이해 할 수 있지만, 다른사람이(그리고 한달뒤의 내가) 이해하기 어려운 코드..가 이런것 이구나.

지금 바로 기존 코드들을 리팩토링 할 수는 없겠지만.. 적어도 새로운 코드를 작성할때 책에서 배운내용을 활용하기위해 노력해야겠다.

 

/*	
*	리팩토링 실행.
*	제어플래그 삭제 및 =를 찾아서 앞뒤를 StringBuffer로 완성하는 로직에서 
*	'='의 위치를 찾아 subString으로 추출하는 방법으로 변경... + 변수명을 알기 쉽게 변경
*/

public vlass SimpleDatabase {
	private Map<String, String> _map = new HashMap<String, String>();
    public SimpleDatabase(Reader r) throws IOException{
    	BuffueredReader reader = new BufferedReader(r);
        String line;
        while (true){
        	line = reader.readLine;
            if(line = null) {
            	break;
            }
            
            int equalIndex = line.indexOf("=");
            if (equalIndex > 0) {
                String key = line.substring(0, equalIndex);
                String value = line.substring(equalIndex + 1, line.length());
                _map.put(key, value);
            }
        }
    }
    public void putValue(String key, String value){
    	_map.put(key, value);
    }
    
    public String getValue(String key) {
    	return _map.get(key);
    }
    
    public Iterator<String> iterator() {
    	return _map.keySet().iterator();
    }
}

//SimpleDatabase 클래스를 사용하는 Main 클래스

public class Main {
	public static void main(String[] args){
    	try {
        	SimpleDatabase db = new SimpleDatabase(new FileReader("dbfile.txt"));
            Iterator<String> it = db.iterator();
            while (it.hasNext()){
            	String key = it.next();
                System.out.println("KEY: \"" + key + "\"");
                System.out.println("VALUE: \"" + db.getValue(key) + "\"");
                System.out.println();
            }
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
}

-break나 return을 쓰면 가독성이 좋아지는 이유

break나 return을 본 순간 이후에 오는 코드를 읽지 않아도 되는 경우가 많기 때문이다.

 

//생략된 부분에서 flag가 true가 될수있으므로.. 아래 코드 만으로는 while문 종료 여부를 알 수 없다... 
flag = true;
while (flag) {
	if (조건 A) {
    	flag = false;
    }
    ...
    ...
    ...
}

//생략된 부분을 보지 않아도 종료여부를 알 수 있다.
while (true) {
	if (조건 A) {
    	break;
    }
    ...
    ...
    ...
}

-인스턴스 필드로 만든 제어 플래그의 위험성

위 예제에 등장한 제어 플래그는 모두 지역 변수였다. 따라서 *제어 플래그 상태는 메서드 내부 처리에만 영향을 준다.*

지역변수의 제어플래그의 영향력은 해당 메서드 안에서 끝난다.

 

인스턴스 필드를 제어 플래그로 사용하는 경우, 어떤 메서드가 종료된 다음에도 제어 플래그는 인스턴스 상태를 계속 유지하게 된다.

따라서 *인스턴스 제어 플래그를 남용하면 해당 클래스 전체 코드를 읽기 어려워 지므로 조심해야 한다.*

 

-플래그명

의미를 알아내기 쉬운 플래그 이름

initialized : 초기화가 끝났음을 뜻함

debug: 디버깅시 true로 설정하는 플래그. true일때 디버깅 정보를 표시하거나 로그 파일에 기록을 남김

error: 객체에 에러가 발생했음을 나타내는 플래그. 비슷한것으로 데이터가 손상되었음을 나타내는 broken플래그가 있다.

done: 처리가 완료되었음을 나타냄. 반대로 처리가 중단되었음을 나타내는 aborted 플래그가 있다.

interrupted: 처리취소(인터럽트)가 발생했음을 나타내는 플러그

recurse: 재귀구조가 있을때 재귀로 메서드를 적용할지 안 할지를 제어하는 플래그

 

 

댓글