简体   繁体   English

在 Kotlin 应用程序中将数据计算与数据格式化分开是否有性能优势?

[英]Are there performance benefits from separating data calculations from data formatting in Kotlin app?

I have a codebase where all my data calculations and data formatting occur within a single function. Are there any performance benefits to separating these out into separate functions or does separating them only improve readability?我有一个代码库,其中我所有的数据计算和数据格式化都发生在一个 function 中。将它们分离成单独的函数是否有任何性能优势,或者将它们分离只会提高可读性?

I know that I should separate them but I don't really know all the reasons why.我知道我应该将它们分开,但我真的不知道所有原因。

Here is the function I am referring to:这是我指的 function:

private fun processData(data: ByteArray) {
        progressBar.visibility = GONE
        Log.d(TAG, "displayDiagnosticData: ")

        val bmsVersionView = findViewById<TextView>(R.id.textview_bms_version)
        val boardVersionView = findViewById<TextView>(R.id.textview_board_version)
        val cellOneView = findViewById<TextView>(R.id.textview_cell_1)
        val cellTwoView = findViewById<TextView>(R.id.textview_cell_2)
        val cellThreeView = findViewById<TextView>(R.id.textview_cell_3)
        val cellFourView = findViewById<TextView>(R.id.textview_cell_4)
        val cellFiveView = findViewById<TextView>(R.id.textview_cell_5)
        val cellSixView = findViewById<TextView>(R.id.textview_cell_6)
        val cellSevenView = findViewById<TextView>(R.id.textview_cell_7)
        val cellEightView = findViewById<TextView>(R.id.textview_cell_8)
        val cellNineView = findViewById<TextView>(R.id.textview_cell_9)
        val cellTenView = findViewById<TextView>(R.id.textview_cell_10)
        val cellElevenView = findViewById<TextView>(R.id.textview_cell_11)
        val cellTwelveView = findViewById<TextView>(R.id.textview_cell_12)
        val cellThirteenView = findViewById<TextView>(R.id.textview_cell_13)
        val cellFourteenView = findViewById<TextView>(R.id.textview_cell_14)
        val packTotalView = findViewById<TextView>(R.id.textview_diagnostic_voltage)
        val packSocView = findViewById<TextView>(R.id.textview_diagnostic_soc)
        val chargeTempView = findViewById<TextView>(R.id.textview_charge_temp)
        val dischargeTempView = findViewById<TextView>(R.id.textview_discharge_temp)
        val chargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_charge_current)
//        val dischargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_discharge_current)
        val dischargeCircuitStateView = findViewById<TextView>(R.id.textview_discharge_circuit)
        val chargeCircuitStateView = findViewById<TextView>(R.id.textview_charge_circuit)
        val balanceCircuitStateView = findViewById<TextView>(R.id.textview_balance_circuit)
        val emptyCircuitStateView = findViewById<TextView>(R.id.textview_empty_circuit)

        val bmsVersion = data[0] + (data[1] * 256)
        val cellOne = data[2].toDouble() / 100 + 3.52
        val cellTwo = data[3].toDouble() / 100 + 3.52
        val cellThree = data[4].toDouble() / 100 + 3.52
        val cellFour = data[5].toDouble() / 100 + 3.52
        val cellFive = data[6].toDouble() / 100 + 3.52
        val cellSix = data[7].toDouble() / 100 + 3.52
        val cellSeven = data[8].toDouble() / 100 + 3.52
        val cellEight = data[9].toDouble() / 100 + 3.52
        val cellNine = data[10].toDouble() / 100 + 3.52
        val cellTen = data[11].toDouble() / 100 + 3.52
        val cellEleven = data[12].toDouble() / 100 + 3.52
        val cellTwelve = data[13].toDouble() / 100 + 3.52
        val cellThirteen = data[14].toDouble() / 100 + 3.52
        val cellFourteen = data[15].toDouble() / 100 + 3.52
        val totalVoltage = 47.8 + (data[16].toDouble() / 10)
        val chargeTempCelsius = data[19]
        val dischargeTempCelsius = data[20]
        val chargeTempFahr = (chargeTempCelsius * 9.0 / 5.0) + 32.0
        val dischargeTempFahr = (dischargeTempCelsius * 9.0 / 5.0) + 32.0
        val chargeCurrent = data[21]
//        val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()
        val chargeCircuitState = data[25].toInt()
        val dischargeCircuitState = data[26].toInt()
        val balanceCircuitState = data[27].toInt()
        val emptyCircuitState = data[28].toInt()

        val chargeCircuit: String = if (chargeCircuitState == 1) {
            "On"
        }else {
            "Off"
        }

        val dischargeCircuit: String = if (dischargeCircuitState == 1) {
            "On"
        }else {
            "Off"
        }

        val balanceCircuit: String = if (balanceCircuitState == 1) {
            "On"
        }else {
            "Off"
        }

        val emptyCircuit: String = if (emptyCircuitState == 1) {
            "On"
        }else {
            "Off"
        }

        val bmsVersionString = SpannableString("BMS Version: $bmsVersion")
        bmsVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
        val boardVersionString = SpannableString("Board Version: 2.1")
        boardVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)
        val cellOneString = SpannableString("Cell 1: %.2fV".format(cellOne))
        cellOneString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellTwoString = SpannableString("Cell 2: %.2fV".format(cellTwo))
        cellTwoString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellThreeString = SpannableString("Cell 3: %.2fV".format(cellThree))
        cellThreeString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellFourString = SpannableString("Cell 4: %.2fV".format(cellFour))
        cellFourString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellFiveString = SpannableString("Cell 5: %.2fV".format(cellFive))
        cellFiveString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellSixString = SpannableString("Cell 6: %.2fV".format(cellSix))
        cellSixString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellSevenString = SpannableString("Cell 7: %.2fV".format(cellSeven))
        cellSevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellEightString = SpannableString("Cell 8: %.2fV".format(cellEight))
        cellEightString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellNineString = SpannableString("Cell 9: %.2fV".format(cellNine))
        cellNineString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
        val cellTenString = SpannableString("Cell 10: %.2fV".format(cellTen))
        cellTenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
        val cellElevenString = SpannableString("Cell 11: %.2fV".format(cellEleven))
        cellElevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
        val cellTwelveString = SpannableString("Cell 12: %.2fV".format(cellTwelve))
        cellTwelveString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
        val cellThirteenString = SpannableString("Cell 13: %.2fV".format(cellThirteen))
        cellThirteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
        val cellFourteenString = SpannableString("Cell 14: %.2fV".format(cellFourteen))
        cellFourteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
        val packTotalString = SpannableString("Pack Total: %.1fV".format(totalVoltage))
        packTotalString.setSpan(StyleSpan(Typeface.BOLD), 0, 10, 0)
        val socString = SpannableString("SOC: ${data[17].toInt()}%")
        socString.setSpan(StyleSpan(Typeface.BOLD), 0, 3, 0)
        val chargeTempString = SpannableString("Charge Temp: ${chargeTempFahr.toInt()}" + "°F")
        chargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
        val dischargeTempString = SpannableString("Discharge Temp: ${dischargeTempFahr.toInt()}" + "°F")
        dischargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
        val chargeCurrentString = SpannableString("Charge Current: $chargeCurrent" + "A")
        chargeCurrentString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
//        val dischargeCurrentString = "Discharge Current: $dischargeCurrent" + "A"
        val chargeCircuitStateString = SpannableString("Charge Circuit: $chargeCircuit")
        chargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
        val dischargeCircuitStateString = SpannableString("Discharge Circuit: $dischargeCircuit")
        dischargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 17, 0)
        val balanceCircuitStateString = SpannableString("Balance Circuit: $balanceCircuit")
        balanceCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
        val emptyCircuitStateString = SpannableString("Empty Circuit: $emptyCircuit")
        emptyCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)


        bmsVersionView.text = bmsVersionString
        boardVersionView.text = boardVersionString
        cellOneView.text = cellOneString
        cellTwoView.text = cellTwoString
        cellThreeView.text = cellThreeString
        cellFourView.text = cellFourString
        cellFiveView.text = cellFiveString
        cellSixView.text = cellSixString
        cellSevenView.text = cellSevenString
        cellEightView.text = cellEightString
        cellNineView.text = cellNineString
        cellTenView.text = cellTenString
        cellElevenView.text = cellElevenString
        cellTwelveView.text = cellTwelveString
        cellThirteenView.text = cellThirteenString
        cellFourteenView.text = cellFourteenString
        packTotalView.text = packTotalString
        packSocView.text = socString
        chargeTempView.text = chargeTempString
        dischargeTempView.text = dischargeTempString
        chargeCurrentView.text = chargeCurrentString
//        dischargeCurrentView.text = dischargeCurrentString
        chargeCircuitStateView.text = chargeCircuitStateString
        dischargeCircuitStateView.text = dischargeCircuitStateString
        balanceCircuitStateView.text = balanceCircuitStateString
        emptyCircuitStateView.text = emptyCircuitStateString

    }

Performance?表现? No. Maintainability?不,可维护性? Yes.是的。 Separation of concerns is one of the core t.nets of modern software architecture.关注点分离是现代软件架构的核心 t.net 之一。 Combining the two things makes a difficult to read, difficult to debug mess.将这两件事结合起来会造成难以阅读、难以调试的混乱局面。 Separating them allows you to concentrate on one thing at a time, and makes it easier for people maintaining the code (which may even be you 6 months from now when fixing a bug and you've forgotten how it works) to understand the logic and flow of the program.将它们分开可以让您一次专注于一件事,并使维护代码的人(甚至可能是从现在起 6 个月后修复错误时您已经忘记它是如何工作的)更容易理解逻辑和程序的流程。

That function you posted would not be accepted in any professional codebase.您发布的 function 不会被任何专业代码库接受。 It's too long, it does too many things.它太长了,它做了太多的事情。 It needs to be broken up.它需要被打破。

Your bigger problem there is that you're repeating yourself, a lot.你更大的问题是你在重复自己,很多。 Not only does that make the whole thing longer, and arguably harder to read, it also makes it hard to maintain like Gabe says, and makes it far more likely a bug will sneak in there.这不仅使整个事情变得更长,而且可以说更难阅读,而且还像 Gabe 所说的那样难以维护,并且更有可能出现错误。 Imagine you needed to add another row of cells - there's a lot of boilerplate involved, a lot of repetitive work, and that's where humans tend to mess up想象一下,您需要添加另一行单元格 - 涉及很多样板文件,很多重复性工作,而这正是人类容易搞砸的地方

Like as an example of the kind of thing you can do - see how your cell data is basically taken from a range of values in data , with the same calculation applied to each?就像您可以做的事情的例子一样-看看您的单元格数据基本上是如何从data中的一系列值中获取的,并对每个值应用相同的计算? You could do this instead:你可以这样做:

val cells = (2..15).map { index -> data[index].toDouble() / 100 + 3.52 }

or, to keep things more explicit and separate:或者,为了让事情更明确和独立:

// Except give this a good name since it's doing something specific
// Because this is some kind of conversion formula, putting it in its own function
// makes it easy to maintain and document, and it's clear exactly what it is
fun getCellValue(dataValue: Int) = dataValue.toDouble() / 100 + 3.52

val cells = (2..15).map { index -> getCellValue(data[index]) }

Now you have one or two lines replacing 14 lines of initialisation code.现在你用一两行代码替换了 14 行初始化代码。 It's also easier to make changes - if the format of data changes, you can easily change the range of indices to use, or the formula applied to each value.更改也更容易 - 如果data格式发生变化,您可以轻松更改要使用的索引范围或应用于每个值的公式。 It's one place, instead of on each line, where you have to update each one and make sure you haven't made a typo or skipped one.这是一个地方,而不是在每一行上,您必须在其中更新每一行并确保您没有打错字或跳过一个。


And when you have structured data like that, it can make your other code easier to work with too - because instead of needing to work with separate variables, you can work with indices and loop over things instead of writing each step out:当你有这样的结构化数据时,它也可以让你的其他代码更容易使用——因为你不需要使用单独的变量,你可以使用索引和循环遍历事物,而不是写出每个步骤:

// no need for a separate line for each with hardcoded values if you can work it out
// (Because it's a separate function, you can use it for the other display lines too,
// it's not cell-specific)
fun SpannableString.applyBoldPrefix() = apply {
    val colonIndex = indexOf(':')
    if (colonIndex >= 0) setSpan(StyleSpan(Typeface.BOLD), 0, colonIndex, 0)
}

// you could also just pass in the index and look up cells[index] here -
// this is a more functional approach, but whatever's good
fun getCellDisplayString(cellIndex: Int, cellData: Double) =
    SpannableString("Cell ${cellIndex + 1}: %.2fV".format(cellData))
        .applyBoldPrefix()

// lots of ways to define/grab a set of views programmatically - putting them all
// in a container you can look up is one way. You can also generate resource identifier
// strings, like "R.id.textview_cell_$i" and look that up
val cellTextViews = findViewById<ViewGroup>(R.id.containerHoldingCells)
    .children.filterIsInstance<TextView>

// now you can just iterate over stuff to complete the repetitive task
cellTextViews.forEachIndexed { i, view ->
    view.text = getCellDisplayString(i, cells[i])
}

That's about half your code right there.这大约是你的代码的一半。 I wouldn't necessarily structure everything this way (I feel like given you're working with a data format here, a more formal structure definition would be helpful, and you can generalise more too) and it's a bit rough-and-ready, but hopefully it gives you a general sense of how you can cut things down, but also make it easier to maintain them and try out changes我不一定会以这种方式构建所有内容(我觉得如果你在这里使用数据格式,更正式的结构定义会有所帮助,你也可以概括更多)而且它有点粗糙,准备就绪,但希望它能让您大致了解如何减少事情,同时也使维护它们和尝试更改变得更容易

I condensed my code a lot more and created functions to process separate chunks of data.我进一步压缩了我的代码并创建了函数来处理单独的数据块。 I also was able to get rid of a lot of my repetitious code.我也能够摆脱很多重复的代码。 For anyone who is interested, here is the updated code:对于任何感兴趣的人,这里是更新的代码:

private fun String.withStyling() = SpannableString(this).apply {
        setSpan(StyleSpan(Typeface.BOLD), 0, indexOfFirst { it == ':' }, 0)
    }

private fun processDiagnosticData(data: ByteArray) {
        binding.progressBarCyclic.visibility = GONE
        Log.d(TAG, "displayDiagnosticData: ")

        processCells(data)

        processTemps(data[19], data[20])

        processCircuits(data)

        processOtherData(data)
    }

    // Process cells 1-14 and display.
    private fun processCells(data: ByteArray) {
        val cellViews = listOf(
            binding.textviewCell1,
            binding.textviewCell2,
            binding.textviewCell3,
            binding.textviewCell4,
            binding.textviewCell5,
            binding.textviewCell6,
            binding.textviewCell7,
            binding.textviewCell8,
            binding.textviewCell9,
            binding.textviewCell10,
            binding.textviewCell11,
            binding.textviewCell12,
            binding.textviewCell13,
            binding.textviewCell14
        )

        for ((i, cellView) in cellViews.withIndex()) {
            val value = data[2 + i].toDouble() / 100 + 3.52
            val cellNumberString = (i + 1).toString()
            val formattedString = "Cell $cellNumberString: %.2fV".format(value).withStyling()
            cellView.text = formattedString
        }
    }

    // Process charge/discharge temps and display.
    private fun processTemps(chargeTempCel: Byte, dischargeTempCel: Byte) {
        val chargeTempFahr = chargeTempCel * 9.0 / 5.0 + 32.0
        val dischargeTempFahr = dischargeTempCel * 9.0 / 5.0 + 32.0
        val chargeTempString = "Charge Temp: $chargeTempFahr°F".withStyling()
        val dischargeTempString = "Discharge Temp: $dischargeTempFahr°F".withStyling()
        binding.textviewChargeTemp.text = chargeTempString
        binding.textviewDischargeTemp.text = dischargeTempString
    }

    // Process circuit states and display.
    private fun processCircuits(data: ByteArray) {
        val circuitViews = listOf(
            binding.textviewChargeCircuit,
            binding.textviewDischargeCircuit,
            binding.textviewBalanceCircuit,
            binding.textviewEmptyCircuit
        )

        val circuitNames = listOf(
            "Charge Circuit: ",
            "Discharge Circuit: ",
            "Balance Circuit: ",
            "Empty Circuit: "
        )

        for ((i, circuit) in circuitViews.withIndex()) {
            val value = if (data[25 + i].toInt() == 1) {
                "On"
            } else {
                "Off"
            }
            val formattedString = (circuitNames[i] + value).withStyling()
            circuit.text = formattedString
        }
    }

    // Process the rest of the data and display.
    private fun processOtherData(data: ByteArray) {

        val totalVoltage = 47.8 + (data[16].toDouble() / 10)
        val packSoc = data[17].toInt()
        val chargeCurrent = data[21]
//        val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()

        val bmsVersionString = "BMS Version: ${data[0] + (data[1] * 256)}".withStyling()
        val boardVersionString = "Board Version: 2.1".withStyling()
        val totalVoltageString = "Pack Total: %.1fV".format(totalVoltage).withStyling()
        val packSocString = "SOC: ${packSoc}%".withStyling()
        val chargeCurrentString = "Charge Current: ${chargeCurrent}A".withStyling()
//        val dischargeCurrent = "Discharge Current: $dischargeCurrentA".withStyling()

        binding.textviewBmsVersion.text = bmsVersionString
        binding.textviewBoardVersion.text = boardVersionString
        binding.textviewDiagnosticVoltage.text = totalVoltageString
        binding.textviewDiagnosticSoc.text = packSocString
        binding.textviewDiagnosticChargeCurrent.text = chargeCurrentString
//        binding.textviewDiagnosticDischargeCurrent.text = dischargeCurrentString
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM