Mission1. Baseball-String Calculator

Properties

๐Ÿ“2024.06.17

๐Ÿ’ป source_code

๋„ฅ์ŠคํŠธ ์Šคํ…: ์ž๋ฐ” ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ - ์ˆซ์ž ์•ผ๊ตฌ๊ฒŒ์ž„

์ˆซ์ž์•ผ๊ตฌ๊ฒŒ์ž„ - 1. ๋ฌธ์ž์—ด ๊ณ„์‚ฐ๊ธฐ ๊ตฌํ˜„ ๋ฐ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

๋ฌธ์ž์—ด ๊ณ„์‚ฐ๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„

โœ… ์š”๊ตฌ์‚ฌํ•ญ

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ฌธ์ž์—ด ๊ฐ’์— ๋”ฐ๋ผ ์‚ฌ์น™์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

  2. ์—ฐ์‚ฐ์ž ์šฐ์„  ์ˆœ์œ„๋Š” ์ ์šฉ ํ•˜์ง€ ์•Š์œผ๋ฉฐ ์™ผ์ชฝ ๋ถ€ํ„ฐ ์‚ฌ์น™์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋ฌธ์ œ ์ •์˜

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” Scanner๋ฅผ ์ด์šฉ ํ•  User๊ฐ€ ํ•„์š”ํ•จ.

  2. ๋ฌธ์ž์—ด์„ ์ž…๋ ฅ ๋ฐ›์•„์„œ ๊ฐœ๋ณ„ ์š”์†Œ์— ์ ‘๊ทผ์ด ์‰ฌ์šด Array ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜์ด ํ•„์š”ํ•จ.

  3. Array์—์„œ ๊ฐœ๋ณ„ ์š”์†Œ์— ์ ‘๊ทผ ํ•  ๋•Œ ๋งˆ๋‹ค ๋ฌธ์ž์—ด ์ˆ˜์‹์ด ํ”ผ์—ฐ์‚ฐ์ž์ธ์ง€, ์—ฐ์‚ฐ์ž์ธ์ง€ ๊ตฌ๋ถ„์ด ํ•„์š”ํ•จ.

  4. ์—ฐ์‚ฐ์ž์— ๋”ฐ๋ฅธ ํ”ผ์—ฐ์‚ฐ์ž์™€์˜ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ ํ•˜๋Š” ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํ•„์š”ํ•จ.

๐Ÿ‘‰ ํ•„์š”ํ•œ ๋‚ด์šฉ์„ ์ฝ”๋“œ๋กœ ์˜ฎ๊ธธ ๋•Œ, ์–ด๋–ค ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ๊ฐ€ ๋งŒ๋“ค์–ด์ ธ์•ผ ํ• ๊นŒ?

  • ์—ญํ• ๊ณผ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ณ„์ธต ๊ตฌ์กฐ ์„ค๊ณ„

  • ์‚ฌ์šฉ์ž์˜ ์ž…, ์ถœ๋ ฅ์„ ๋‹ค๋ฃจ๋Š” Presentation Layer

  • ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ์—ฐ์‚ฐ ํ•  Service Layer

  • ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ ํ•  Main

๐Ÿ‘ ์œ„ ๋ฐฉ์‹์œผ๋กœ ๊ฐ€์žฅ ๋Ÿฌํ”„ํ•˜๊ฒŒ ์ฝ”๋“œ๊ฐ€ ์ž‘๋™ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„

๐Ÿƒโ€โ™‚๏ธ ์ž‘๋™ํ•˜๋Š” ์ฝ”๋“œ ๊ตฌํ˜„ํ•˜๊ธฐ

๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„์˜ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ ํ•˜๊ณ  ํด๋ž˜์Šค๋ช…์„ ๊ธฐ์ค€์œผ๋กœ ๊ณ„์ธต์„ ์ž ์‹œ ๋‚˜๋ˆด๋‹ค. CalculatorService, CalculatorUser, StringCalculatorMain ์ด๋ ‡๊ฒŒ ํŒŒ์ผ์„ ์ƒ์„ฑ ํ•œ ๋’ค ๊ฐ€์žฅ ์‰ฝ๊ฒŒ ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ ๊ฐ’์„ ๋ฐ›๋Š” ๊ณผ์ •์„ ๋จผ์ € ๊ตฌํ˜„ ํ•˜๊ณ  split ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•œ String -> Array ๋ณ€ํ™˜ ์ž‘์—…์„ ๋งˆ๋ฌด๋ฆฌ ์ง€์—ˆ๋‹ค.

๋ฌธ์ž์—ด ์ˆ˜์‹์ด ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ ๋œ ์ƒํƒœ์—์„œ ๋ฐฐ์—ด์˜ ์š”์†Œ์— ์ ‘๊ทผ ํ•˜์—ฌ ์ˆซ์ž์™€ ์—ฐ์‚ฐ์ž๋ฅผ ๋ถ„๋ฆฌ ํ•ด์•ผ ํ–ˆ๋Š”๋ฐ, ํŒŒ์ด์ฌ์—์„œ๋Š” type์œผ๋กœ ์‰ฝ๊ฒŒ ๊ฒ€์‚ฌ ํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋‚˜ ์•„์‰ฝ๊ฒŒ ์ž๋ฐ”์—์„œ๋Š” ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ๋Œ€๋ถ€๋ถ„ ์ง์ ‘ ๊ตฌํ˜„ ํ•˜๋Š” ๋‚ด์šฉ์ด๊ฑฐ๋‚˜, Apache์˜ ํŒจํ‚ค์ง€๋ฅผ ์ด์šฉ ํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋Š”๋ฐ ๋ฏธ์…˜์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋’€๋‹ค๋ฉด ๋‹น์—ฐํžˆ ์™ธ๋ถ€ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์† ์‰ฝ๊ฒŒ ํ’€์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ ์ž๋ฐ”์— ์ต์ˆ™ํ•ด์ง€๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ง์ ‘ ๊ตฌํ˜„ํ•œ ์ˆซ์ž ํƒ€์ž… ๊ฒ€์‚ฌ

/**
 * ๋ฌธ์ž๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ˆซ์ž ์—ฌ๋ถ€ ํ™•์ธ
 * @param string ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ
 * @return boolean - true: ์ˆซ์ž, false: ๋ฌธ์ž
 */
public boolean isNumeric(String string) {
    try {
        Integer.parseInt(string);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

์—ฐ์‚ฐ์ž ๋ถ€๋ถ„์€ ํŒŒ์ด์ฌ์„ ์‚ฌ์šฉ ํ•˜๋ฉด์„œ ๋”•์…”๋„ˆ๋ฆฌ์˜ ํ‚ค๋Š” ๋งคํ•‘ ๊ฐ’, ๋ฐธ๋ฅ˜๋Š” ํ•จ์ˆ˜์˜ ์˜ค๋ธŒ์ ํŠธ ์ฃผ์†Œ ๊ฐ’์„ ์ €์žฅ ํ•œ ํ›„ ์‚ฌ์šฉ์„ ๋งŽ์ด ํ•ด ์™”๋Š”๋ฐ ์ž๋ฐ”์—์„œ๋„ ๋žŒ๋‹ค๊ฐ€ ์ง€์›๋˜๊ณ  ์žˆ์–ด ๋น„์Šทํ•˜๊ฒŒ ๊ตฌํ˜„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

// NOTE: ์—ฐ์‚ฐ์ž ์˜ต์…˜
public static final Map<String, BiFunction<Integer, Integer, Integer>> operator = new HashMap<>();
static {
    operator.put("+", (x, y) -> x + y);
    operator.put("-", (x, y) -> x - y);
    operator.put("*", (x, y) -> x * y);
    operator.put("/", (x, y) -> {
        if (y == 0) {
            throw new ArithmeticException("can't division by zero");
        }
        return x / y;
    });
}

์œ„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ํ•˜๋ฉด์„œ ๊ตฌ๊ธ€๋ง์„ ์ •๋ง ๋งŽ์ด ํ–ˆ์—ˆ๋Š”๋ฐ BiFunction์„ ์•Œ๊ธฐ ๊นŒ์ง€ ์‹œ๊ฐ„์„ ์ •๋ง ์˜ค๋ž˜ ์“ด ๊ฒƒ ๊ฐ™๋‹ค. ๋ฌด์•„์ง€๊ฒฝ์œผ๋กœ ๊ฒ€์ƒ‰ ํ•˜๋‹ค๋ณด๋‹ˆ BiFunction ํ‚ค์›Œ๋“œ๋ฅผ ์–ป์—ˆ๊ณ  ํ•ด๋‹น ๋ธ”๋กœ๊ทธ ์— ์žˆ๋Š” ๋‚ด์šฉ์„ ์ฐธ๊ณ ๋กœ ํ•ด์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ํ–ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ”ผ์—ฐ์‚ฐ์ž์™€ ์—ฐ์‚ฐ์ž๋ฅผ ๊ตฌ๋ถ„ ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ด ๋˜์—ˆ์œผ๋‹ˆ ๋ฐ”๋กœ ๋ฌธ์ž์—ด ๊ณ„์‚ฐ๊ธฐ์˜ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ธ ์—ฐ์‚ฐ ๋กœ์ง์„ ์ž‘์„ฑ ํ–ˆ๋‹ค.

๋ฌธ์ž์—ด ์ˆ˜์‹์„ ์—ฐ์‚ฐ ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ ๋ฐ›์•˜๋˜ ๋ฐ์ดํ„ฐ๋Š” ํŒจํ„ด์ด ์กด์žฌํ•˜๋Š”๋ฐ, 0๋ฒˆ์งธ ๋ถ€ํ„ฐ ์ง์ˆ˜ ์ธ๋ฑ์Šค๋Š” ํ”ผ์—ฐ์‚ฐ์ž์ด๊ณ , ํ™€ ์ˆ˜๋Š” ์—ฐ์‚ฐ์ž ๊ธฐํ˜ธ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์ธ๋ฑ์Šค ๋ฒˆํ˜ธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐ˜๋ณต ํ•˜๋ฉด์„œ ์—ฐ์‚ฐ์ž๋งŒ ๋ฝ‘์•„๋„ ๋˜๊ณ , ํ”ผ์—ฐ์‚ฐ์ž๋งŒ ๋ฝ‘์•„๋„ ๋˜๋‹ˆ ์ต์ˆ™ํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด 0๋ฒˆ์งธ ๋ถ€ํ„ฐ ๋ฐ˜๋ณต ํ•  ์ˆ˜ ์žˆ๋Š” ํ”ผ์—ฐ์‚ฐ์ž๋ฅผ ํƒ€๊ฒŸํ–ˆ๋‹ค.

๋ฌธ์ž์—ด ์ˆ˜์‹ ์—ฐ์‚ฐ

/**
 * ๋ฌธ์ž์—ด ์ˆ˜์‹ ๊ณ„์‚ฐ๊ธฐ
 * @param expressionValues `Array`๋กœ ๋ณ€ํ™˜ ๋œ ๋ฌธ์ž์—ด ์ˆ˜์‹์–ด
 * @return ์—ฐ์‚ฐ ๊ฒฐ๊ณผ, ์šฐ์„  ์ˆœ์œ„ ์—†์ด ์™ผ์ชฝ ๋ถ€ํ„ฐ ์—ฐ์‚ฐ ์ˆ˜ํ–‰
 */
public int calculate(String[] expressionValues) {
    if (!isNumeric(expressionValues[0])) {
        throw new IllegalArgumentException("Invalid math expression must be start with number");
    }

    String op = "";
    int expresionResult = Integer.parseInt(expressionValues[0]);
    int operandLast = 0;

    // NOTE: ์ง์ˆ˜ ์ธ๋ฑ์Šค๋Š” ํ•ญ์ƒ ํ”ผ์—ฐ์‚ฐ์ž๋ฉฐ, ํ™€์ˆ˜ ์ธ๋ฑ์Šค๋Š” ํ•ญ์ƒ ์—ฐ์‚ฐ์ž๊ฐ€ ๋˜์–ด์•ผ ์ •์ƒ ์ˆ˜์‹์˜ ํŒจํ„ด์ด ๋จ
    for (int i = 0; i < expressionValues.length - 2; i += 2) {
        String expressionNumber = expressionValues[i + 2];

        if (isNumeric(expressionNumber)) {
            operandLast = Integer.parseInt(expressionValues[i + 2]);
        }

        if (isOperator(expressionValues[i + 1])) {
            op = expressionValues[i + 1];
            // NOTE: ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ๋„˜๊ธฐ๊ธฐ ์œ„ํ•ด `operandFirst` ๋ณ€์ˆ˜์— ๋ฐ˜ํ™˜ ๊ฐ’์„ ํ• ๋‹น
            expresionResult = operator.get(op).apply(expresionResult, operandLast);
        }
    }
    return expresionResult;

์ด์ „์— ์—ฐ์‚ฐ ํ–ˆ๋˜ ๊ฐ’์„ ๋‹ค์Œ ํ”ผ์—ฐ์‚ฐ์ž์™€ ๋‹ค์‹œ ์—ฐ์‚ฐ์„ ํ•ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์žฌ์‚ฌ์šฉ ํ•˜์˜€๊ณ  ์ด๋ ‡๊ฒŒ ๋ฌธ์ž์—ด ์—ฐ์‚ฐ์„ ์ฝ˜์†”๋กœ ํ…Œ์ŠคํŠธ ํ•ด ๋ณด์•˜์„ ๋•Œ ์ •์ƒ์ ์œผ๋กœ ๊ฐ’์ด ๋ฐ˜ํ™˜ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋ฆฌํŒฉํ„ฐ๋ง


์ •์ƒ์ ์ธ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์ฝ”๋“œ๋Š” ์ž‘์„ฑ์ด ์™„๋ฃŒ ๋˜์—ˆ์œผ๋‹ˆ ์ด์ œ ๊ฐ์ฒด๊ฐ€ ๋„๊ณ  ์žˆ๋Š” ํŠน์„ฑ๋“ค์„ ์ด์šฉ ํ•ด์„œ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™, ์—ญํ• ๊ณผ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ ๋“ฑ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก ์— ์žˆ์–ด ์ถ” ํ›„ ํ…Œ์ŠคํŠธ ์‹œ ๋‹จ์ผ ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ๋„๋ก ์œ ๋„ ํ•  ๊ณ„ํš์ด๋ฉฐ ๋ฉ”์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์€ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

์ผ๋‹จ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋‹ด๊ฒจ ์žˆ๋Š” ์„œ๋น„์Šค ํŒŒ์ผ์— ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ๋‹ด๊ฒจ์žˆ๋‹ค. ํ”ผ์—ฐ์‚ฐ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐฉ๋ฒ•, ์—ฐ์‚ฐ์ž ๊ธฐํ˜ธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์—ฐ์‚ฐ ํ•˜๋Š” ๋ฉ”์„œ๋“œ, ๋ฌธ์ž์—ด ์—ฐ์‚ฐ, ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ ๋“ฑ ์„œ๋น„์Šค ๊ณ„์ธต์— ๋ชจ๋‘ ์˜์กดํ•˜๊ฒŒ ๋  ๊ฒƒ ๊ฐ™์œผ๋‹ˆ ๋ถ„๋ฆฌ ์‹œํ‚ฌ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

๐Ÿ˜Ÿ ์—ฐ์‚ฐ์ž๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฐ์ฒด์™€ ํ”ผ์—ฐ์‚ฐ์ž(๋ฌธ์ž์—ด ์ˆ˜์‹์˜ ์ˆ˜)๋Š” ์ธ์Šคํ„ด์Šคํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฐ์ฒด์ผ๊นŒ?

์—ฐ์‚ฐ์ž ๊ธฐํ˜ธ๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๋Š” ์•„๋ฌด๋ž˜๋„ ์œ ํ‹ธ์„ฑ ํด๋ž˜์Šค ๋Š๋‚Œ์ด ๊ฐ•ํ•˜๋‹ค. ๊ทธ ์ด์œ ๋Š” ํ•˜๋‚˜์˜ ์ˆ˜์‹์—๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์—ฐ์‚ฐ ๊ธฐํ˜ธ๋“ค์ด ์žˆ๋Š”๋ฐ, ์ด ๋•Œ ๋งˆ๋‹ค ์—ฐ์‚ฐ ๊ธฐํ˜ธ๋ฅผ ๋„˜๊ฒจ ๋ฐ›์€ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ ํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๋น„ํšจ์œจ์ ์ด๋‹ค. ๊ฒฐ๋ก ์€ ์—ฐ์‚ฐ์ž ๊ธฐํ˜ธ ๊ฐ์ฒด๋Š” ์œ ํ‹ธ์„ฑ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค.

ํ”ผ์—ฐ์‚ฐ์ž๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฐ์ฒด๋Š” ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ์ƒ์ˆ˜ ๊ฐ’์œผ๋กœ ์ €์žฅ ํ•˜๊ณ  ์‚ฌ์šฉ ํ•˜๋ฉด ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ, ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ์—ฌ๋Ÿฌ๋ฒˆ ์ž…๋ ฅ ํ•˜๋Š” ์ƒํ™ฉ์ด ์˜จ๋‹ค๋ฉด ์ด ๋˜ํ•œ ์ˆ˜์‹์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๋ฒˆ ์ธ์Šคํ„ด์Šคํ™”๋ฅผ ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์œ ํ‹ธ์„ฑ์œผ๋กœ ํŒ๋‹จํ•˜์˜€๋‹ค.

๋ถ„๋ฆฌ๋œ ์—ฐ์‚ฐ ๊ธฐํ˜ธ ๊ฐ์ฒด

public class Operator {

    // NOTE: ์—ฐ์‚ฐ์ž ์˜ต์…˜
    private static final Map<String, BiFunction<Integer, Integer, Integer>> operator = new HashMap<>();
    static {
        operator.put("+", (x, y) -> x + y);
        operator.put("-", (x, y) -> x - y);
        operator.put("*", (x, y) -> x * y);
        operator.put("/", (x, y) -> {
            if (y == 0) {
                throw new ArithmeticException("can't division by zero");
            }
            return x / y;
        });
    }

    public static BiFunction<Integer, Integer, Integer> getOperator(String symbol) {
        BiFunction<Integer, Integer, Integer> op = operator.get(symbol);

        if (op == null) {
            throw new IllegalArgumentException("does not matching operator symbol");
        };
        return op;
    }
}

๋ถ„๋ฆฌ๋œ ํ”ผ์—ฐ์‚ฐ์ž ๊ฐ์ฒด

public class StringExpression {

    /**
     * ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
     * @param mathExpression ๋ฌธ์ž์—ด ์ˆ˜์‹
     * @return ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ๊ณต๋ฐฑ์„ ๊ธฐ์ค€์œผ๋กœ `split` ํ•œ ๋ฐฐ์—ด ๋ฐ˜ํ™˜ ๊ฐ’
     */
    public static String[] convertExpressionArraysByBlank(String mathExpression) {
        return mathExpression.split(" ");
    }

    /**
     * ๋ฌธ์ž๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ˆซ์ž ์—ฌ๋ถ€ ํ™•์ธ
     * @param string ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ
     * @return boolean - true: ์ˆซ์ž, false: ๋ฌธ์ž
     */
    public static boolean isNumeric(String string) {
        try {
            Integer.parseInt(string);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

}

์ด๋ ‡๊ฒŒ ๋‹จ์ผ ์ฑ…์ž„์˜ ์›์น™์— ๋”ฐ๋ผ ๊ฐ์ฒด๋ฅผ ๋ถ„๋ฆฌ ํ•˜๊ณ , ์„œ๋กœ๊ฐ€ ์˜์กดํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ตฌ์กฐ๊ฐ€ ๋˜์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์„ค๊ณ„๋ฅผ ํ–ˆ์„ ๋•Œ ํ…Œ์ŠคํŠธ ํ•  ๋•Œ๋„ ํŽธ๋ฆฌํ•˜๋‹ค. ๊ฒฐ๊ตญ ๋‹จ์ผ ๋ฉ”์„œ๋“œ์˜ ์ •์ƒ, ์˜ˆ์™ธ ์ผ€์ด์Šค๋งŒ ๊ฒ€์ฆ ํ•˜๋ฉด ๋์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋ฌธ์ž์—ด ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ ํ•˜๋Š” ์„œ๋น„์Šค ๊ณ„์ธต์€ ์ด์ œ ์—ฐ์‚ฐํ•˜๋Š” ๋ฉ”์„œ๋“œ ํ•˜๋‚˜๋งŒ ์กด์žฌ ํ•œ๋‹ค. ์—ฐ์‚ฐ์ด ํ•„์š”ํ•  ๋•Œ๋งŒ ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌ ํ•˜๋ฉด ๋œ๋‹ค.

๊ตฌ์กฐ๋ฅผ ์žก์€ ๋’ค ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‚˜๋ฆ„ ์˜ˆ์™ธ ์ผ€์ด์Šค๋ฅผ ์„ค์ • ํ–ˆ๋‹ค๊ณ  ํ•œ๋“ค ๋น ์ ธ๋‚˜๊ฐˆ ํ‹ˆ๋“ค์ด ๋ณด์˜€๋‹ค.

  1. ์ˆ˜์˜ ๋‚˜๋ˆ—์…ˆ -> a / b ์ผ ๋•Œ, a๋Š” ์•ž์„œ ์—ฐ์‚ฐ ๋œ ๊ฐ’์„ ๋ฐ›์€ ์ธ์ž์ด๊ธฐ ๋•Œ๋ฌธ์— 0์ด ์•„๋‹์ˆ˜๋„ ์žˆ๊ณ  ๋งž์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๋Œ€์ฒด์ ์œผ๋กœ b๊ฐ€ 0์ธ ๊ฒฝ์šฐ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์˜€๋Š”๋ฐ, a๊ฐ€ 0์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‚ด์šฉ์„ ๋น ํŠธ๋ ธ๋‹ค.

-             if (y == 0) {
->            if (x == 0 || y == 0) {
  1. ๋ฌธ์ž์—ด ์ˆ˜์‹์„ ์ „๋‹ฌ ๋ฐ›์•„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ ํ•˜๋Š” ๊ณผ์ •์€ ๊ณต๋ฐฑ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค. ๋งŒ์•ฝ, ๊ณต๋ฐฑ์ด ์ค‘๊ฐ„์— ์žˆ์ง€๋งŒ ์ˆ˜์‹ ๋‹ค์Œ ๊ณต๋ฐฑ์ด ์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋Š” ์—๋Ÿฌ ์ƒํ™ฉ์„ ๋งŒ๋“ค์–ด์ฃผ๋Š”๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

public static String[] convertExpressionArraysByBlank(String mathExpression) {
    for (int i = 1; i < mathExpression.length(); i += 2) {
        char nextChar = mathExpression.charAt(i);

        if (nextChar != ' ') {
            throw new IllegalArgumentException("Expression must be seperated by a space");
        }
    }

    return mathExpression.split(" ");
}

์ด๋ ‡๊ฒŒ ์ˆซ์ž๋‚˜ ์—ฐ์‚ฐ ๊ธฐํ˜ธ ๋‹ค์Œ ๊ณต๋ฐฑ์ด ์—†๋Š” ๊ฒฝ์šฐ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด ์ฃผ์—ˆ๋‹ค.

  1. ์—ฐ์‚ฐ์ž ๊ธฐํ˜ธ๊ฐ€ ๊ณต๋ฐฑ์„ ๊ตฌ๋ถ„ ํ•˜์—ฌ ์—ฐ์†์œผ๋กœ ์˜ค๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ์—๋Ÿฌ๋กœ ์œ ๋„๋ฅผ ํ•ด์•ผํ•œ๋‹ค.


// NOTE: ์ง์ˆ˜ ์ธ๋ฑ์Šค๋Š” ํ•ญ์ƒ ํ”ผ์—ฐ์‚ฐ์ž๋ฉฐ, ํ™€์ˆ˜ ์ธ๋ฑ์Šค๋Š” ํ•ญ์ƒ ์—ฐ์‚ฐ์ž๊ฐ€ ๋˜์–ด์•ผ ์ •์ƒ ์ˆ˜์‹์˜ ํŒจํ„ด์ด ๋จ
for (int i = 0; i < expressionValues.length - 2; i += 2) {
    String expressionNumber = expressionValues[i + 2];

-    if (isNumeric(expressionNumber)) {
-        operandLast = Integer.parseInt(expressionValues[i + 2]);
-    }

->   if (!StringExpression.isNumeric(expressionNumber)) {
->       throw new ArithmeticException("Math expression must be operated on numeric only");
->   }

    operandLast = Integer.parseInt(expressionValues[i + 2]);
    op = expressionValues[i + 1];

์œ„์—์„œ ์‚ฌ์šฉํ•œ calculate ๋ฉ”์„œ๋“œ์—์„œ ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ๋Š” ์—๋Ÿฌ๋กœ ์œ ๋„ํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ์กด ์†Œ์Šค ์ฝ”๋“œ์—์„œ ์ˆซ์ž๋ฅผ ๊ฒ€์‚ฌํ•œ ํ›„ ์ˆซ์ž์ผ ๋•Œ ํ”ผ์—ฐ์‚ฐ์ž ๊ฐ’์„ ๋„˜๊ฒผ๋Š”๋ฐ ์—ฐ์‚ฐ์ž๊ฐ€ ์—ฐ์†์œผ๋กœ ์˜ค๋Š” ๊ฒฝ์šฐ๋Š” ์ด์ „ ํ• ๋‹น ๋œ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ์—ฐ์‚ฐ์„ ํ•˜๊ฒŒ ๋˜๋‹ˆ ์˜๋„์น˜ ์•Š์€ ๊ฐ’์ด ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž‘์„ฑ


๐Ÿค” ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธ ํ•  ๊ฒƒ์ธ๊ฐ€?

  1. ์œ ํ‹ธ์„ฑ ํด๋ž˜์Šค๊ฐ€ ์ œ๊ณต ํ•˜๋Š” ๋ฉ”์„œ๋“œ์˜ ์ •์ƒ ์ผ€์ด์Šค, ์˜ˆ์™ธ ์ผ€์ด์Šค ๊ฒ€์ฆ

  2. ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๊ฒฐ๊ณผ ๊ฐ’ ๊ฒ€์ฆ

๊ทธ ์™ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„๋„ ํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ฝ˜์†” ํ”„๋กœ์ ํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฉ”์„œ๋“œ ๊ฒ€์ฆ์„ ์œ„์ฃผ๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑ ํ•  ๊ณ„ํš์ด๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ํ•˜๋ฉด ์ข‹์€ ์ด์ ์€ ๊ต‰์žฅํžˆ ๋งŽ์€ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๊ฑธ ์•Œ์ง€๋งŒ ๊ต‰์žฅํžˆ ๋งŽ์€ ์‹œ๊ฐ„์„ ์Ÿ์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ๋†“์น˜๋Š” ์ž‘์—… ์ค‘ ํ•˜๋‚˜์ธ๋ฐ, ๊ทธ ์ค‘ ์ด์ ์„ ๋ฝ‘์•„ ๋ณด์ž๋ฉด ์ด๋Ÿฐ ๋‚ด์šฉ๋“ค์ด ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

  • ์ฝ”๋“œ ๊ตฌํ˜„ ๋‹จ๊ณ„์—์„œ ์ƒ๊ฐ ํ•˜์ง€ ๋ชป ํ–ˆ๋˜ ์˜ˆ์™ธ ์ผ€์ด์Šค

  • ํšŒ๊ท€ ์ผ€์ด์Šค ์˜ˆ๋ฐฉ

  • ์ž˜๋ชป๋œ ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ๊ฐ’

Fixture ์ƒ์„ฑ

public class StringCalculatorTest {

    String stringExpression;
    static StringCalculator service;

    @BeforeAll
    static void serviceInit() {
        service = new StringCalculator();
    }

    @BeforeEach
    void setUp() {
        stringExpression = "2 + 3 * 4 / 2";
    }
    ...

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์‹คํ–‰ ์‹œํ‚ฌ ๋•Œ ๋งˆ๋‹ค ์‚ฌ์šฉ ํ•ด์•ผ ํ•  ๋ฌธ์ž์—ด ์ˆ˜์‹๊ณผ, ์„œ๋น„์Šค ํด๋ž˜์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™” ํ•จ์œผ๋กœ ์จ ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ฌธ์ž์—ด ์ˆ˜์‹๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๋ช…ํ™•ํ•œ Given์„ ์ฃผ๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๋‚ด๋ถ€์—์„œ ์ž‘์„ฑํ•ด๋„ ์ƒ๊ด€ ์—†์„ ๊ฒƒ ๊ฐ™๋‹ค.

์œ ํ‹ธ ํด๋ž˜์Šค ๊ฒ€์ฆ

    @ParameterizedTest
    @DisplayName("`StringExpression` ์ˆซ์ž ์—ฌ๋ถ€ ๊ฒ€์ฆ")
    @CsvSource(value = {"1:true", "+:false"}, delimiter = ':')
    void ์ˆซ์ž_์—ฌ๋ถ€_๊ฒ€์ฆ(String expectedNumericValue, boolean expectedResult) {
        // Given
        String stringExpressionValue = expectedNumericValue;
        boolean expect = expectedResult;

        // When
        boolean result = StringExpression.isNumeric(stringExpressionValue);

        // Then
        assertThat(result).isEqualTo(expect);
    }

ํŒŒ์ด์ฌ์—์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์„ ๊ตญ๋ฌธ์œผ๋กœ ์ง“๋Š”๊ฑธ ์„ ํ˜ธํ•˜๋Š” ํŽธ์ธ๋ฐ, ์ž๋ฐ”์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์‚ฌ์šฉ ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ DisplayName์ด ์žˆ๋‹ค๋ฉด ๊ตณ์ด ๋ฉ”์„œ๋“œ๋ฅผ ๊ตญ๋ฌธ์œผ๋กœ ํ•ด์•ผ ๋์„๊นŒ๋ž€ ์ƒ๊ฐ๋„ ๋“ค์—ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•์€ Given-When-Then ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ๋žŒ์ด ์ดํ•ดํ•˜๊ธฐ ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ ํ–ˆ๊ณ , ๋Œ€๋ถ€๋ถ„์˜ ์ผ€์ด์Šค๋„ ๊ทธ๋ ‡๊ฒŒ ์‚ฌ์šฉ ํ•ด ์™”๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ฃผ์–ด์ง„ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋– ํ•œ ํ–‰๋™์„ ํ–ˆ์„ ๋•Œ ์ด๋Ÿฌํ•œ ๋ฐ˜์‘์„ ๋ณด์ผ ๊ฒƒ์ด๋‹ค ๋ผ๋Š”๊ฒŒ ์›Œํฌํ”Œ๋กœ์šฐ ์ž์ฒด๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ์† ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ์—์„œ ์ •์ƒ, ์ด์ƒ ๊ฒ€์ฆ์„ ํ•  ๋•Œ ParameterizedTest ๋ฅผ ์ด์šฉ ํ•˜๋ฉด ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆ ํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— 1๊ฐœ์˜ ๋ฉ”์„œ๋“œ์—์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ƒํ™ฉ์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

์—๋Ÿฌ ์ผ€์ด์Šค ๊ฒ€์ฆ


    @ParameterizedTest
    @DisplayName("`Operator` ์—ฐ์‚ฐ์ž ๋‚˜๋ˆ—์…ˆ ์˜ˆ์™ธ ์ผ€์ด์Šค ๊ฒ€์ฆ")
    @CsvSource({"0,1", "1,0"})
    void ์—ฐ์‚ฐ์ž_๋‚˜๋ˆ—์…ˆ_์˜ˆ์™ธ_๊ฒ€์ฆ(int num1, int num2) {
        // Given
        String op = "/";

        // When
        assertThatThrownBy(() -> {
            Operator.getOperator(op).apply(num1, num2);
        })      // Then
                .isInstanceOf(ArithmeticException.class)
                .hasMessageContaining("can't division by zero");
    }

์—๋Ÿฌ ์ผ€์ด์Šค๋ฅผ ๊ฒ€์ฆ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์˜๋„์ ์œผ๋กœ ๋‚ด๊ฐ€ ํ•ด๋‹น ์ผ€์ด์Šค๋ฅผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ–ˆ๋‹ค๋Š” ๋‚ด์šฉ๊ณผ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ ์˜๋„์ ์ด์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ด๋ ‡๊ฒŒ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚˜ ์„œ๋น„์Šค๊ฐ€ ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์„ ๋ฏธ์—ฐ์— ๋ฐฉ์ง€ ํ•  ์ˆ˜ ์žˆ๋‹ค. Mission1 - ํ•™์Šต ํ…Œ์ŠคํŠธ ์‹ค์Šต ๊ณผ์ •์—์„œ ์‚ฌ์šฉ ํ–ˆ๋˜ Exceptionํด๋ž˜์Šค ๊ฒ€์ฆ + ErrorMessage ๊ฒ€์ฆ์„ ํ†ตํ•ด ํ•ด๋‹น ์—๋Ÿฌ ์ผ€์ด์Šค๋ฅผ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ์ด์œ ๋Š” Throw ๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์ง€๋งŒ Try-Catch์—์„œ ํ•ด๋‹น ์—๋Ÿฌ ์ƒํ™ฉ์ด ์•ˆ ์žกํžˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฒ€์ฆ

    @Test
    @DisplayName("`StingCalculator` ๋ฌธ์ž์—ด ์—ฐ์‚ฐ ๊ฒ€์ฆ")
    void ๋ฌธ์ž์—ด_์—ฐ์‚ฐ_๊ฒ€์ฆ() {
        // Given
        String[] fixture = StringExpression.convertExpressionArraysByBlank(stringExpression);

        // When
        int result = service.calculate(fixture);

        // Then
        assertThat(result).isEqualTo(10);
    }

์œ„์—์„œ ์œ ํ‹ธ์„ฑ ๋ฉ”์„œ๋“œ๋“ค์„ ํ…Œ์ŠคํŠธ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์ฃผ์–ด์ง„ ๋ฌธ์ž์—ด ์ˆ˜์‹์ด ์›ํ•˜๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค. ๊ทธ ๊ณผ์ •์—์„œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ParameterizedTest๋กœ ๋„ฃ์–ด ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ์กฐ๊ธˆ ๋” ์š”๊ตฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ผ๋ฐ˜์ ์ธ ์ผ€์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด ์ˆ˜์‹๋งŒ ๊ฒ€์ฆ ํ–ˆ๋‹ค.

Last updated