※자바로 배우는 리팩토링 입문 책을 보고 작성하였습니다.
제어플래그 삭제
제어플래그 - 상태를 기록하고 처리 흐름을 제어하기위한 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: 재귀구조가 있을때 재귀로 메서드를 적용할지 안 할지를 제어하는 플래그
'Today I learned' 카테고리의 다른 글
HttpServletXxx 관련 메서드 Cheet Sheat (0) | 2019.07.16 |
---|---|
자바로 배우는 리팩토링 입문 4 (0) | 2019.07.14 |
서블릿 컨테이너가 필터체인을 구축할 때 사용하는 순서 (0) | 2019.07.12 |
base64로 인코딩된 바이너리 이미지태그를 서버에 업로드 후 업로드된 URL로 치환하는 jsCode (0) | 2019.07.10 |
직렬화 역직렬화 (0) | 2019.07.09 |
댓글