1일차 다짐처럼 우선 기능 요구사항 및 프로그래밍 요구사항을 만족하는 0.0 버전을 구현했다. 1주차 미션의 핵심 기능은 '계산기'다. 아래는 구현 과정에서 했던 고민이다.
1. Console 라이브러리 분석
아래는 우아한테크코스에서 제공한 Console 라이브러리이다.
readLine()
- 콘솔에서 한 줄의 입력을 받아 문자열로 반환하는 메서드
close()
- scanner가 생성되어 있는 경우, 이를 닫고 다시 null로 설정하는 메서드
getInstance() 메서드
- scanner가 아직 생성되지 않았을 경우 Scanner(System.in)으로 초기화하고, 생성된 객체를 반환하는 메서드
import java.util.Scanner;
public class Console {
private static Scanner scanner;
private Console() {
}
public static String readLine() {
return getInstance().nextLine();
}
public static void close() {
if (scanner != null) {
scanner.close();
scanner = null;
}
}
private static Scanner getInstance() {
if (scanner == null) {
scanner = new Scanner(System.in);
}
return scanner;
}
}
2. 문자열에서 구분자와 숫자를 추출하는 과정에서
(1) String vs Matcher/Pattern
String을 이용하게 되면 indexOf()로 특정 위치를 찾고, substring()으로 구분자와 숫자 문자열을 추출할 수 있다. 반면 Matcher와 Pattern을 이용하면 정규식을 사용해 해당하는 패턴을 찾아준다. 이 때 사용한 정규식 "//(.)\n(.*)"은 "//"로 시작하고, "\n"이 포함된 부분을 찾아준다.
'좋은 코드, 나쁜 코드'에 따르면 코드는 읽기 쉽고, 예측 가능해야 한다. 위 과정을 코드로 비교하니 Matcher와 Pattern을 사용한 코드가 효율성 측면에서는 느리지만, 가독성 측면에서 훨씬 뛰어나 Matcher와 Pattern을 사용했다.
코드는 읽기 쉽고 예측 가능해야 한다.
- 좋은 코드, 나쁜 코드 -
- String을 사용한 경우
public static int getSum(String input) {
String delimiter = ",|:";
String numbers = input;
if (input.startsWith("//")) {
int delimiterStartIndex = input.indexOf("//") + 2;
int delimiterEndIndex = input.indexOf("\n");
if (delimiterEndIndex > delimiterStartIndex) {
delimiter = input.substring(delimiterStartIndex, delimiterEndIndex);
numbers = input.substring(delimiterEndIndex + (input.contains("\n") ? 2 : 1));
}
return sumNumbers(numbers, delimiter);
}
- Matcher, Pattern을 사용한 경우
public static int getSum(String input) {
String delimiter = ",|:";
String numbers = input;
Matcher matcher = Pattern.compile("//(.)\n(.*)").matcher(input);
if (matcher.matches()) {
delimiter = Pattern.quote(matcher.group(1));
numbers = matcher.group(2);
}
return sumNumbers(numbers, delimiter);
}
또한, 정규식으로 인한 NumberFormatException이 지속적으로 발생했다. 해당 문제는 자바에서의 이스케이프 처리가 제대로 되지 않았기 때문이다. "//(.)\n(.)" 정규식에서 사용되는 "\n"은 줄바꿈을 뜻하는 문자다. 따라서 해당 정규식을 ‘"//(.)\\\\n(.)”’로 바꾸니 문제가 해결되었다.
- 바꾼 코드
public static int getSum(String input) {
String delimiter = ",|:";
String numbers = input;
Matcher matcher = Pattern.compile("//(.)\\\\n(.*)").matcher(input);
if (matcher.matches()) {
delimiter = Pattern.quote(matcher.group(1));
numbers = matcher.group(2);
}
return sumNumbers(numbers, delimiter);
}
(2) String vs StringBuilder
String은 불변 객체다. 또한 내부적으로 문자 배열을 사용하여 문자열을 저장한다. 반면에 StringBuilder는 가변 객체다. 또한 해당 객체 역시 내부적으로 문자 배열을 사용하지만, 크기를 유동적으로 조절할 수 있다.
'이펙티브 자바'에 따르면 StringBuilder sb = new StringBuilder()와 같은 불필요한 객체 생성을 피하라고 한다. 반면에 String과 같은 불변 객체는 언제든지 재사용할 수 있다. StringBuilder를 사용하려 했던 이유는 유동적이고 더 빠르다(근거는 없는,,)는 인식이 있었다. 하지만 두 코드 모두 실행 시간이 약 7sec로 실행 시간 면에서는 큰 차이가 없었기에 String을 사용했다.
아이템 6 : 불필요한 객체 생성을 피하라
아이템 17 : 변경 가능성을 최소화하라
아이템 1 : 생성자 대신 정적 팩터리 메서드를 고려하라
- 이펙티브 자바 -
- StringBuilder를 사용한 경우
public static int getSum(String input) {
String delimiter = ",|:";
StringBuilder numbers = new StringBuilder(input);
Matcher matcher = Pattern.compile("//(.)\\\\n(.*)").matcher(input);
if (matcher.matches()) {
delimiter = Pattern.quote(matcher.group(1));
numbers = new StringBuilder(matcher.group(2));
}
return sumNumbers(numbers, delimiter);
}
- String을 사용한 경우
public static int getSum(String input) {
String delimiter = ",|:";
String numbers = input;
Matcher matcher = Pattern.compile("//(.)\\\\n(.*)").matcher(input);
if (matcher.matches()) {
delimiter = Pattern.quote(matcher.group(1));
numbers = matcher.group(2);
}
return sumNumbers(numbers, delimiter);
}
'우아한테크코스 > 7기 프리코스' 카테고리의 다른 글
[우아한테크코스 7기 프리코스] 3주차 회고록 (0) | 2024.11.06 |
---|---|
[우아한테크코스 7기 프리코스] 2주차 회고록 (3) | 2024.10.28 |
[우아한테크코스 7기 프리코스] 1주차 회고록 (2) | 2024.10.22 |
[우아한테크코스 7기 프리코스] 첫째주 3일차 회고록 (0) | 2024.10.17 |
[우아한테크코스 7기 프리코스] 첫째주 1일차 회고록 (0) | 2024.10.15 |