1. 리팩토링
1) 제어 플래그 삭제
- 처리 흐름을 제어할 때 사용하는 플래그 : 제어 플래그
- 제어 플래그가 나쁜건 아닌데 지나치게 사용하면 처리 흐름 파악하기 어려움
2) 리팩토링 카탈로그
이름 | 제어 프래그 삭제 |
상황 | 처리 흐름을 제어한다. |
문제 | 처리 흐름을 제어하는 플래그 때문에 코드가 복잡해집니다. |
해법 | 제어 플래그를 삭제하고 break, continue, return을 사용한다. |
결과 | O 조건 의미와 제어 흐름이 명확해짐 X 단순 반복에도 무리하게 적용하면 코드가 부자연스러워집니다. |
방법 |
(break, continue을 사용하는 경우)
1) 제어 플래그로 제어하는 반복문 찾기2) 제어 플래그 할당을 break나 continue로 치환 3) 컴파일 (return을 사용하는 경우)
1) 제어 플래그로 제어하는 반복문 찾기2) 해당 반복문을 새로운 메서드로 추출 3) 제어 플래그 할당을 return으로 치환 4) 컴파일 |
2. 예제 프로그램
1) 리팩토링 전
- 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++) { //flag가 false일 때만 실행
if (data[i] == target) {
flag = true;
}
}
return flag;
}
}
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...");
}
}
}
FindInt 클래스를 보면 조금 어지럽다(굳이 저렇게 쓰네..) 교재 왈 나쁜 코드
2) 리팩토링 실행
- 변수명 변경도 리팩토링! flag -> found
- 제어 플래그를 삭제하고 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 이용
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;
}
}
3. 예제 프로그램(SimpleDatabase)
- 메일 주소와 사용자명 대응표를 만드는 간단한 데이터베이스
- 텍스트 파일 예제(dbfile.txt)
1) 리팩토링 전
import java.io.*;
import java.util.*;
public class SimpleDatabase {
private Map<String,String> _map = new HashMap<String,String>();
public SimpleDatabase(Reader r1) throws IOException {
BufferedReader 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.charAt(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();
}
}
- java에서는 키와 값으로 구성되는 쌍으로 해시맵(HashMap)을 사용한다.
해시맵은 Map인터페이스를 구현한 대표적인 Map 컬렉션으로, 해싱을 사용하여 많은 양의 데이터를 검색하는데 뛰어난 성능을 보인다.
- String builder는 1개의 스레드를 사용, String값 불변! String buffer는 멀티스레드 사용, String 값 변할 수 있음(대치)
- iterator는 반복자로 해시맵의 데이터 값을 순회할 수 있도록 해준다. (keyset : 전체출력)
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
try {
SimpleDatabase db = new SimpleDatabase(new FileReader(System.getProperty("user.dir")+"/src/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();
}
}
}
2) 리팩토링 실행
- 제어 플래그 삭제 전 변수명 변경 해준다.
- indexOf를 사용하여 구분자가 "="임을 활용해 보다 간단한 코드 작성이 가능하다
import java.io.*;
import java.util.*;
public class SimpleDatabase {
private Map<String,String> _map = new HashMap<String,String>();
public SimpleDatabase(Reader r) throws IOException {
BufferedReader reader = new BufferedReader(r);
while (true) {
String 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();
}
}
4. 한 걸은 더 나아가기
1) break나 return을 쓰면 가독성이 좋아지는 이유
- break나 return을 본 순간 이후에 오는 코드를 읽지 않아도 되는 경우가 많기 때문
2) 인스턴스 필도로 만든 제어 플래그의 위험성
- 제어플래그를 지역변수로 선언하면 상관없지만 인스턴스 필드로 사용하면 메서드가 종료해도 그 인스턴스 상태를 유지하기 때문에 전체 코드를 읽기 어려워진다.
3) 플래그명
- 플래그명은 그 변수의 의미를 알 수 있게 이름을 지어야 됨
ex) initialized, debug, error, done, interrupted, recurse
4. boolean 이외의 플래그
5. 정규 표현식 패키지 사용
- 정규 표현식을 이용하여 좀 더 간단하게 코드 작성 가능
ex) Pattern.compile("([^=]+)=(.*)");
'Major > Java' 카테고리의 다른 글
[자바로 배우는 리팩토링 입문] 4장 널 객체 도입 (0) | 2022.04.15 |
---|---|
[자바로 배우는 리팩토링 입문] 3장 어서션 도입 (0) | 2022.04.15 |
[자바로 배우는 리팩토링 입문] 1장 매직 넘버를 기호 상수로 치환 (0) | 2022.04.13 |
[자바로 배우는 리팩토링 입문] 0장 리팩토링이란 (0) | 2022.04.13 |
[Power Java Compact] 7장 연습문제 (0) | 2022.01.05 |