Serie de traducciones efectivas de Kotlin - Capítulo 1 - Elemento 2 - Minimización del alcance variable

¡Acostúmbrate a escribir juntos! Este es el segundo día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento

Traducción: jiajianchen Revisión: zhiyueli

Elemento 2: Minimizar el alcance de la variable

TLDR:

En cualquier caso, le recomendamos que reduzca el alcance de la propiedad variable o miembro tanto como sea posible.

introducir

Cuando definimos un estado, tendemos a reducir el alcance de las variables y propiedades haciendo referencia a las siguientes pautas :

  • Prefiere las variables locales sobre las propiedades de los miembros;
  • Use variables en el ámbito más pequeño posible, por ejemplo, si una variable solo se usa en un ciclo, defina la variable en el ciclo;

El rango de elementos que mencionamos aquí se refiere al rango visible de elementos en el programa. En Kotlin, el alcance visible de un elemento generalmente está determinado por las llaves externas, y generalmente se puede acceder a los miembros fuera del alcance actual, como en el siguiente ejemplo:

val a = 1
fun fizz() {
  val b = 2
  print(a + b) // 当前位置可以访问 a
}
val buzz = {
  val c = 3
  print(a + c)
}
// 当前位置可以访问 a,但是不能访问 b 和 c
复制代码

En el ejemplo anterior, dentro de métodos fizzy métodos buzz, se puede acceder a variables de ámbito externo. Sin embargo, no se puede acceder a los miembros dentro del método desde el exterior.

El siguiente ejemplo muestra cómo limitar el alcance de una variable:

// 坏的
var user : User
for (i in users.indices) {
   user = users[i]
   print("User at $i is $user")
}
​
// 好的
for (i in users.indices) {
  val user = users[i]
  print("User at $i is $user")
}
​
// 同样的变量可见范围,更佳的语法实现
for ((i, user) in users.withIndex()) {
  print("User at $i is $user")
}
复制代码

En el primer ejemplo, se puede acceder a la variable userno solo dentro del bucle, sino también fuera; mientras que en el segundo y tercer ejemplo, limitamos el alcance de la variable al bucle.for

Del mismo modo, puede haber casos en los que los rangos visibles estén anidados , como lambdaotra expresión anidada en una lambdaexpresión. Recomendamos que la mejor práctica sea definir las variables en el ámbito más pequeño posible.

por qué

En cuanto a por qué hacemos esto, hay varias razones:

首先最重要的是:当我们收紧了变量的范围,能更容易地去跟踪和管理我们的程序。当我们分析代码的时候,我们需要去考虑目前都有哪些元素。如果需要处理的元素越多,那么进行下一步编程的难度就会越大;而如果程序越简单,那么它就越不容易被破坏。其次,这跟我们更喜欢用不可变的属性或对象的原因是类似的。结合可变的属性来考虑,如果它仅能在一个更小的范围中修改,那么我们能更容易跟踪它是如何被修改的。我们也能更容易地对其进行进一步的推理和修改。

另一个问题是,具备更宽泛范围的变量,可能会被另一个开发者滥用。举个例子:

  • 我们使用一个变量来记录列表的最后一个元素。做法是对列表进行遍历,不断通过列表对应值修改当前变量,在循环结束时当前变量即可获取到列表的最后一个元素。

但这可能会引发严重的问题,比如说在循环结束之后去修改这最后的元素。这就非常糟糕了,因为另一个开发者会努力去理解整套逻辑,分析出当前元素代表的数值究竟是什么。而带来的这些副作用很明显是不必要的。

译者注:作者举这个例子的意图其实是推荐使用一个不可变的属性来记录上述提到的“列表末尾值”。

除此之外,无论一个变量是只读的还是可读写的,我们会更推荐在变量定义时对其进行初始化。别让开发者被迫要去找变量定义的位置。实现上述观点,我们可以在表达式中使用一些控制结构(如ifwhentry-catch和 Kotlin 的多目运算符)。

// 坏的
val user : User
if (hasValue) {
  user = getValue()
} else {
  user = User()
}
​
// 好的
val user : User = if (hasValue) {
  getValue()
} else {
  User()
}
复制代码

如果我们需要同时定义多个属性,可以使用 Kotlin 的解构声明语法:

// 坏的
fun updateWeather(degrees: Int) {
  val description: String
  val color: Int
  if (degrees < 5) {
    description = "cold"
    color = Color.BLUE
  } else if (degrees < 23) {
    description = "mild"
    color = Color.YELLOW
  } else {
    description = "hot"
    color = Color.RED
  }
}
​
// 好的
fun updateWeather(degrees: Int) {
  val (description, color) = when {
    degrees < 5 -> "cold" to Color.BLUE
    degrees < 23 -> "mild" to Color.YELLOW
    else -> "hot" to Color.RED
  }
}
复制代码

隐患

最后,太宽泛的可见范围是很危险的。接下来讲一种一个常见的危险做法:变量捕获「Capturing」

当我在传授 Kotlin 协程知识的时候,我布置的其中一个练习题是:通过 Sequence Builder 来过滤出某个列表中的素数。解题思路如下:

  1. 创建一个从 2 开始的列表;
  2. 取列表中的第一个数,同时它也是一个素数;
  3. 接下来从列表中过滤掉所有能被这个素数整除的数字。

下面是这个算法的简单实现:

var numbers = (2..100).toList()
val primes = mutableListOf<Int>()
while (numbers.isNotEmpty()) {
  val prime = numbers.first()
  primes.add(prime)
  numbers = numbers.filter { it % prime != 0 }
}
print(primes) // [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
​
复制代码

现在增加一点难度,我需要一个能返回无限个数的素数序列「sequence」。(如果你想要挑战这道题,你可以在这里停下来,并且尝试自己去实现它)

解决的答案如下:

val primes: Sequence<Int> = sequence {
  var numbers = generateSequence(2) { it + 1 }
  while (true) {
    val prime = numbers.first()
    yield(prime)
    numbers = numbers.drop(1).filter { it % prime != 0 }
  }
}
​
print(primes.take(10).toList()) // [2,3,5,7,11,13,17,19,23,29]
复制代码

然后,几乎每个组里面都有一个人想要试图去“优化”它,认为不应该在每次循环中都创建变量来提取素数,于是改成了以下代码:

val primes: Sequence<Int> = sequence {
  var numbers = generateSequence(2) { it + 1 }
  var prime: Int
  while (true) {
    prime = numbers.first()
    yield(prime)
    numbers = numbers.drop(1).filter { it % prime != 0 }
  }
}
复制代码

问题是,“优化”后的实现并不能正确工作,得出的答案是错误的。

print(primes.take(10).toList())
// [2,3,5,6,7,8,9,10,11,12]
复制代码

(你可以在这停下来,思考为什么会是这个结果)

为什么会出现这样的结果,是因为我们访问的是变量prime。当我们使用Sequence时候,执行过滤操作是惰性的。在每一次循环中,我们添加越来越多的循环操作。在“优化后”的代码中,我们添加的过滤器引用的prime是可变的,因此,每次执行过滤逻辑时使用的必然是最后一次变量 prime 的值(而并非预期的值),因此导致我们得出的结果是错误的。

了解到上面的情况之后,我们应该多注意获取数值过程中可能引发的意想不到的问题。要规避这些问题,我们推荐的做法是给变量设置更小的可见范围。

总结

Por muchas razones, preferimos definir las variables en un ámbito lo más pequeño posible. Y para las variables locales var, recomendamos usarlas en su lugar val. Estas sencillas reglas pueden ahorrarnos muchos problemas.

Supongo que te gusta

Origin juejin.im/post/7085637121725169700
Recomendado
Clasificación