이번 리팩토링에서는 중복 제거, Rq 클래스 도입을 통해 컨트롤러의 역할을 단순화하는 과정을 진행하였다.
1. findIndexById 메서드로 중복 제거
삭제와 수정 기능에서는 모두 ID에 해당하는 명언의 위치를 찾는 코드가 중복되어 있었다.
int targetIdx = -1;
for (int i = 0; i < wiseSayings.size(); i++) {
WiseSaying wiseSaying = wiseSayings.get(i);
if (wiseSaying.getId() == id) {
targetIdx = i;
break;
}
}
이 코드는 어디서든 재사용 가능한 기능이므로 findIndexById() 메서드로 분리하였다.
private int findIndexById(int id) {
for (int i = 0; i < wiseSayings.size(); i++) {
if (wiseSayings.get(i).getId() == id) {
return i;
}
}
return -1;
}
반면, "명언이 존재하지 않습니다" 같은 출력문은 삭제와 수정에서 사용하는 메시지이므로 재사용성이 낮아 별도의 메서드로 분리하지 않았다.
2. Stream 적용
기존 명언을 찾는 반복문을 Stream으로 변경했다.
for (WiseSaying wiseSaying : wiseSayings) {
if (wiseSaying.getId() == id) {
return wiseSaying;
}
}
Stream을 적용하면 다음과 같이 작성할 수 있다.
return wiseSayings.stream()
.filter(wiseSaying -> wiseSaying.getId() == id)
.findFirst()
.orElse(null);
3. removeIf를 이용한 삭제
삭제 기능에서는 먼저 인덱스를 찾은 뒤 remove()를 호출하였다.
ArrayList의 removeIf()를 사용하면 조건에 맞는 객체를 바로 삭제할 수 있다.
wiseSayings.removeIf(wiseSaying -> wiseSaying.getId() == id);
삭제 조건만 작성하면 컬렉션이 알아서 해당 객체를 제거한다.
4. Rq 클래스 도입
명령어를 분석하는 코드는 앞으로도 여러 곳에서 사용할 가능성이 있다.
이러한 컨트롤러 수준의 공통 기능을 관리하기 위해 Rq 클래스를 도입하였다.
이와 같은 방식은 이후 Spring Boot의 Controller에서도 비슷한 개념으로 사용된다.
getActionName 구현
명령어에서 실행할 동작(Action)만 추출하는 메서드이다.
삭제?id=3
이라는 명령이 들어오면 ?를 기준으로 문자열을 나눈 뒤,
public String getActionName() {
String[] cmdBits = cmd.split("\\?");
return cmdBits[0];
}
cmdBits[0]인 삭제를 반환한다.
이를 통해 App에서는 명령어를 직접 파싱하지 않고, rq.getActionName()만 호출하여 어떤 기능을 실행할지 판단할 수 있다.
5. getParam 구현
getParam()은 명령어에 포함된 파라미터 값을 가져오는 메서드이다.
예를 들어 다음과 같은 명령이 입력되었다고 가정한다.
목록?keywordType=content&keyword=과거
Rq 객체가 생성되면 생성자에서 먼저 명령어를 분석한다.
1. ?를 기준으로 명령어와 파라미터를 분리한다.
String[] cmdBits = cmd.split("\\?");
//["목록", "keywordType=content&keyword=과거"]
이 때 cmdBits[0]은 getActionName()에서 구하는 명령과 같으므로 아래와 같이 수정할 수 있다.
public String getActionName() {
return actionName;
}
2. &를 기준으로 각 파라미터를 분리한다.
String params = cmdBits.length > 1 ? cmdBits[1] : ""; //예외 처리
String[] paramBits = params.split("&");
//["keywordType=content", "keyword=과거"]
3. =를 기준으로 key와 value를 나누어 Map에 저장한다.
for (String param : paramBits) {
String[] keyValue = param.split("=");
if (keyValue.length < 2) //예외처리
continue;
paramMap.put(keyValue[0], keyValue[1]);
}
이후 getParam()은 Map에서 원하는 값을 반환한다.
public String getParam(String key, String defaultValue) {
return paramMap.getOrDefault(key, defaultValue);
}
사용 예시는 다음과 같다.
String keywordType = rq.getParam("keywordType", ""); //content
String keyword = rq.getParam("keyword", ""); //과거
명령어를 한 번만 파싱하여 Map에 저장해두므로, 이후에는 문자열을 다시 분리하지 않고 필요한 값을 바로 가져올 수 있다.
6. getParamAsInt 구현
getParamAsInt()는 파라미터 값을 정수(int)로 변환하여 반환하는 메서드이다.
getParam()은 문자열을 반환하므로, 숫자가 필요한 경우에는 Integer.parseInt()를 이용해 형변환해야 한다.
이를 메서드로 분리하여 재사용할 수 있도록 구현하였다.
public int getParamAsInt(String key, int defaultValue) {
String rst = getParam(key, "");
try {
return Integer.parseInt(rst);
} catch (NumberFormatException e) {
return defaultValue;
}
}
먼저 getParam()을 호출하여 문자열 값을 가져온 뒤, Integer.parseInt()로 정수로 변환한다.
만약 파라미터가 비어 있거나 숫자가 아닌 값이라면 NumberFormatException이 발생하므로, catch에서 기본값(defaultValue)을 반환한다.
7. paramMap을 Stream으로 변경
기존에는 반복문을 사용하여 파라미터를 하나씩 Map에 저장하였다.
이를 Stream으로 변경하여 문자열 분리부터 Map 생성까지 하나의 흐름으로 처리하도록 개선하였다.
String queryString = cmdBits.length > 1 ? cmdBits[1] : "";
String[] queryStringBits = queryString.split("&");
paramMap = Arrays.stream(queryStringBits)
.map(part -> part.split("="))
.filter(bits -> bits.length == 2 && bits[0] != null && bits[1] != null)
.collect(
Collectors.toMap(
bits -> bits[0],
bits -> bits[1]
)
);
split("&")를 통해 각 파라미터로 분리되는 것 까진 동일하다.
map()에서는 각 문자열을 다시 =를 기준으로 분리한다.
filter()는 key=value 형태가 아닌 잘못된 데이터가 Map에 저장되지 않도록 걸러준다.
마지막으로 Collectors.toMap()을 사용하여 key와 value를 Map으로 변환한다.
'BackEnd > Java' 카테고리의 다른 글
| [Java] 명언 게시판(1~8단계) - 리스트 버전 (0) | 2026.07.20 |
|---|---|
| [Java] 명언 게시판 만들기(6~8단계) - 리팩토링 (0) | 2026.07.20 |
| [Java] 명언 게시판 만들기(6~8단계) (0) | 2026.07.20 |
| [Java] 명언 게시판 만들기(1~5단계) - 리팩토링 (0) | 2026.07.16 |
| [Java] 명언 게시판 만들기(1~5단계) (0) | 2026.07.16 |
댓글