End

Kotlin 朱涛-4 实战 计算器 单元测试 大数加法

本文地址


目录

04 | 实战:构建一个四则运算计算器

计算器 1.0

  • 选择菜单 File -> New -> Project
  • 或者 File -> New -> Project from Version Control 导入 GitHub 工程

基础代码

fun main() {
    while(true) {
        println("请输入标准的算式,并且按回车; \n" +
                "比如:1 + 1,注意符合与数字之间要有空格。\n" +
                "输入exit,退出程序。")

        var input = readLine()
        if (input == null) continue
        if (input == "exit") exitProcess(0)

        var inputList = input.split(" ")
        var result = calculate(inputList)

        if (result == null) {
            println("输入格式不对")
        } else {
            println("$input = $result")
        }
    }
}

private fun calculate(inputList: List<String>): Int? {
    if (inputList.size != 3) return null

    var left = inputList.get(0).toInt()
    var operation = inputList.get(1)
    var right = inputList.get(2).toInt()

    when(operation) {
        "+" -> return left + right
        "-" -> return left - right
        "*" -> return left * right
        "/" -> return left / right
        else -> return null
    }
}

代码优化

  • ①:使用 Kotlin 的原始字符串
  • ②:使用 Elvis 表达式
  • ③:程序中所有的 var 都应该尽可能改为 val
  • ④:Kotlin 统一了数组和集合的元素访问操作,哪种方便哪种来
  • ⑤:可以将 return 放到 when 表达式的前面
  • ⑤:结合枚举或密封类使用 when 表达式,可以省略 else 分支
val help = """
--------------------------------------
使用说明:
1. 输入 1 + 1,按回车,即可使用计算器;
2. 注意:数字与符号之间要有空格;
3. 想要退出程序,请输入:exit
--------------------------------------""".trimIndent()

fun main() {
    while (true) {
        println(help)

        val input = readLine() ?: continue
        if (input == "exit") exitProcess(0)

        val inputList = input.split(" ")
        val result = calculate(inputList)

        if (result == null) {
            println("输入格式不对")
            continue
        } else {
            println("$input = $result")
        }
    }
}

private fun calculate(inputList: List<String>): Int? {
    if (inputList.size != 3) return null

    val left = inputList[0].toInt()
    val operation = Operation.valueOf(inputList[1]) // 这里其实还有一些问题
    val right = inputList[2].toInt()

    return when (operation) {
        Operation.ADD -> left + right
        Operation.MINUS -> left - right
        Operation.MULTI -> left * right
        Operation.DIVI -> left / right
    }
}

enum class Operation(val value: String) {
    ADD("+"),
    MINUS("-"),
    MULTI("*"),
    DIVI("/")
}

计算器 2.0

优化方向:

  • 融入面向对象的思想:将程序封装到一个类中,并尽量让每个函数的功能划分清楚、简单
  • 兼容输入格式:兼容不同的输入格式,不管数字和符号之间有没有空格,都能成功执行
import kotlin.system.exitProcess

class CalculatorV2 {
    private val exit = "exit"
    private val help = """
--------------------------------------
使用说明:
1. 输入 1 + 1,按回车,即可使用计算器;
2. 注意:数字与符号之间要有空格;
3. 想要退出程序,请输入:exit
--------------------------------------""".trimIndent()

    fun start() {
        while (true) {
            println(help)

            val input = readLine() ?: continue
            val result = calculate(input)

            if (result == null) {
                println("输入格式不对")
                continue
            } else {
                println("$input = $result")
            }
        }
    }

    fun calculate(input: String): String? {
        if (shouldExit(input)) exitProcess(0) // 封装是否需要退出的逻辑到一个方法中
        val exp = parseExpression(input) ?: return null // 封装解析逻辑到一个方法中

        val left = exp.left
        val operator = exp.operator
        val right = exp.right

        return when (operator) {
            Operation.ADD -> addString(left, right)
            Operation.MINUS -> minusString(left, right)
            Operation.MULTI -> multiString(left, right)
            Operation.DIVI -> diviString(left, right)
        }
    }

    private fun shouldExit(input: String): Boolean {
        return input == exit
    }

    private fun parseExpression(input: String): Expression? {
        val operation = parseOperator(input) ?: return null
        val list = input.split(operation.value)
        if (list.size != 2) return null
        return Expression(list[0].trim(), operation, list[1].trim())
    }

    private fun parseOperator(input: String): Operation? {
        Operation.values().forEach {  // 枚举的遍历
            if (input.contains(it.value)) {
                return it
            }
        }
        return null
    }

    private fun addString(left: String, right: String): String {
        val result = left.toInt() + right.toInt()
        return result.toString()
    }

    private fun minusString(left: String, right: String): String {
        val result = left.toInt() - right.toInt()
        return result.toString()
    }

    private fun multiString(left: String, right: String): String {
        val result = left.toInt() * right.toInt()
        return result.toString()
    }

    private fun diviString(left: String, right: String): String {
        val result = left.toInt() / right.toInt()
        return result.toString()
    }
}

enum class Operation(val value: String) {
    ADD("+"),
    MINUS("-"),
    MULTI("*"),
    DIVI("/")
}

data class Expression(
    val left: String,
    val operator: Operation,
    val right: String
)

fun main() {
    val calculator = CalculatorV2()
    calculator.start()
}

计算器 3.0

优化方向:

  • 增加单元测试:以类为单元,对类中的方法进行一一测试
  • 支持大数的加法:对特别大的数进行兼容

单元测试

要在 Kotlin 中使用单元测试,需要添加 Kotlin 官方提供的依赖:

testImplementation 'org.jetbrains.kotlin:kotlin-test'

单元测试的代码,一般放在工程的 test 目录下。

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class TestCalculatorV3 {
    @Test
    fun testCalculate() {
        val calculator = CalculatorV2()
        assertEquals("2", calculator.calculate("1+1"))
        assertEquals("2333333333333333", calculator.calculate("2333333333333332+1"))
    }
}
  • 使用注解 @Test 修饰用来做测试的方法
  • 执行断言 assertEquals,如果两个参数相等,单元测试就会成功,否则就会失败

在实际的开发工作当中,单元测试是需要尽量覆盖所有情况的。比如上面这个计算器,它的单元测试用例数量会达到几百上千个。

单元测试不仅可以验证新开发的功能,同时还可以用于保证旧的功能不受影响。在实际开发工作中,我们很容易因为对功能 A 的改动,导致功能 B 出问题。然后往往由于时间限制,测试人员只测试了功能 A,忽略了功能 B,最终导致线上故障带来经济损失。

而借助单元测试,在每一次的开发工作完成以后,我们就统一跑一遍所有的单元测试,只要单元测试通过了,我们就能保证新的功能没问题,而旧的功能也没问题。

大数加法

大数加法是面试中的一道高频题,它的解题思路很简单,就是通过模拟我们手写加法竖式的方法,从个位、十位、百位、千位,一直累加,超过 10 的时候,我们需要进位

private fun addString(leftNum: String, rightNum: String): String {
    val result = StringBuilder()
    var leftIndex = leftNum.length - 1 // 默认指向数字的个位(字符串最后一位字符)
    var rightIndex = rightNum.length - 1
    var carry = 0 // 存储每一位计算结果的进位

    while (leftIndex >= 0 || rightIndex >= 0) {
        // 取每一位上的数字,如果超出最大位,则返回 0
        val leftVal = if (leftIndex >= 0) leftNum[leftIndex].digitToInt() else 0
        val rightVal = if (rightIndex >= 0) rightNum[rightIndex].digitToInt() else 0
        val sum = leftVal + rightVal + carry // 计算当前位的值
        carry = sum / 10 // 计算当前位的进位
        result.append(sum % 10) // 计算当前位的结果
        leftIndex-- // 从低位一直到高位,直到遍历完它们所有的数字位
        rightIndex--
    }
    if (carry != 0) { // 遍历完之后,判断最高位是否还有进位
        result.append(carry)
    }

    return result.reverse().toString() // 翻转回来
}

2018-06-11

posted @ 2018-06-11 17:20  白乾涛  阅读(820)  评论(0编辑  收藏  举报