넥스트스텝 - 플레이그라운드를 진행하면서
시작부분의 아주 기초적인 "문자열 계산기"를 객체지향적으로 만들어 보았다.
생각보다 너무 힘겨웠고,
완료하고 나서도 너무 보기 복잡한 코드인 것 같아 마음에 들지 않는다.
현재 상황과 문제점을 정리해서 기억을 남기고자 한다.
이름이 너무 쓸데없이 긴 것처럼 느껴지는 클래스,변수,메소드 명이 많다.
모든 기능마다 메소드를 매번 분리했다.
이름도 매번 길게 만들다 보니 읽기 불편하게 느껴진다.
처음보는 사람이 보기엔 이렇게 이름이 긴게 좋다지만, 너무 과한 것이 아닌가 하는 생각이 든다.
이에 대한 원인으로는
내가 이름을 잘못지었다.
더 깔끔한 코드가 나올 수 있는데 복잡한 과정을 거치고 있다.
줄임말을 쓰지 않는 원칙이 있다.
calculate -> cal, Iterator -> Iter, String -> str
정도만 변경해도 가독성이 더 좋아질 것 같은데...
calculateOneByOne() 메소드는
if/else if의 위의 4가지 경우(부호가 정상적으로 입력된 경우)
함수가 정상적으로 작동하고 끝나야 한다.
그러나 부호가 정상적이지 않은 경우를 else로 처리하고 싶은데
그렇지 않아서, else if로 검사를 1회 더 하는 코드로 작성되어 있다.
기호마다 다른 메소드를 발동시켜야 하기 때문에
if, else if문으로 equals를 4회 확인하고 있다.
이거보다 더 좋은 코드가 분명히 있을 것 같다.
인터넷으로 찾아보면 1) enum을 활용하거나 2) HashMap을 사용해서 입력값을 그대로 꺼내는 형태로 사용하는 것 같던데, 내가 아직 자바에 익숙하지 못해서 활용할 수 없는 방법이었다.
import java.util.Arrays;
import java.util.Scanner;
import java.util.Iterator;
/*
조건에 의해 다음의 경우는 고려하지 않는다.
1. 괄호
2. 소수점 이하로 내려가는 경우
3. 숫자와 부호 사이에 띄어쓰기가 없는 경우
4. 나누기에 의해 정수가 아니게 되는 경우
5. 처음이나 마지막이 숫자가 아닌 경우
*/
public class StringCalculator {
// 결과를 저장할 객체를 생성한다.
static CalculateWithSign resultObj = new CalculateWithSign();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String stringOfInput = scanner.nextLine();
// 에러체크: 길이 0 또는 1인지 확인
if (isShortInput(stringOfInput)) return;
// 이터레이터로 바꿀 객체 만들어서 변환하기.
StringToIterator iterated = new StringToIterator(stringOfInput);
Iterator<String> iteratorOfString = iterated.change();
// 계산하기
calculateWhileIterator(iteratorOfString);
// 결과 출력
println(resultObj.getValue());
}
private static boolean isShortInput(String stringOfInput) {
if (stringOfInput.length() == 0) {
println(0);
return true;
} else if (stringOfInput.length() == 1) {
println(Integer.parseInt(stringOfInput));
return true;
}
return false;
}
private static void calculateWhileIterator(Iterator<String> iteratorOfString) {
resultObj.plus(toInt(iteratorOfString.next()));
while (iteratorOfString.hasNext()) {
calculateOneByOne(iteratorOfString.next(), toInt(iteratorOfString.next()));
}
}
private static int toInt(String number){
return Integer.parseInt(number);
}
private static void calculateOneByOne(String sign, int number) throws NotSupportingSignException {
if (sign.equals("+")) {resultObj.plus(number);}
else if (sign.equals("-")) {resultObj.minus(number);}
else if (sign.equals("*")) {resultObj.multiply(number);}
else if (sign.equals("/")) {resultObj.divide(number);}
else if (!"+-*/".contains(sign)){
throw new NotSupportingSignException("계산 부호가 옳지 않습니다.");
}
}
private static class NotSupportingSignException extends RuntimeException{
NotSupportingSignException(String msg){
System.out.println(msg);
}
}
private static void println(int integer) {
System.out.println(integer);
}
}
a. "123 + 456" 이라는 문자를 [123, +, 456] 으로 분리하고
b. iterator로 읽을 수 있게 하는 메소드를 갖는다.
class StringToIterator{
private final String stringToChange;
StringToIterator(String stringOfInput){
this.stringToChange = stringOfInput;
}
public Iterator<String> change(){
return arrayToIterator();
}
Iterator<String> arrayToIterator(){
return Arrays.stream(stringToArrayByBlank()).iterator();
}
String[] stringToArrayByBlank(){
return stringToChange.split(" ");
}
}
a. 사용하려면 인스턴스를 생성해야 한다. 인스턴스전용 멤버변수로 결과값을 저장한다.
b. 계산은 멤버변수에 대해 하며, 계산할 number 하나만 입력받는다.
c. 값을 꺼내기 위한 getValue() 메소드를 분리했다.
class CalculateWithSign{
private int result = 0;
int getValue(){
return result;
}
void plus(int number){
result += number;
}
void minus(int number){
result -= number;
}
void multiply(int number){
result *= number;
}
void divide(int number){
if (number == 0){
throw new ArithmeticException("0으로 나눌 수 없습니다.");
} else if ((float) result % (float) number != 0) {
throw new NumberFormatException("나눈 숫자가 정수가 아닙니다.");
}
result /= number;
}
}
일단 정리한 내용들을 토대로 리팩토링을 해보고
도저히 고칠 수 없을 때까지 좋은 코드를 만들어보겠다.
파이팅!