DevLog ๐Ÿ˜ถ

[Intellij] ์•ˆ์ „ํ•œ ๋ฆฌํŒฉํ„ฐ๋ง ์ง„ํ–‰ํ•˜๊ธฐ - Intellij๋ฅผ ํ™œ์šฉํ•œ ์ ์ง„์  ๋ฆฌํŒฉํ„ฐ๋ง ๋ณธ๋ฌธ

๊ฐœ๋ฐœ์ผ์ง€

[Intellij] ์•ˆ์ „ํ•œ ๋ฆฌํŒฉํ„ฐ๋ง ์ง„ํ–‰ํ•˜๊ธฐ - Intellij๋ฅผ ํ™œ์šฉํ•œ ์ ์ง„์  ๋ฆฌํŒฉํ„ฐ๋ง

dolmeng2 2024. 2. 25. 16:04

๐ŸŒฑ ๋“ค์–ด๊ฐ€๊ธฐ ์ „

๋‚˜๋Š” ํ‰์†Œ์— ๋งˆ์šฐ์Šค๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ, ๋งฅ๋ถ ํ„ฐ์น˜ํŒจ๋“œ๋ฅผ ์ •๋ง ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด์—ˆ์–ด์„œ ๊ฐœ๋ฐœํ•  ๋•Œ ๋‹จ์ถ•ํ‚ค๋ฅผ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํŽธ์ด์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค ๋ณด๋‹ˆ ์ž๋™์œผ๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ์†๋„ ์ฐจ์ด๊ฐ€ ๋‚ฌ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์— ๋‹จ์ถ•ํ‚ค๋„ ๊ณต๋ถ€ํ• ๊ฒธ, ๋ฆฌํŒฉํ„ฐ๋ง ์‹œ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด Intellij ๋ฅผ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์Šคํ„ฐ๋”” ํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ๋‹ค. (๊ฐ“๋‚˜๋‹ˆ๊ฐœ๋ฐœ์ž๋‹˜ ๋•๋ถ„์— ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์ด์—ˆ๋‹ค ใ…Ž_ใ…Ž)

 

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

 

์–ด๋–ค ์‹์œผ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง์„ ํ–ˆ๋Š”์ง€๋Š” ์•„๋ž˜์˜ ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ์— ์ปค๋ฐ‹๋ณ„๋กœ ๋‚˜ํƒ€๋‚ด์—ˆ๋‹ค.

 

GitHub - Cl8D/kotlin-expense: rafactoring practice

rafactoring practice. Contribute to Cl8D/kotlin-expense development by creating an account on GitHub.

github.com

main ๋ธŒ๋žœ์น˜์˜ ๊ฒฝ์šฐ ๋ฆฌํŒฉํ„ฐ๋ง ์ด์ „์˜ ์ฝ”๋“œ์ด๊ณ , refactoring ๋ธŒ๋žœ์น˜๋ฅผ ๊ฐ€๋ฉด ์ ์ง„์  ๋ฆฌํŒฉํ„ฐ๋ง ๊ณผ์ •์„ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ˜น์‹œ ํ•„์š”ํ•œ ๋ถ„์ด ๊ณ„์‹œ๋‹ค๋ฉด ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

 


 

๐ŸŒฑ ์›๋ณธ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์  

์šฐ์„ , ๋ฆฌํŒฉํ„ฐ๋ง์ด ๋˜๊ธฐ ์ด์ „์˜ ์ƒ˜ํ”Œ ์ฝ”๋“œ์ด๋‹ค.

class Expense(
    var type: Type,
    var amount: Int
) {
    enum class Type {
        DINNER,
        BREAKFAST,
        CAR_RENTAL
    }
}
interface ReportPrinter {
    fun print(text: String?)
}

class ExpenseReport {
    private val expenses: MutableList<Expense> = ArrayList()
    private val date: String
        get() = "9/12/2002"
    
    fun printReport(printer: ReportPrinter) {
        var total = 0
        var mealExpenses = 0
        printer.print("Expenses " + date + "\\n")

        for (expense in expenses) {
            if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
                mealExpenses += expense.amount
            }
            var name = "TILT"

            when (expense.type) {
                Expense.Type.DINNER -> name = "Dinner"
                Expense.Type.BREAKFAST -> name = "Breakfast"
                Expense.Type.CAR_RENTAL -> name = "Car Rental"
            }

            printer.print(
                String.format(
                    "%s\\t%s\\t$%.02f\\n",
                    if (expense.type == Expense.Type.DINNER
                        && expense.amount > 5000
                        || expense.type === Expense.Type.BREAKFAST
                        && expense.amount > 1000) "X" else " ",
                    name, expense.amount / 100.0
                )
            )
            total += expense.amount
        }
        printer.print(String.format("\\nMeal expenses $%.02f", mealExpenses / 100.0))
        printer.print(String.format("\\nTotal $%.02f", total / 100.0))
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

 

์•ž์œผ๋กœ ์œ„์˜ ์ฝ”๋“œ๊ฐ€ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋Œ€๋กœ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋„๋ก, ์•„๋ž˜์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

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

class MockReportPrinter : ReportPrinter {
    private var printedText = ""

    override fun print(text: String?) {
        printedText += text

    }

    fun getText(): String {
        return printedText
    }
}

internal class ExpenseReportTest : StringSpec({
    lateinit var report: ExpenseReport
    lateinit var printer: MockReportPrinter

    beforeTest {
        report = ExpenseReport()
        printer = MockReportPrinter()
    }

    "printEmpty" {
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002

            Meal expenses $0.00
            Total $0.00
        """.trimIndent()
    }

    "printOneDinner" {
        report.addExpense(Expense(Expense.Type.DINNER, 1678))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$16.78

            Meal expenses $16.78
            Total $16.78
        """.trimIndent()
    }

    "twoMeals" {
        report.addExpense(Expense(Expense.Type.DINNER, 1000))
        report.addExpense(Expense(Expense.Type.BREAKFAST, 500))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$10.00
             	Breakfast	$5.00

            Meal expenses $15.00
            Total $15.00
        """.trimIndent()
    }

    "twoMealsAndCarRental" {
        report.addExpense(Expense(Expense.Type.DINNER, 1000))
        report.addExpense(Expense(Expense.Type.BREAKFAST, 500))
        report.addExpense(Expense(Expense.Type.CAR_RENTAL, 50000))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$10.00
             	Breakfast	$5.00
             	Car Rental	$500.00

            Meal expenses $15.00
            Total $515.00
        """.trimIndent()
    }

    "overages" {
        report.addExpense(Expense(Expense.Type.BREAKFAST, 1000))
        report.addExpense(Expense(Expense.Type.BREAKFAST, 1001))
        report.addExpense(Expense(Expense.Type.DINNER, 5000))
        report.addExpense(Expense(Expense.Type.DINNER, 5001))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Breakfast	$10.00
            X	Breakfast	$10.01
             	Dinner	$50.00
            X	Dinner	$50.01

            Meal expenses $120.02
            Total $120.02
        """.trimIndent()
    }
})

 

 

๊ธฐ์กด์˜ ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ์ ์ด ์กด์žฌํ•œ๋‹ค.

1. ExpenseReport ๊ฐ€ ํ•˜๊ณ  ์žˆ๋Š” ์ผ์ด ๋„ˆ๋ฌด ๋งŽ์Œ
2. ๋น„์šฉ์— ๋Œ€ํ•œ ๊ณ„์‚ฐ์„ ํ•˜๋Š” ํ•จ์ˆ˜์™€ ์ถœ๋ ฅ์— ๋Œ€ํ•œ ํ•จ์ˆ˜๊ฐ€ ํ•ฉ์ณ์ ธ ์žˆ์–ด์„œ ๊ธฐ๋Šฅ ์ž์ฒด์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’์Œ
3. ๋น„์šฉ์— ๋Œ€ํ•œ ๋ณ€์ˆ˜์˜ ์Šค์ฝ”ํ”„๊ฐ€ printReport() ํ•จ์ˆ˜์˜ ์ง€์—ญ ๋ณ€์ˆ˜๋กœ ์„ ์–ธ๋˜์–ด ์žˆ์–ด, ๊ฐœ๋ฐœ์ž๊ฐ€ ์ธ์ง€ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›€.
4. ๋น„์šฉ์— ๋Œ€ํ•œ ํƒ€์ž…์ด ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ ํ™•์žฅํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›€

 

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

 


 

๐ŸŒฑ Step1 - ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ

fun printReport(printer: ReportPrinter) {
    var total = 0
    var mealExpenses = 0
    printer.print("Expenses " + date + "\\n")

    for (expense in expenses) {
        if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
            mealExpenses += expense.amount
        }
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }

        printer.print(
            String.format(
                "%s\\t%s\\t$%.02f\\n",
                if (expense.type == Expense.Type.DINNER
                    && expense.amount > 5000
                    || expense.type === Expense.Type.BREAKFAST
                    && expense.amount > 1000) "X" else " ",
                name, expense.amount / 100.0
            )
        )
        total += expense.amount
    }
    printer.print(String.format("\\nMeal expenses $%.02f", mealExpenses / 100.0))
    printer.print(String.format("\\nTotal $%.02f", total / 100.0))
}

 

๋จผ์ €, ์œ„ ํ•จ์ˆ˜์—์„œ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์„ ์ตœ๋Œ€ํ•œ ์ถ”์ถœํ•˜์—ฌ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด๋ณด์ž.

 

์šฐ๋ฆฌ๋Š” ์œ„์˜ ํ•จ์ˆ˜๋ฅผ ํฌ๊ฒŒ 3๊ฐ€์ง€ ๋ถ€๋ถ„์œผ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

1. ํ—ค๋” ์ •๋ณด ์ถœ๋ ฅํ•˜๊ธฐ
2. ๋น„์šฉ์„ ๊ณ„์‚ฐํ•˜๊ณ  ์ค‘๊ฐ„ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ
3. ์ตœ์ข… ๋น„์šฉ์„ ์ถœ๋ ฅํ•˜๊ธฐ

 

๋น„๊ต์  ๋ณต์žกํ•œ 2๋ฒˆ ๋‚ด์šฉ์„ ์šฐ์„  ๋ฐฐ์ œํ•˜๊ณ , 1๋ฒˆ๊ณผ 3๋ฒˆ ๋‚ด์šฉ์„ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ์ˆ˜๋กœ ์ถ”์ถœํ•˜์ž.

์ด๋•Œ, 1๋ฒˆ ๋‚ด์šฉ์—์„œ total, mealExpenses ๋Š” ํ—ค๋” ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์ •๋ณด๋Š” ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ ์–ธ๋œ ๋ณ€์ˆ˜๋ฅผ 2๋ฒˆ ๋ถ€๋ถ„์—๊ฒŒ ๋‚ด๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, 3๋ฒˆ ๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•ด total, mealExpenses๋ฅผ ๋„˜๊ฒจ์ฃผ๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํฌ๊ฒŒ ๋ฌด๋ฆฌ๊ฐ€ ์—†๋‹ค.

 

command + option + m (extract method) ์„ ํ†ตํ•ด ๊ฐ๊ฐ์„ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•˜์ž.

๊ทธ๋Ÿผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ถ„๋ฆฌ๊ฐ€ ๋˜์—ˆ์„ ํ…๋ฐ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด์„œ ์•„์ง ๊ธฐ๋Šฅ์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ์ฒดํฌํ•˜์ž.

 

fun printReport(printer: ReportPrinter) {
    printHeader(printer)
    var total = 0
    var mealExpenses = 0

    for (expense in expenses) {
        if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
            mealExpenses += expense.amount
        }
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }

        printer.print(
            String.format(
                "%s\\t%s\\t$%.02f\\n",
                if (expense.type == Expense.Type.DINNER
                    && expense.amount > 5000
                    || expense.type === Expense.Type.BREAKFAST
                    && expense.amount > 1000) "X" else " ",
                name, expense.amount / 100.0
            )
        )
        total += expense.amount
    }
    printTotal(printer, mealExpenses, total)
}

private fun printTotal(printer: ReportPrinter, mealExpenses: Int, total: Int) {
    printer.print(String.format("\\nMeal expenses $%.02f", mealExpenses / 100.0))
    printer.print(String.format("\\nTotal $%.02f", total / 100.0))
}

private fun printHeader(printer: ReportPrinter) {
    printer.print("Expenses " + date + "\\n")
}

 

ํ…Œ์ŠคํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผํ•œ๋‹ค๋ฉด, ์•ˆ์‹ฌํ•˜๊ณ  ๋‹ค์Œ ์Šคํ…์œผ๋กœ ๋„˜์–ด๊ฐ€๋ณด์ž.

 


 

๐ŸŒฑ Step 2 - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์ถœ๋ ฅ์„ ๋ถ„๋ฆฌํ•˜์ž

์ด ๋‹ค์Œ์—๋Š” 2๋ฒˆ ๋ถ€๋ถ„์„ ๋ฟŒ์‹ค ์ฐจ๋ก€์ด๋‹ค. ํ•ด๋‹น ๋ถ€๋ถ„์€ ์ถœ๋ ฅ๊ณผ ๊ณ„์‚ฐ์ด ํ•จ๊ป˜ ํ˜ผํ•ฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„๋ฆฌํ•˜๊ธฐ๊ฐ€ ํ˜„์žฌ๋กœ์„œ๋Š” ์–ด๋ ค์šฐ๋‹ˆ, ๋ฐ˜๋ณต๋ฌธ์„ 2๊ฐœ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์ถœ๋ ฅ๊ณผ ๊ณ„์‚ฐ์„ ๋ถ„๋ฆฌํ•ด๋ณด์ž.

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ถ„๋ฆฌ ํ›„์—๋„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด์ž. ์ž˜ ํ†ต๊ณผํ•˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

์ฐธ๊ณ ๋กœ, ์ฝ”๋“œ ๋ธ”๋ก์„ ์„ ํƒํ•  ๋•Œ option + ๋ฐฉํ–ฅํ‚ค ์œ—ํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด ๋ธ”๋ก๋ณ„๋กœ ํ•œ ๋ฒˆ์— select๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ„ฐ์น˜ํŒจ๋“œ๋‚˜ ๋งˆ์šฐ์Šค๋ฅผ ์“ฐ์ง€ ์•Š๊ณ ๋„ ๋ธ”๋ก์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ํ‚ค์ด๋‹ค.

fun printReport(printer: ReportPrinter) {
    printHeader(printer)
    var total = 0
    var mealExpenses = 0

    for (expense in expenses) {
        if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    for (expense in expenses) {
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }

        printer.print(
            String.format(
                "%s\\t%s\\t$%.02f\\n",
                if (expense.type == Expense.Type.DINNER
                    && expense.amount > 5000
                    || expense.type === Expense.Type.BREAKFAST
                    && expense.amount > 1000) "X" else " ",
                name, expense.amount / 100.0
            )
        )
    }

    printTotal(printer, mealExpenses, total)
}

 

๊ทธ๋ ‡๋‹ค๋ฉด, ์ด์ œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด๋ณด์ž.

์ด๋•Œ, Intellij์˜ ๋„์›€์„ ๋ฐ›๋Š”๋‹ค๋ฉด ๊ฝค๋‚˜ ์ด์ƒํ•œ ํ˜•ํƒœ๋กœ ๋ถ„๋ฆฌ๋ฅผ ํ•ด์ฃผ๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

private fun calculateExpense(): Pair<Int, Int> {
    var total = 0
    var mealExpenses = 0

    for (expense in expenses) {
        if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }
    return Pair(total, mealExpenses)
}

 

์œ„์™€ ๊ฐ™์ด Pair๋ฅผ ํ†ตํ•ด์„œ ๋ถ„๋ฆฌ๋ฅผ ํ•ด์ฃผ๋ ค๊ณ  ํ•œ๋‹ค. Pair์˜ ๊ฒฝ์šฐ first, second ๋ฅผ ํ†ตํ•ด์„œ ๊ฐ๊ฐ์˜ ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฝ๋Š” ์‚ฌ๋žŒ์˜ ์ž…์žฅ์—์„œ ๊ฐ ๋ณ€์ˆ˜๊ฐ€ ๋ฌด์—‡์„ ๋œปํ•˜๋Š”์ง€ ์ธ์ง€ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.

๊ทธ๋ž˜์„œ, ์ด ๋ฐฉ๋ฒ• ๋Œ€์‹ ์— total, mealExpenses์˜ ๋ณ€์ˆ˜๋ฅผ ํด๋ž˜์Šค์˜ ๋ณ€์ˆ˜๋กœ ์˜ฎ๊ฒจ์„œ, ์‹ค์งˆ์ ์œผ๋กœ ํ•ด๋‹น ํ•จ์ˆ˜์—๋Š” ํด๋ž˜์Šค์˜ ๋ณ€์ˆ˜๋ฅผ ์ ‘๊ทผํ•˜์—ฌ ๊ฐ’์„ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด๋ณด์ž.

 

์ด๋•Œ, ์ž๋ฐ”์—์„œ๋Š” ํ•ด๋‹น ๋ณ€์ˆ˜์— ์ปค์„œ๋ฅผ ๋Œ€๊ณ  command + option + F (extract field)๋ฅผ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๋‹น ๋ณ€์ˆ˜๋ฅผ ์–ด๋””๋กœ ์˜ฎ๊ธธ์ง€ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฝ์ด ๋œจ๊ฒŒ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ, ์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” var ๋ณ€์ˆ˜์˜ ๊ฒฝ์šฐ ์ž˜ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์„œ ์ˆ˜๋™์œผ๋กœ ์˜ฎ๊ฒจ์ฃผ์—ˆ๋‹ค. (val ๋ณ€์ˆ˜๋ฉด ๊ดœ์ฐฎ์„ ๊ฒƒ ๊ฐ™์€๋ฐ, var ๋ณ€์ˆ˜์—ฌ์„œ ์ž˜ ์ ์šฉ์ด ์•ˆ ๋œ ๊ฒƒ ๊ฐ™์•„์„œ ์•„์‰ฝ๋‹ค.)

class ExpenseReport {  
    private var total = 0
    private var mealExpenses = 0
		....
}

 

์ด์ œ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•ด์„œ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌ๋ฅผ ํ•ด๋ณด์ž. ๋น„์šฉ์— ๋Œ€ํ•œ ๊ณ„์‚ฐ์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์œผ๋‹ˆ, calculateExpense ๋ผ๋Š” ์ด๋ฆ„์„ ๋ถ™์˜€๋‹ค.

private fun calculateExpenses() {
    for (expense in expenses) {
        if (expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }
}

 

์ด ํ•จ์ˆ˜๋Š” ํ˜„์žฌ if๋ฌธ์ด ํ•˜๋Š” ์ผ์„ ์ •ํ™•ํ•˜๊ฒŒ ์•Œ๊ธฐ ์–ด๋ ค์šฐ๋‹ˆ, ํ•ด๋‹น ๋ถ€๋ถ„๋„ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด๋ณด์ž.

๋น„์šฉ์˜ ํƒ€์ž…์ด ์•„์นจ์ด๋‚˜ ์ €๋…์ธ ๊ฒฝ์šฐ mealExpenses๋ฅผ ๋”ํ•˜๊ณ  ์žˆ์œผ๋‹ˆ, ๊ฐ„๋‹จํ•˜๊ฒŒ isMeal ์ด๋ผ๋Š” ๋„ค์ด๋ฐ์„ ๋ถ™์—ฌ์ฃผ์—ˆ๋‹ค.

private fun calculateExpenses() {
    for (expense in expenses) {
        if (isMeal(expense)) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }
}

private fun isMeal(expense: Expense) = expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER

 

์—ฌ์ „ํžˆ ํ•จ์ˆ˜๊ฐ€ 2 depth ์—ฌ์„œ ์•Œ์•„๋ณด๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค. ํ•œ ๋ฒˆ ๋” ๋ถ„๋ฆฌ๋ฅผ ํ•ด์ฃผ์ž.

private fun calculateExpenses() {
    for (expense in expenses) {
        addTotal(expense)
    }
}

private fun addTotal(expense: Expense) {
    if (isMeal(expense)) {
        mealExpenses += expense.amount
    }
    total += expense.amount
}

 

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์ถœ๋ ฅ์— ๋Œ€ํ•œ ๋ถ€๋ถ„๋„ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด๋ณด์ž.

private fun printExpenses(printer: ReportPrinter) {
    for (expense in expenses) {
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }

        printer.print(
            String.format(
                "%s\t%s\t$%.02f\n",
                if (expense.type == Expense.Type.DINNER
                    && expense.amount > 5000
                    || expense.type === Expense.Type.BREAKFAST
                    && expense.amount > 1000
                ) "X" else " ",
                name, expense.amount / 100.0
            )
        )
    }
}

 

์œ„ ํ•จ์ˆ˜์—์„œ๋„ ํฌ๊ฒŒ 2๊ฐ€์ง€๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

1. ๋น„์šฉ์— ๋Œ€ํ•œ ์ด๋ฆ„์„ ๊ณ„์‚ฐํ•˜๊ธฐ
2. ์‹ค์งˆ์ ์œผ๋กœ ๋น„์šฉ์— ๋Œ€ํ•œ ์ด๋ฆ„์„ ์ถœ๋ ฅํ•˜๊ธฐ

 

์ด์— ๋”ฐ๋ผ์„œ ํ•จ์ˆ˜๋ฅผ ๋˜ 2๊ฐœ๋กœ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค.

์ด๋•Œ, name์˜ ๊ฒฝ์šฐ ํ•จ์ˆ˜๋กœ๋ถ€ํ„ฐ ์–ป์–ด์˜ค๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ด์ƒ var ๋กœ ์žˆ์„ ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— val๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ๋‹ค.

private fun printExpenses(printer: ReportPrinter) {
    for (expense in expenses) {
        val name = getName(expense)

        printer.print(
            String.format(
                "%s\\t%s\\t$%.02f\\n",
                if (expense.type == Expense.Type.DINNER
                    && expense.amount > 5000
                    || expense.type === Expense.Type.BREAKFAST
                    && expense.amount > 1000
                ) "X" else " ",
                name, expense.amount / 100.0
            )
        )
    }
}

private fun getName(expense: Expense): String {
    var name = "TILT"

    when (expense.type) {
        Expense.Type.DINNER -> name = "Dinner"
        Expense.Type.BREAKFAST -> name = "Breakfast"
        Expense.Type.CAR_RENTAL -> name = "Car Rental"
    }
    return name
}

 

์—ฌ๊ธฐ๊นŒ์ง€ ์ง„ํ–‰ํ•˜๊ณ , ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ค‘๊ฐ„์ค‘๊ฐ„ ์ฒดํฌ๋ฅผ ์ง„ํ–‰ํ•ด์ฃผ์ž.

 


 

๐ŸŒฑ Step3 - ๋ถˆํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐํ•˜๊ธฐ

Step 1, 2๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๋ฉ”์ธ์ด ๋˜๋ฉด public ํ•จ์ˆ˜์ธ printReport() ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

class ExpenseReport {
    private val expenses: MutableList<Expense> = ArrayList()
    private val date: String
        get() = "9/12/2002"

    private var total = 0
    private var mealExpenses = 0

    fun printReport(printer: ReportPrinter) {
        printHeader(printer)
        calculateExpenses()
        printExpenses(printer)
        printTotal(printer, mealExpenses, total)
    }

    private fun printExpenses(printer: ReportPrinter) {
        for (expense in expenses) {
            val name = getName(expense)

            printer.print(
                String.format(
                    "%s\t%s\t$%.02f\n",
                    if (expense.type == Expense.Type.DINNER
                        && expense.amount > 5000
                        || expense.type === Expense.Type.BREAKFAST
                        && expense.amount > 1000
                    ) "X" else " ",
                    name, expense.amount / 100.0
                )
            )
        }
    }

    private fun getName(expense: Expense): String {
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    private fun calculateExpenses() {
        for (expense in expenses) {
            addTotal(expense)
        }
    }

    private fun addTotal(expense: Expense) {
        if (isMeal(expense)) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    private fun isMeal(expense: Expense) = expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER

    private fun printTotal(printer: ReportPrinter, mealExpenses: Int, total: Int) {
        printer.print(String.format("\nMeal expenses $%.02f", mealExpenses / 100.0))
        printer.print(String.format("\nTotal $%.02f", total / 100.0))
    }

    private fun printHeader(printer: ReportPrinter) {
        printer.print("Expenses " + date + "\n")
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

 

ํฌ๊ฒŒ ํ—ค๋”๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ / ๋น„์šฉ์„ ๊ณ„์‚ฐํ•˜๊ธฐ / ๋น„์šฉ์„ ์ถœ๋ ฅํ•˜๊ธฐ / ์ด ํ•ฉ๊ณ„๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ, ์ด๋ ‡๊ฒŒ 4๊ฐ€์ง€๋กœ ๋ถ„๋ฆฌ๋˜์—ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜, ํ•จ์ˆ˜๋กœ ์ถ”์ถœํ•˜๋ฉด์„œ parameter drilling ์œผ๋กœ ์ธํ•ด ํ•„์š”๊ฐ€ ์—†์Œ์—๋„ ์ธ์ž๋กœ ๊ณ„์† ๋„˜๊ฒจ์ฃผ๋Š” ๋ถ€๋ถ„๋“ค์ด ๋ˆˆ์— ๋“ค์–ด์˜ฌ ๊ฒƒ์ด๋‹ค.

์ด๋ฒˆ ๋‹จ๊ณ„์—์„œ๋Š” ํ•ด๋‹น ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์ œ๊ฑฐํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

 

๊ฐ€์žฅ ๋ˆˆ์— ๋„๋Š” ๊ฒƒ์€ ReportPrinter ๊ฐ€ ๊ณ„์†ํ•ด์„œ ์ธ์ž๋กœ ๋„˜์–ด๊ฐ€๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.

ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ReportPrinter์— ๋Œ€ํ•ด ํด๋ž˜์Šค์˜ ๋ณ€์ˆ˜๋กœ ์Šน๊ฒฉ์‹œํ‚ค๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•ด๋ณด์ž.

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž๋ฐ” ์ฝ”๋“œ์˜€๋‹ค๋ฉด command + option + F ๋ฅผ ํ†ตํ•ด์„œ ์ž˜ ์ถ”์ถœ์ด ๋˜์ง€๋งŒ, ์ฝ”ํ‹€๋ฆฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ์šฐ์„  ์ˆ˜๊ธฐ๋กœ ์ถ”์ถœ์„ ์ง„ํ–‰ํ•ด์ฃผ์—ˆ๋‹ค. ์ด๋•Œ, printer์˜ ๊ฒฝ์šฐ NPE ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด์„œ lateinit ์„ ํ†ตํ•ด ์ง€์—ฐ ์ดˆ๊ธฐํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

private lateinit var printer: ReportPrinter

fun printReport(printer: ReportPrinter) {
    this.printer = printer
    printHeader(printer)
    calculateExpenses()
    printExpenses(printer)
    printTotal(printer, mealExpenses, total)
}

 

 

์ด๋Ÿฌ๋ฉด, ํด๋ž˜์Šค์˜ ํ•„๋“œ ๋ ˆ๋ฒจ์—์„œ printer๊ฐ€ ์„ ์–ธ์ด ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ด์ƒ ์ธ์ž๋กœ ๊ณ„์† ๋„˜๊ฒจ์ค„ ํ•„์š”๊ฐ€ ์—†์–ด์ง„๋‹ค.

์ž๋ฐ”๋ผ๋ฉด ๋Œ€์ƒ์ด ๋˜๋Š” ํ•จ์ˆ˜์—์„œ command + option + n (Inline Parameter) ์„ ๋ˆ„๋ฅด๋ฉด ์œ„์™€ ๊ฐ™์ด ํด๋ž˜์Šค์˜ ๋ณ€์ˆ˜๋กœ ์„ ์–ธ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋„๋ก ์‰ฝ๊ฒŒ ๋ฆฌํŒฉํ„ฐ๋ง์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

ํ•˜์ง€๋งŒ, ์ฝ”ํ‹€๋ฆฐ์ด๋ผ์„œ ๊ทธ๋Ÿฐ์ง€ ์ž˜ ๋™์ž‘ํ•˜์ง€ ์•Š์•„ command + F6 (Change Signature) ๋ฅผ ํ†ตํ•ด์„œ ์ธ์ž๋กœ ๋„˜์–ด์˜จ printer ๋ฅผ ์ œ๊ฑฐํ•ด์ฃผ์—ˆ๋‹ค. printer๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š” ํ•จ์ˆ˜๋“ค์— ๋Œ€ํ•ด์„œ ๋ชจ๋‘ ์ ์šฉํ•ด๋ณด๋„๋ก ํ•˜์ž.

 

์ฐธ๊ณ ๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ ์ง€์šธ ๋•Œ๋Š” command + backspace๋ฅผ ํ™œ์šฉํ•˜๊ณ  command + enter๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด ์ ์šฉ๋œ๋‹ค. (์ด๋Ÿฐ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋„ ํ„ฐ์น˜ํŒจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋…ธ๋ ฅ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.)

์ด ๊ณผ์ •์—์„œ conflict view๊ฐ€ ๋‚˜์˜ค๋ฉด continue๋กœ ํƒ€๊ฒŸ์„ ์˜ฎ๊ธด ๋‹ค์Œ space๋ฅผ ๋ˆŒ๋ ค์ฃผ๋ฉด ์ ์šฉ๋œ๋‹ค!

 

๊ทธ๋Ÿผ ์—ฌ๊ธฐ๊นŒ์ง€ ํ–ˆ์„ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ์Šต์ด ๋‚˜์˜ฌ ๊ฒƒ์ด๋ฉฐ, ํ•œ ๋ฒˆ ๋” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค ์ฒดํฌํ•ด์ฃผ์ž.

cf) ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋„ control + r (๋ฐ”๋กœ ์ด์ „์˜ ์‹คํ–‰ ๋‚ด์—ญ ์žฌ์‹คํ–‰) ์„ ํ•˜๊ฑฐ๋‚˜, control + option + r์„ ํ†ตํ•ด์„œ ์‹คํ–‰ ๋‚ด์—ญ ์ค‘์—์„œ ์›ํ•˜๋Š” ๊ฒƒ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด๋ณด์ž.

class ExpenseReport {
    private val expenses: MutableList<Expense> = ArrayList()
    private val date: String
        get() = "9/12/2002"

    private var total = 0
    private var mealExpenses = 0
    private lateinit var printer: ReportPrinter

    fun printReport(printer: ReportPrinter) {
        this.printer = printer
        printHeader()
        calculateExpenses()
        printExpenses()
        printTotal()
    }

    private fun printExpenses() {
        for (expense in expenses) {
            val name = getName(expense)

            printer.print(
                String.format(
                    "%s\t%s\t$%.02f\n",
                    if (expense.type == Expense.Type.DINNER
                        && expense.amount > 5000
                        || expense.type === Expense.Type.BREAKFAST
                        && expense.amount > 1000
                    ) "X" else " ",
                    name, expense.amount / 100.0
                )
            )
        }
    }

    private fun getName(expense: Expense): String {
        var name = "TILT"

        when (expense.type) {
            Expense.Type.DINNER -> name = "Dinner"
            Expense.Type.BREAKFAST -> name = "Breakfast"
            Expense.Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    private fun calculateExpenses() {
        for (expense in expenses) {
            addTotal(expense)
        }
    }

    private fun addTotal(expense: Expense) {
        if (isMeal(expense)) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    private fun isMeal(expense: Expense) = expense.type == Expense.Type.BREAKFAST || expense.type == Expense.Type.DINNER

    private fun printTotal() {
        printer.print(String.format("\nMeal expenses $%.02f", mealExpenses / 100.0))
        printer.print(String.format("\nTotal $%.02f", total / 100.0))
    }

    private fun printHeader() {
        printer.print("Expenses " + date + "\n")
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

 

์—ฌ๊ธฐ์—์„œ, printReport()์˜ ํ•จ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ ๋” ๋ถ„๋ฆฌํ•˜์—ฌ ์™„์ „ํ•˜๊ฒŒ ๊ณ„์‚ฐ๊ณผ ์ถœ๋ ฅ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ„์ž.

fun printReport(printer: ReportPrinter) {
    this.printer = printer
    calculateExpenses()
    printExpensesAndTotal()
}

private fun printExpensesAndTotal() {
    printHeader()
    printExpenses()
    printTotal()
}

 

printExpenses() ์—ญ์‹œ ํ•จ์ˆ˜๊ฐ€ 2 depth ๋ฅผ ๋„˜์–ด๊ฐ€๋‹ˆ ์•Œ์•„๋ณด๊ธฐ ์–ด๋ ค์šด ๊ฒƒ ๊ฐ™๋‹ค. ํ•œ ๋ฒˆ ๋” ๋ถ„๋ฆฌํ•ด์ฃผ์ž.

private fun printExpenses() {
    for (expense in expenses) {
        printExpense(expense)
    }
}

private fun printExpense(expense: Expense) {
    val name = getName(expense)

    printer.print(
        String.format(
            "%s\t%s\t$%.02f\n",
            if (expense.type == Expense.Type.DINNER
                && expense.amount > 5000
                || expense.type === Expense.Type.BREAKFAST
                && expense.amount > 1000
            ) "X" else " ",
            name, expense.amount / 100.0
        )
    )
}

 

printExpense() ๋‚ด๋ถ€์—์„œ name ์ด ํ•œ ๊ณณ์—์„œ๋งŒ ์“ฐ์ด๊ณ  ์žˆ์œผ๋‹ˆ inline ์„ ํ†ตํ•ด์„œ ์กฐ๊ธˆ ๋” ์ค„์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

private fun printExpense(expense: Expense) {
    printer.print(
        String.format(
            "%s\t%s\t$%.02f\n",
            if (expense.type == Expense.Type.DINNER
                && expense.amount > 5000
                || expense.type === Expense.Type.BREAKFAST
                && expense.amount > 1000
            ) "X" else " ",
            getName(expense), expense.amount / 100.0
        )
    )
}

 

์ถ”๊ฐ€์ ์œผ๋กœ ๊ฐ’์„ ํฌ๋งทํŒ… ํ•˜๋Š” ๋ถ€๋ถ„์˜ ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด if๋ฌธ์„ ํ•œ ๋ฒˆ ๋” ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด์ฃผ์ž.

private fun printExpense(expense: Expense) {
    printer.print(
        String.format(
            "%s\t%s\t$%.02f\n",
            if (isOverage(expense)) "X" else " ",
            getName(expense), expense.amount / 100.0
        )
    )
}

private fun isOverage(expense: Expense) = (expense.type == Expense.Type.DINNER
        && expense.amount > 5000
        || expense.type === Expense.Type.BREAKFAST
        && expense.amount > 1000)

 

๋งˆ์ง€๋ง‰์œผ๋กœ, ๊ธฐ์กด ์ฝ”๋“œ์—์„œ๋Š” 100์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๋ถ€๋ถ„๋“ค์ด ์ƒ๋‹นํžˆ ๊ฒน์น˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ํ•จ์ˆ˜๋กœ ์ถ”์ถœํ•ด๋ณด์ž.

์ด๋•Œ, ํ˜„์žฌ ์ƒํƒœ์—์„œ ๋‹จ์ˆœํ•˜๊ฒŒ ํ•จ์ˆ˜๋กœ ์ถ”์ถœํ•˜๊ฒŒ ๋˜๋ฉด ๊ฒน์น˜๋Š” ๋ถ€๋ถ„๋“ค์— ๋Œ€ํ•ด intellij ๊ฐ€ ์ถ”์ฒœ์„ ํ•ด์ฃผ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, 1์ฐจ์ ์œผ๋กœ ์ง€์—ญ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌ๋ฅผ ํ•ด๋ณด์ž. (์–ด์ฐจํ”ผ ์‚ฌ๋ผ์งˆ ๋ณ€์ˆ˜๋“ค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฆ„์„ ๋Œ€์ถฉ ์ง€์—ˆ๋‹ค.)

private fun printExpense(expense: Expense) {
    val amount = expense.amount
    printer.print(
        String.format(
            "%s\\t%s\\t$%.02f\\n",
            if (isOverage(expense)) "X" else " ",
            getName(expense), amount / 100.0
        )
    )
}

private fun printTotal() {
    val tempMealExpense = mealExpenses
    val tempTotal = total
    printer.print(String.format("\\nMeal expenses $%.02f", tempMealExpense / 100.0))
    printer.print(String.format("\\nTotal $%.02f", tempTotal / 100.0))
}

 

์ด ์ƒํƒœ์—์„œ extract method ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด, ํ•จ์ˆ˜์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ Int → Double ๋กœ ๊ณ ์ •๋˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์ „๋ถ€ ๊ต์ฒด๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

private fun printExpense(expense: Expense) {
    val amount = expense.amount
    printer.print(
        String.format(
            "%s\t%s\t$%.02f\n",
            if (isOverage(expense)) "X" else " ",
            getName(expense), getRate(amount)
        )
    )
}

private fun printTotal() {
    val tempMealExpense = mealExpenses
    val tempTotal = total
    printer.print(String.format("\nMeal expenses $%.02f", getRate(tempMealExpense)))
    printer.print(String.format("\nTotal $%.02f", getRate(tempTotal)))
}

private fun getRate(amount: Int) = amount / 100.0

 

๋งˆ์ง€๋ง‰์œผ๋กœ inline variable ์„ ํ†ตํ•ด์„œ ์ž„์‹œ ๋ณ€์ˆ˜๋ฅผ ์ œ๊ฑฐํ•ด์ฃผ์ž.

๊ทธ๋Ÿผ ์ตœ์ข…์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ์ž˜ ์ˆ˜์ •๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

private fun printExpense(expense: Expense) {
    printer.print(
        String.format(
            "%s\t%s\t$%.02f\n",
            if (isOverage(expense)) "X" else " ",
            getName(expense), getRate(expense.amount)
        )
    )
}

private fun printTotal() {
    printer.print(String.format("\nMeal expenses $%.02f", getRate(mealExpenses)))
    printer.print(String.format("\nTotal $%.02f", getRate(total)))
}

private fun getRate(amount: Int) = amount / 100.0

 


 

๐ŸŒฑ Step4 - ๋„๋ฉ”์ธ์˜ ์‘์ง‘๋„๋ฅผ ๋†’์—ฌ๋ณด์ž

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

ํ•˜์ง€๋งŒ, ํ•ต์‹ฌ ๋„๋ฉ”์ธ์ธ Expense ๊ฐ€ ํ•˜๋Š” ์ผ์ด ๊ต‰์žฅํžˆ ๋นˆ์•ฝํ•˜๊ฒŒ ๋Š๊ปด์ง„๋‹ค.

์ถœ๋ ฅ๊ณผ ํด๋ž˜์Šค์˜ ์ „์—ญ ๋ณ€์ˆ˜์™€ ๊ด€๋ จ์—†์ด ์ˆ˜ํ–‰๋˜๊ณ  ์žˆ๋Š” ‘๋น„์šฉ์˜ ์ด๋ฆ„์— ๋Œ€ํ•ด์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ’๊ณผ ‘meal์ธ์ง€ ํŒ๋‹จํ•˜๋Š” ๊ธฐ๋Šฅ’, ‘ํ‰๊ท ์ธ์ง€ ๊ณ„์‚ฐํ•˜๋Š” ๊ธฐ๋Šฅ’์€ ๋„๋ฉ”์ธ์—๊ฒŒ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ?

 

f6์„ ๋ˆŒ๋Ÿฌ (Move Method) ๋„๋ฉ”์ธ์—๊ฒŒ ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์œ„์ž„ํ•ด๋ณด์ž.

์ฐธ๊ณ ๋กœ, ์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ๊ธฐ๋Šฅ์ด ๊บผ์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค.

์•„๋ž˜์˜ ๊ธ€์—์„œ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋ฆฌ ํ•ด๋‘๋ฉด ์ข‹๋‹ค. (๊ฐ“๋ง๊ทœ)

 

[Kotlin] ์ธํ…”๋ฆฌ์ œ์ด(IntelliJ)์—์„œ ์ฝ”ํ‹€๋ฆฐ move instance method(๋‹ค๋ฅธ ํด๋ž˜์Šค๋กœ ๋ฉ”์†Œ๋“œ ์˜ฎ๊ธฐ๊ธฐ) ๋ฆฌํŒฉํ† ๋ง

1. ์ฝ”ํ‹€๋ฆฐ์—์„œ move instance method ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”ํ•˜๊ธฐ [ move instance method ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๋Šฅ ์†Œ๊ฐœ ] ์˜ˆ๋ฅผ ๋“ค์–ด ์‹ ์šฉ์นด๋“œ๋ฅผ ๋ฐœ๊ธ‰ํ•˜๋Š” ์œ ์Šค์ผ€์ด์Šค๊ฐ€ ์žˆ๊ณ , ์‹ ์šฉ์นด๋“œ ๋ฐœ๊ธ‰์„ ์œ„ํ•ด์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ

mangkyu.tistory.com

 

๊ทธ๋Ÿฌ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋„๋ฉ”์ธ์—๊ฒŒ ํ•ด๋‹น ๊ธฐ๋Šฅ๋“ค์ด ์ž˜ ๋„˜์–ด๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

class Expense(
    var type: Type,
    var amount: Int
) {
    enum class Type {
        DINNER,
        BREAKFAST,
        CAR_RENTAL
    }

    fun getName(): String {
        var name = "TILT"

        when (this.type) {
            Type.DINNER -> name = "Dinner"
            Type.BREAKFAST -> name = "Breakfast"
            Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    fun isMeal() = this.type == Type.BREAKFAST || this.type == Type.DINNER

    fun isOverage() = (this.type == Type.DINNER
        && this.amount > 5000
        || this.type === Type.BREAKFAST
        && this.amount > 1000)
}

 


 

๐ŸŒฑ Step5 - ์ถœ๋ ฅํ•˜๋Š” ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๋ถ„๋ฆฌํ•ด๋ณด์ž

 

์—ฌ์ „ํžˆ ์ถœ๋ ฅ๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํ•ฉ์ณ์ ธ ์žˆ๋‹ค๋Š” ๋ถ€๋ถ„์ด ๋งˆ์Œ์— ๊ฑธ๋ฆฐ๋‹ค.

๊ทธ๋ž˜์„œ, ์šฐ๋ฆฌ๋Š” ๋น„์šฉ์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ์ถœ๋ ฅ์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ณ„๋„์˜ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ธฐ๋Šฅ๋ณ„ ์‘์ง‘๋„๋ฅผ ๋†’์—ฌ๋ณด๋„๋ก ํ•  ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿฌ๋‚˜… ์ž๋ฐ”์—๋Š” control + t → extract delegate ๋ฅผ ์„ ํƒํ•˜๋ฉด ์‰ฝ๊ฒŒ ๋ถ„๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ฝ”ํ‹€๋ฆฐ์—๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. (๊ฐ€์žฅ ์•„์‰ฝ๋‹ค)

 

๊ทธ๋ž˜์„œ, extract delegate ๋Œ€์‹  extract superclass๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์•ฝ๊ฐ„์˜ ๊ผผ์ˆ˜๋กœ ํด๋ž˜์Šค๋ฅผ ๋ถ„๋ฆฌํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

๋จผ์ €, ๊ธฐ์กด์˜ ํด๋ž˜์Šค์˜ ์ด๋ฆ„์„ ExpenseReporter ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ถœ๋ ฅ์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ๋“ค๋งŒ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋„๋ก ์˜๋ฏธ๋ฅผ ๋ถ€์—ฌํ•ด์ฃผ์—ˆ๋‹ค. (Shift + F6 - Rename)

๊ทธ๋ฆฌ๊ณ , extract superclass๋ฅผ ํ†ตํ•ด์„œ ๋น„์šฉ์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ถ€๋ถ„์„ ์ถ”์ถœํ•ด์ฃผ์ž.

 

ํ•จ์ˆ˜๋ฅผ ์„ ํƒํ•˜๋‹ค ๋ณด๋ฉด ์œ„์™€ ๊ฐ™์ด !๊ฐ€ ๋– ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Š” ๊ฐ™์ด ์˜ฎ๊ฒจ์•ผ ๊นจ์ง€์ง€ ์•Š๋Š” ์นœ๊ตฌ๋“ค์„ Intellj ๊ฐ€ ๊ฐ์ง€ํ•˜์—ฌ ์•Œ๋ ค์ค€๋‹ค.

!๊ฐ€ ๋œฌ ์นœ๊ตฌ๋“ค๊นŒ์ง€ ํ•จ๊ป˜ ์ฒดํฌํ•˜์—ฌ ์„ ํƒํ•ด์ค€๋‹ค.

 

๊ทธ๋Ÿผ ์•„๋ž˜์™€ ๊ฐ™์ด ExpenseReport ๋ผ๋Š” ์นœ๊ตฌ๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค. ์šฐ๋ฆฌ๋Š” SuperClass๋กœ ์–ด์ฉ” ์ˆ˜ ์—†์ด ์ƒ์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— open class๊ฐ€ ๋˜์—ˆ๋Š”๋ฐ, ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์ผ๋ฐ˜ ํด๋ž˜์Šค๋กœ ๋ฐ”๊พธ์–ด์ฃผ๊ณ , ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ์ˆ˜ ์—ญ์‹œ public ์œผ๋กœ ์—ด์–ด๋‘๋„๋ก ํ•˜์ž.

// AS-IS
open class ExpenseReport {
    protected val expenses: MutableList<Expense> = ArrayList()
    protected var total = 0
    protected var mealExpenses = 0
    protected fun calculateExpenses() {
        for (expense in expenses) {
            addTotal(expense)
        }
    }

    private fun addTotal(expense: Expense) {
        if (expense.isMeal()) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

// TO-BE
class ExpenseReport {
     val expenses: MutableList<Expense> = ArrayList()
     var total = 0
     var mealExpenses = 0

     fun calculateExpenses() {
        for (expense in expenses) {
            addTotal(expense)
        }
    }

    private fun addTotal(expense: Expense) {
        if (expense.isMeal()) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

 

๋˜ํ•œ, ๊ธฐ์กด ํด๋ž˜์Šค๋„ ๊นจ์ ธ์žˆ์„ ํ…Œ๋‹ˆ๊นŒ ์ˆ˜์ •์ด ํ•„์š”ํ•˜๋‹ค. Report์— ๋Œ€ํ•ด ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์ค€ ๋‹ค์Œ, F2๋ฅผ ๋ˆŒ๋Ÿฌ ์˜ค๋ฅ˜ ๋ถ€๋ถ„์„ ์ฐพ์•„๊ฐ€๋ฉฐ ์ˆ˜์ •ํ•ด์ฃผ์ž. (addExpense์˜ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ตœ๋Œ€ํ•œ ๋œ ๊นจ์ง€๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์‹œ ๋งŒ๋“ค์–ด ๋‘์—ˆ๋‹ค.)

class ExpenseReporter {
    private val date: String
        get() = "9/12/2002"

    private lateinit var printer: ReportPrinter
    private val expenseReport = ExpenseReport()

    fun printReport(printer: ReportPrinter) {
        this.printer = printer
        expenseReport.calculateExpenses()
        printExpensesAndTotal()
    }

    private fun printExpensesAndTotal() {
        printHeader()
        printExpenses()
        printTotal()
    }

    private fun printExpenses() {
        for (expense in expenseReport.expenses) {
            printExpense(expense)
        }
    }

    private fun printExpense(expense: Expense) {
        printer.print(
            String.format(
                "%s\t%s\t$%.02f\n",
                if (isOverage(expense)) "X" else " ",
                expense.getName(), expense.amount / 100.0
            )
        )
    }

    private fun isOverage(expense: Expense) = (expense.type == Expense.Type.DINNER
            && expense.amount > 5000
            || expense.type === Expense.Type.BREAKFAST
            && expense.amount > 1000)

    private fun printTotal() {
        printer.print(String.format("\nMeal expenses $%.02f", expenseReport.mealExpenses / 100.0))
        printer.print(String.format("\nTotal $%.02f", expenseReport.total / 100.0))
    }

    private fun printHeader() {
        printer.print("Expenses " + date + "\n")
    }

    fun addExpense(expense: Expense) {
        expenseReport.expenses.add(expense)
    }
}

 


 

๐ŸŒฑ Step6 - OCP๋ฅผ ๊ฐœ์„ ํ•ด๋ณด์ž

์—ฌ๊ธฐ๊นŒ์ง€ ์ž˜ ์‹คํ–‰ํ–ˆ๋‹ค๋ฉด ํด๋ž˜์Šค๊ฐ€ ๊ฝค๋‚˜ ๊ฐ„๊ฒฐํ•ด์ง„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

// ์ถœ๋ ฅ์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค
class ExpenseReporter {
    private val date: String
        get() = "9/12/2002"
    private lateinit var printer: ReportPrinter
    private val expenseReport: ExpenseReport = ExpenseReport()

    fun printReport(printer: ReportPrinter) {
        this.printer = printer
        expenseReport.calculateExpenses()
        printExpensesAndTotal()
    }

    private fun printExpensesAndTotal() {
        printHeader()
        printExpenses()
        printTotal()
    }

    private fun printExpenses() {
        for (expense in expenseReport.expenses) {
            printExpense(expense)
        }
    }

    private fun printExpense(expense: Expense) {
        printer.print(
            String.format(
                "%s\t%s\t$%.02f\n",
                if (expense.isOverage()) "X" else " ",
                expense.getName(), getRate(expense.amount)
            )
        )
    }

    private fun printTotal() {
        printer.print(String.format("\nMeal expenses $%.02f", getRate(expenseReport.mealExpenses)))
        printer.print(String.format("\nTotal $%.02f", getRate(expenseReport.total)))
    }

    private fun getRate(amount: Int) = amount / 100.0

    private fun printHeader() {
        printer.print("Expenses " + date + "\n")
    }

    fun addExpense(expense: Expense) {
        expenseReport.addExpense(expense)
    }
}

// ๊ณ„์‚ฐ์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค
class ExpenseReport {
    val expenses: MutableList<Expense> = ArrayList()
    var total = 0
    var mealExpenses = 0

    fun calculateExpenses() {
        for (expense in expenses) {
            addTotal(expense)
        }
    }

    private fun addTotal(expense: Expense) {
        if (expense.isMeal()) {
            mealExpenses += expense.amount
        }
        total += expense.amount
    }

    fun addExpense(expense: Expense) {
        expenses.add(expense)
    }
}

// ๋น„์šฉ์— ๋Œ€ํ•œ ๋„๋ฉ”์ธ
class Expense(
    var type: Type,
    var amount: Int
) {
    enum class Type {
        DINNER,
        BREAKFAST,
        CAR_RENTAL
    }

    fun getName(): String {
        var name = "TILT"

        when (this.type) {
            Type.DINNER -> name = "Dinner"
            Type.BREAKFAST -> name = "Breakfast"
            Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    fun isMeal() = this.type == Type.BREAKFAST || this.type == Type.DINNER

    fun isOverage() = (this.type == Type.DINNER
            && this.amount > 5000
            || this.type === Type.BREAKFAST
            && this.amount > 1000)
}

 

์šฐ๋ฆฌ๋Š” ์ด์ œ ๋งˆ์ง€๋ง‰์œผ๋กœ, ์ƒˆ๋กœ์šด Expense์˜ ํƒ€์ž…์ด ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ ์œ ์—ฐํ•˜๊ฒŒ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ๊ฐ์— ๋Œ€ํ•ด ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•  ๊ฒƒ์ด๋‹ค.

๋จผ์ €, ์›ํ™œํ•œ ์ž‘์—…์„ ์œ„ํ•ด์„œ Expense์˜ ํด๋ž˜์Šค๋ฅผ ๋ฏธ๋ฆฌ abstract class๋กœ ๋ฐ”๊พธ์–ด๋‘์ž. (์ด๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š์œผ๋ฉด, ์•„๋ž˜์—์„œ ์„ธ๋ถ€ ํด๋ž˜์Šค๋“ค์„ ๋งŒ๋“ค ๋•Œ ๋‹จ์ถ•ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.)

abstract class Expense(
    var type: Type,
    var amount: Int
)

 

๊ทธ๋ฆฌ๊ณ , ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์ž‘์—…์„ ์ง„ํ–‰ํ•ด๋ณด์ž. 

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

// AS-IS
"printOneDinner" {
    report.addExpense(Expense(Expense.Type.DINNER, 1678))
    report.printReport(printer)

    printer.getText() shouldBe """
        Expenses 9/12/2002
         	Dinner	$16.78

        Meal expenses $16.78
        Total $16.78
    """.trimIndent()
}

// TO-BE
"printOneDinner" {
    report.addExpense(DinnerExpense(1678))
    report.printReport(printer)

    printer.getText() shouldBe """
        Expenses 9/12/2002
         	Dinner	$16.78

        Meal expenses $16.78
        Total $16.78
    """.trimIndent()
}

 

๊ทธ๋Ÿผ, ์šฐ๋ฆฌ๋Š” ์ด ํ…Œ์ŠคํŠธ๋ฅผ ๊นจ์ง€์ง€ ์•Š๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ค. DinnerExpense๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์ž.

(๋งˆ์น˜ TDD๋ฅผ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ํ…Œ์ŠคํŠธ๋ฅผ ์ค‘์ ์œผ๋กœ ํ•˜์—ฌ ๋ฆฌํŒฉํ„ฐ๋ง์„ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.)

 

option + enter๋ฅผ ๋ˆ„๋ฅด๋ฉด ์œ„์™€ ๊ฐ™์ด create class๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋‚˜๋จธ์ง€ ํƒ€์ž…์— ๋Œ€ํ•œ ํด๋ž˜์Šค๋„ ๋งŒ๋“ค์–ด์ฃผ์ž.

์ด๋ฏธ ํด๋ž˜์Šค์—์„œ ์–ด๋–ค ํƒ€์ž…์ธ์ง€ ๋“œ๋Ÿฌ๋‚˜๊ณ  ์žˆ์–ด, ์ธ์ž๋กœ type ์กฐ๊ฑด์„ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ์–ด์ƒ‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋ž˜์Šค ์ƒ์„ฑ ํ›„ change Signature๋ฅผ ํ†ตํ•ด ์ผ๊ด„์ ์œผ๋กœ ์ œ๊ฑฐํ•ด์ฃผ์—ˆ๋‹ค.

class DinnerExpense(amount: Int) : Expense(Type.DINNER, amount) 

class BreakfastExpense(amount: Int) : Expense(Type.BREAKFAST, amount)

class CarRentalExpense(amount: Int) : Expense(Type.CAR_RENTAL, amount)

 

๊ทธ๋Ÿผ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์—ญ์‹œ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๋€Œ์—ˆ์„ ๊ฒƒ์ด๋‹ค.

internal class ExpenseReportTest : StringSpec({
    lateinit var report: ExpenseReporter
    lateinit var printer: MockReportPrinter

    beforeTest {
        report = ExpenseReporter()
        printer = MockReportPrinter()
    }

    "printEmpty" {
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002

            Meal expenses $0.00
            Total $0.00
        """.trimIndent()
    }

    "printOneDinner" {
        report.addExpense(DinnerExpense(1678))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$16.78

            Meal expenses $16.78
            Total $16.78
        """.trimIndent()
    }

    "twoMeals" {
        report.addExpense(DinnerExpense(1000))
        report.addExpense(BreakfastExpense(500))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$10.00
             	Breakfast	$5.00

            Meal expenses $15.00
            Total $15.00
        """.trimIndent()
    }

    "twoMealsAndCarRental" {
        report.addExpense(DinnerExpense(1000))
        report.addExpense(BreakfastExpense(500))
        report.addExpense(CarRentalExpense(50000))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Dinner	$10.00
             	Breakfast	$5.00
             	Car Rental	$500.00

            Meal expenses $15.00
            Total $515.00
        """.trimIndent()
    }

    "overages" {
        report.addExpense(BreakfastExpense(1000))
        report.addExpense(BreakfastExpense(1001))
        report.addExpense(DinnerExpense(5000))
        report.addExpense(DinnerExpense(5001))
        report.printReport(printer)

        printer.getText() shouldBe """
            Expenses 9/12/2002
             	Breakfast	$10.00
            X	Breakfast	$10.01
             	Dinner	$50.00
            X	Dinner	$50.01

            Meal expenses $120.02
            Total $120.02
        """.trimIndent()
    }
})

 


 

๐ŸŒฑ Step7 - ์ž์‹ ํด๋ž˜์Šค๋“ค์—๊ฒŒ ์ฑ…์ž„์„ ๋ถ„๋ฆฌํ•˜์ž

๊ฐ ํด๋ž˜์Šค๋“ค์„ ๋งŒ๋“ค์–ด์ฃผ์—ˆ์ง€๋งŒ, ๊นกํ†ต ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ต‰์žฅํžˆ ๋ถ€์‹คํ•˜๋‹ค.

์šฐ๋ฆฌ์˜ Expense ํด๋ž˜์Šค๋ฅผ ๋˜๋Œ์•„๋ณด์ž. ์—ฌ๊ธฐ์—์„œ ๊ฐ ํ•จ์ˆ˜๋“ค์€ ์ž์‹ ํด๋ž˜์Šค๋“ค๋กœ ๋‚ด๋ ค๊ฐ€๊ธฐ์— ์ข‹์•„ ๋ณด์ธ๋‹ค.

abstract class Expense(
    var type: Type,
    var amount: Int
) {
    enum class Type {
        DINNER,
        BREAKFAST,
        CAR_RENTAL
    }

    fun getName(): String {
        var name = "TILT"

        when (this.type) {
            Type.DINNER -> name = "Dinner"
            Type.BREAKFAST -> name = "Breakfast"
            Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    fun isMeal() = this.type == Type.BREAKFAST || this.type == Type.DINNER
    fun isOverage() = (this.type == Type.DINNER
            && this.amount > 5000
            || this.type === Type.BREAKFAST
            && this.amount > 1000)
}

 

์ด๋ฅผ ์œ„ํ•ด์„œ, ๊ฐ๊ฐ์˜ ํ•จ์ˆ˜๋ฅผ ์ž์‹ ํด๋ž˜์Šค๋“ค์—๊ฒŒ ๋„˜๊ฒจ์ฃผ๊ธฐ ์œ„ํ•ด control + t → push member down์„ ๋ˆŒ๋Ÿฌ์ฃผ์ž.

 

๊ทธ๋Ÿฌ๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด Expense ํด๋ž˜์Šค์˜ ๊ฐ ๋ฉ”์„œ๋“œ๋“ค์ด abstract ํด๋ž˜์Šค๋กœ ๋ณ€ํ•˜๊ฒŒ ๋œ๋‹ค.

abstract class Expense(
    var type: Type,
    var amount: Int
) {
    enum class Type {
        DINNER,
        BREAKFAST,
        CAR_RENTAL
    }

    abstract fun getName(): String
    abstract fun isMeal(): Boolean
    abstract fun isOverage(): Boolean
}

 

์ด์ œ, ๊ฐ๊ฐ์˜ ํด๋ž˜์Šค๋“ค์—๊ฒŒ ๊ฐ€์„œ ์–ด์šธ๋ฆฌ๋Š” ํ–‰๋™์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ •ํ•ด์ฃผ์ž.

// AS-IS
class BreakfastExpense(amount: Int) : Expense(Type.BREAKFAST, amount) {
    override fun getName(): String {
        var name = "TILT"

        when (this.type) {
            Type.DINNER -> name = "Dinner"
            Type.BREAKFAST -> name = "Breakfast"
            Type.CAR_RENTAL -> name = "Car Rental"
        }
        return name
    }

    override fun isMeal() = this.type == Type.BREAKFAST || this.type == Type.DINNER
    override fun isOverage() = (this.type == Type.DINNER
            && this.amount > 5000
            || this.type === Type.BREAKFAST
            && this.amount > 1000)

}

// TO-BE
class BreakfastExpense(amount: Int) : Expense(Type.BREAKFAST, amount) {
    override fun getName(): String = "Breakfast"
    override fun isMeal() = true
    override fun isOverage() = this.amount > 1000
}

// ๋‚˜๋จธ์ง€๋„ ๋™์ผํ•˜๊ฒŒ ๋ณ€๊ฒฝ
class CarRentalExpense(amount: Int) : Expense(Type.CAR_RENTAL, amount) {
    override fun getName(): String = "Car Rental"
    override fun isMeal() = false
    override fun isOverage() = false
}

class DinnerExpense(amount: Int) : Expense(Type.DINNER, amount) {
    override fun getName(): String = "Dinner"
    override fun isMeal() = true
    override fun isOverage() = this.amount > 5000
}

 

๋˜ํ•œ, ์—ฌ๊ธฐ์—์„œ ๊ธฐ์กด์— ์ •์˜ํ•ด์ฃผ์—ˆ๋˜ Expense ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ Type์ด๋ผ๋Š” enum class๋Š” getName์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋˜์—ˆ๋˜ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ œ๊ฑฐํ•ด๋„ ๋ฌด๋ฆฌ๊ฐ€ ์—†๋‹ค. ์ง€์›Œ์ฃผ๋„๋ก ํ•˜์ž. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ด์ „์— ์‚ฌ์šฉํ–ˆ๋˜ command + F6 (Change Signature)๋ฅผ ์‚ฌ์šฉํ•˜์ž. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ž์‹ ํด๋ž˜์Šค์— ๊ฐ€์„œ ์ง์ ‘ ์ œ๊ฑฐํ•ด์ค„ ํ•„์š”๊ฐ€ ์—†์ด ํŽธํ•˜๊ฒŒ ์ง€์šธ ์ˆ˜ ์žˆ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€ ์™€์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ•œ ๋ฒˆ ๋Œ๋ ค๋ณด๋„๋ก ํ•˜์ž. ๋‹ค ๋Œ์•„๊ฐ”๋‹ค๋ฉด ์„ฑ๊ณต์ด๋‹ค.

 

 


 

๐ŸŒฑ ๋งˆ๋ฌด๋ฆฌ

 

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

 

๋ฟŒ๋“ฏ!

 

๊ธ€์ด ๊ต‰์žฅํžˆ ๊ธธ์–ด์กŒ๊ธฐ๋„ ํ•˜๊ณ , ์‹ค์ œ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง์„ ์ง„ํ–‰ํ•˜๋Š”๋ฐ๋„ ๊ฝค ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค.

์ด๋ฒˆ ๋ฆฌํŒฉํ„ฐ๋ง์˜ ํ•ต์‹ฌ์€, ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์•ˆ์ „ํ•˜๊ฒŒ ๋ฆฌํŒฉํ„ฐ๋ง์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๊ฐ€ ์ค‘์ ์ด ๋˜์—ˆ๋‹ค.

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

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

 

๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์ค‘์š”ํ•œ ๊ฑด, ๊ธฐ๋Šฅ์„ ์ˆ˜์ •ํ–ˆ์„ ๋•Œ ๋ถˆ์•ˆํ•˜์ง€ ์•Š๋„๋ก ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ผญ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

์ผ์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ฝ”๋“œ ๋Œ€์‹ ์— ์ง์ ‘ ์ˆ˜๊ธฐ๋กœ QA๋ฅผ ํ•˜๋˜ ๋‚ ๋“ค์ด ๋งŽ์•˜๋Š”๋ฐ, ์•ž์œผ๋กœ๋Š” ๋ฆฌํŒฉํ„ฐ๋ง ํ•  ๋•Œ ์ตœ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์˜์กดํ•˜์—ฌ ์ˆ˜์ •ํ•˜๋„๋ก ์Šต๊ด€์„ ๋“ค์—ฌ์•ผ๊ฒ ๋‹ค. ๋‹จ์ถ•ํ‚ค ์—ฐ์Šต๋„ ์—ด์‹ฌํžˆ ํ•ด์„œ ์•ž์œผ๋กœ๋Š” ํ‚ค๋ณด๋“œ๋กœ๋งŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋„๋ก... ๋…ธ๋ ฅํ•ด์•ผ๊ฒ ๋‹ค ๐Ÿฅน

Comments