在这个例子中如何限制可变性的风险?

How to limit the risks of mutability in this example?

我的代码类似于:

fun verify(problem,answer){
    val errors: MutableList<String> = mutableListOf()
    if (!condiditon_1) {
        errors.add("Condition_1 is not verified")
    }
    if (!condition_2) {
        errors.add("Condition_2 is not verified")
    }
    return errors
}

这里errors是一个可变列表。我正在尝试重写限制可变性的程序

第一种方式是:

errorsImmutable = errors.toList()

和 return 不可变列表 errorsImmutable 以防止从外部对其进行修改。

另一种解法是:

fun verify(problem,answer){
    val errors: List<String> = emptyList()
    if (!condiditon_1) {
        val mutableList =  errorLogs.toMutableList()
        mutableList.add("Condition_1 is not verified")
        errors = mutableList.toList()
    }
    if (!condition_2) {
        val mutableList =  errorLogs.toMutableList()
        mutableList.add("Condition_2 is not verified")
        errors = mutableList.toList()
    }
    return errors
}

哪个更好。更一般地说,有更好的方法吗?

您在示例中忽略了显示函数的 return 类型。简单地 return 一个只读列表并相信消费者不会将其转换为 MutableList 来修改它通常被认为是好的。

fun verify(): List<String> {
    val errors: MutableList<String> = mutableListOf()
    if (!condiditon_1) {
        errors.add("Condition_1 is not verified")
    }
    if (!condition_2) {
        errors.add("Condition_2 is not verified")
    }
    return errors
}

如果不编写自己的不可变列表 class,就没有创建真正不可变列表的干净方法。请注意,标准库的 listOf 函数 return 是只读列表,而不是不可变列表,具有单个元素参数的重载除外。

一般来说,如果您可以创建一个已经填充的集合,这往往比创建一个空集合然后填充它要好——原因有很多,包括您所说的不变性。当然,这并不总是可能的(或优雅的),但在这种情况下,这是一种方法:

fun verify() = listOfNotNull(
    if (condition1) "Condition1 is not verified" else null,
    if (condition2) "Condition2 is not verified" else null,
)

或等价物:

fun verify() = listOfNotNull(
    "Condition1 is not verified".takeIf{ condition1 },
    "Condition2 is not verified".takeIf{ condition2 },
)

这样你就永远不会看到对列表的可变引用(尽管 listOfNotNull() 实现中可能涉及一个)。因为列表现在是单个表达式,您可以使用表达式主体函数,并让它推断类型。 (当然,更短的代码并不 总是 更简单、更清晰或更易于维护,但我认为这里可能是——当然取决于周围的代码。无论哪种方式,它通常是有用的学习练习。)

这里有一个更具声明性的方法,它单独定义条件(并分解出通用消息格式):

val conditions = listOf(
    Pair({ condition1 }, "Condition1"),
    Pair({ condition2 }, "Condition2"),
)

fun verify() = conditions.filter{ it.first() }
                         .map{ "${it.second} is not verified" }

PS。我错过了另一个调整:如果您使用 to 而不是明确指定 Pair,它可能会稍微更具可读性:

val conditions = listOf(
    { condition1 } to "Condition1",
    { condition2 } to "Condition2",
)