在 Kotlin 应用程序中将数据计算与数据格式化分开是否有性能优势?
Are there performance benefits from separating data calculations from data formatting in Kotlin app?
我有一个代码库,我的所有数据计算和数据格式化都在一个函数中进行。将它们分离成单独的函数是否有任何性能优势,或者将它们分离只会提高可读性?
我知道我应该将它们分开,但我真的不知道所有原因。
这里是我指的函数:
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
}
性能?不,可维护性?是的。关注点分离是现代软件架构的核心原则之一。将这两件事结合起来会造成难以阅读、难以调试的混乱局面。将它们分开可以让您一次专注于一件事,并使维护代码的人(甚至可能是从现在起 6 个月后修复错误时您已经忘记它是如何工作的)更容易理解逻辑和程序流程。
任何专业代码库都不会接受您发布的那个函数。它太长了,它做了太多的事情。需要分解了。
你更大的问题是你在重复自己,很多次。这不仅使整个事情变得更长,而且可以说更难阅读,而且还像 Gabe 所说的那样难以维护,并且更有可能出现错误。想象一下,您需要添加另一行单元格 - 涉及很多样板文件,很多重复性工作,而这正是人类容易搞砸的地方
作为您可以做的事情的示例 - 查看您的单元格数据基本上是如何从 data
中的一系列值中获取的,并对每个值应用相同的计算?你可以这样做:
val cells = (2..15).map { index -> data[index].toDouble() / 100 + 3.52 }
或者,为了让事情更加明确和独立:
// 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]) }
现在你用一两行代码替换了 14 行初始化代码。更改也更容易 - 如果 data
的格式发生变化,您可以轻松更改要使用的索引范围或应用于每个值的公式。这是一个地方,而不是在每一行上,您必须更新每一行并确保您没有打错字或跳过一个。
当你有这样的结构化数据时,它也可以让你的其他代码更容易使用 - 因为你不需要使用单独的变量,你可以使用索引和循环遍历事物而不是编写每个步骤输出:
// 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])
}
这大约是你代码的一半。我不一定会以这种方式构建所有内容(我觉得如果你在这里使用数据格式,更正式的结构定义会有所帮助,你也可以概括更多)而且它有点 rough-and-ready,但希望它能让您大致了解如何减少事情,同时也使维护它们和尝试更改变得更容易
我进一步压缩了我的代码并创建了函数来处理单独的数据块。我也能够摆脱很多重复的代码。对于任何感兴趣的人,这里是更新的代码:
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
}
我有一个代码库,我的所有数据计算和数据格式化都在一个函数中进行。将它们分离成单独的函数是否有任何性能优势,或者将它们分离只会提高可读性?
我知道我应该将它们分开,但我真的不知道所有原因。
这里是我指的函数:
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
}
性能?不,可维护性?是的。关注点分离是现代软件架构的核心原则之一。将这两件事结合起来会造成难以阅读、难以调试的混乱局面。将它们分开可以让您一次专注于一件事,并使维护代码的人(甚至可能是从现在起 6 个月后修复错误时您已经忘记它是如何工作的)更容易理解逻辑和程序流程。
任何专业代码库都不会接受您发布的那个函数。它太长了,它做了太多的事情。需要分解了。
你更大的问题是你在重复自己,很多次。这不仅使整个事情变得更长,而且可以说更难阅读,而且还像 Gabe 所说的那样难以维护,并且更有可能出现错误。想象一下,您需要添加另一行单元格 - 涉及很多样板文件,很多重复性工作,而这正是人类容易搞砸的地方
作为您可以做的事情的示例 - 查看您的单元格数据基本上是如何从 data
中的一系列值中获取的,并对每个值应用相同的计算?你可以这样做:
val cells = (2..15).map { index -> data[index].toDouble() / 100 + 3.52 }
或者,为了让事情更加明确和独立:
// 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]) }
现在你用一两行代码替换了 14 行初始化代码。更改也更容易 - 如果 data
的格式发生变化,您可以轻松更改要使用的索引范围或应用于每个值的公式。这是一个地方,而不是在每一行上,您必须更新每一行并确保您没有打错字或跳过一个。
当你有这样的结构化数据时,它也可以让你的其他代码更容易使用 - 因为你不需要使用单独的变量,你可以使用索引和循环遍历事物而不是编写每个步骤输出:
// 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])
}
这大约是你代码的一半。我不一定会以这种方式构建所有内容(我觉得如果你在这里使用数据格式,更正式的结构定义会有所帮助,你也可以概括更多)而且它有点 rough-and-ready,但希望它能让您大致了解如何减少事情,同时也使维护它们和尝试更改变得更容易
我进一步压缩了我的代码并创建了函数来处理单独的数据块。我也能够摆脱很多重复的代码。对于任何感兴趣的人,这里是更新的代码:
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
}