ios - for - En Swift, ¿cómo evito las referencias a objetos opcionales y nulas?
swift reference (4)
¿O se supone que solo debo usar opciones, y si eso es lo que estoy haciendo, ¿cómo es mejor que simplemente tener asignaciones nulas a referencias de objetos como tienen otros idiomas?
Si su cálculo puede necesitar devolver un valor "especial", entonces sí, en Swift se supone que debe usar opciones.
Son mejores que los tipos anulables porque son
explícitos
.
Es fácil pasar por alto un caso en el que un puntero puede ser
nil
, es mucho más difícil (pero completamente posible) fastidiar con opciones.
Si usa "if let" para desenvolverlos de manera segura, evitará bloqueos, pero está atrapado dentro del alcance de la declaración "if let" para trabajar con el valor, y aún tiene que manejar el caso potencial nulo.
Esa es una característica.
Quiero decir, ese es el objetivo de un tipo opcional: tienes que manejar ambos casos (
nil
y no
nil
) y debes ser explícito al respecto.
Vea el tipo y la mónada Quizás de Haskell para un ejemplo de un concepto similar. El tipo Quizás es el equivalente exacto de los tipos opcionales, la mónada Maybe hace que sea muy fácil "encadenar" las operaciones con estos valores opcionales sin tener que verificar manualmente los valores vacíos todo el tiempo.
Toda la razón para los opcionales es evitar bloqueos en tiempo de ejecución que resultan de golpear una variable asignada a nil / null / none. Por lo tanto, las variables no pueden ser nulas; en su lugar, pueden envolverse en un tipo Opcional que los represente como Some o None, y desenvolverse para obtener el contenido específico de Some o nil.
¡Pero si los desenvuelves por todos lados
!
, o con las opciones implícitas sin envolver, solo presentas la posibilidad, ya que eres un codificador imperfecto, de fallas en tiempo de ejecución.
Si usa
if let
para desenvolverlos de manera segura, evitará bloqueos, pero está atrapado dentro del alcance de la declaración
if let
para trabajar con el valor dentro de Some, y aún tiene que manejar el caso nulo potencial.
Si utiliza ?
para desenvolver temporalmente para llamar a un método, se volverá a envolver cuando se complete, presentando la posibilidad desordenada de envoltura opcional de varias capas.
Entonces: me parece que lo que hay que hacer es evitar los opcionales, excepto cuando sea necesario (por ejemplo, al llamar a métodos de marco que los devuelven). Pero si no estoy usando opciones, eso significa que mis referencias a objetos no deben ser nulas, y no puedo entender cómo manejar los casos en los que, por una razón u otra, no debería haber, o no hay , un valor asignado a una referencia de objeto.
Mi pregunta es: ¿Cómo evito la necesidad de nada? Parece que requiere un enfoque de programación diferente. (O se supone que solo debo usar opciones, y si eso es lo que estoy haciendo, ¿cómo es mejor que simplemente tener asignaciones nulas a referencias de objetos como lo han hecho otros idiomas?)
Sé que esta es una pregunta potencialmente subjetiva, pero ¿dónde más debo hacerla? No estoy tratando de molestar o agitar el debate, realmente me gustaría saber cuál es el enfoque correcto a medida que escribo más código Swift.
Piénselo de esta manera: en cualquier situación en la que necesite un
var value: Double?
variable opcional
var value: Double?
, podría usar de manera equivalente un par de variables no opcionales:
var value: Double
var valueExists: Bool
Siempre que desee utilizar el
value
, deberá verificar manualmente si
valueExists
:
if valueExists { /* do something */ }
else { /* handle case where value is not valid */ }
Por lo general, es mucho más fácil usar las características del lenguaje diseñadas para manejar opciones que pasar las variables duales y hacer estas comprobaciones manualmente.
También es mucho más seguro que el compilador siempre le recuerde manejar el caso en el que el
value
no es válido.
¡Es por razones como estas que se inventaron los opcionales!
Swift 2 y versiones posteriores también incluyen
guard let
que resuelve muchos de los problemas mencionados en esta pregunta.
Incluso puede reutilizar el nombre de la variable si lo desea:
func compute(value: Double?) -> Double? {
guard let value = value else { return nil }
// ... `value` is now a regular Double within this function
}
También puede usar el
encadenamiento opcional
(
?
) Para cortocircuitar una expresión cuando la primera parte devuelve
nil
:
let answer = compute(value: 5.5)?.rounded()
Y
nil coalescing
(
??
) para proporcionar un valor predeterminado en lugar de nil:
let answer:Double = compute(value: 5.5) ?? 0
Estoy de acuerdo con las otras publicaciones en que los opcionales no son la herramienta adecuada para cada problema, pero no es necesario evitarlos.
Usar
if let
,
guard let
,
??
, etc. para expresar cómo se manejan o pasan los valores nulos.
Tienes razón, los opcionales pueden ser un dolor, por eso no debes usarlos en exceso. Pero son más que algo con lo que tienes que lidiar cuando usas frameworks. Son una solución a un problema increíblemente común: cómo manejar una llamada que devuelve un resultado que podría estar bien o no.
Por ejemplo, tome el miembro
Array.first
.
Esta es una práctica utilidad que le brinda el primer elemento de una matriz.
¿Por qué es útil poder llamar a
a.first
, cuando puedes llamar
a[0]
?
Porque en tiempo de ejecución la matriz puede estar vacía, en cuyo caso explotará
a[0]
.
Por supuesto, puede verificar una
a.count
antemano, pero nuevamente
a. podrías olvidar
y
si. eso da como resultado un código bastante feo.
Array.first
trata esto devolviendo un opcional.
Por lo tanto, se ve obligado a desenvolver lo opcional antes de poder usar el valor que es el primer elemento de la matriz.
Ahora, a su problema sobre el valor sin envolver que solo existe dentro del bloque con
if let
.
Imagine el código paralelo, comprobando el recuento de la matriz.
Sería lo mismo, ¿verdad?
if a.count > 0 {
// use a[0]
}
// outside the block, no guarantee
// a[0] is valid
if let firstElement = a.first {
// use firstElement
}
// outside the block, you _can''t_
// use firstElement
Claro, podría hacer algo como realizar un retorno temprano de la función si el recuento es cero.
Esto funciona pero es un poco propenso a errores: ¿qué pasa si olvidó hacerlo o lo puso en una declaración condicional que no se ejecutó?
Esencialmente, puede hacer lo mismo con
array.first
: verifique el recuento temprano en la función, y luego haga
array.first!
.
Pero eso
!
es como una señal para usted: tenga cuidado, está haciendo algo peligroso y lamentará si su código no es completamente correcto.
Los opcionales también ayudan a que las alternativas sean un poco más bonitas. Suponga que desea predeterminar el valor si la matriz estaba vacía. En lugar de esto:
array.count > 0 ? a[0] : somedefault
puedes escribir esto:
array.first ?? somedefault
Esto es más agradable de varias maneras. Pone en primer plano lo importante: el valor que desea es cómo comienza la expresión, seguida del valor predeterminado. A diferencia de la expresión ternaria, que lo golpea primero con la expresión de verificación, seguido del valor que realmente desea, y finalmente el valor predeterminado. También es más infalible: es mucho más fácil evitar hacer un error tipográfico e imposible que ese error tipográfico provoque una explosión en tiempo de ejecución.
Tome otro ejemplo: la función de
find
.
Esto comprueba si un valor está en una colección y devuelve el índice de su posición.
Pero el valor puede no estar presente en la colección.
Otros lenguajes pueden manejar esto devolviendo el índice final (que no apunta a un valor, sino más allá del último valor).
Esto es lo que se denomina un valor "centinela", un valor que parece un resultado regular, pero que en realidad tiene un significado especial.
Al igual que en el ejemplo anterior, tendría que verificar que el resultado no fuera igual al índice final antes de usarlo.
Pero tienes que
saber
hacer esto.
Tienes que buscar los documentos para
find
y confirmar que así es como funciona.
Al
find
devolver un opcional es natural, cuando comprende el idioma opcional, darse cuenta de que la razón es porque el resultado podría no ser válido por la razón obvia.
Y también se aplican las mismas cosas sobre seguridad mencionadas anteriormente: no puede olvidarse accidentalmente y usar el resultado como índice, porque primero debe desenvolverlo.
Dicho esto, puede usar en exceso los opcionales, ya que son una carga que debe verificar.
Esta es la razón por la cual los subíndices de matriz no devuelven opcionales: crearía tanta molestia tener que verificarlos constantemente y desenvolverlos, especialmente cuando sabe con certeza que el índice que está usando es válido (por ejemplo, está en un bucle for sobre un rango de índice válido de la matriz) que las personas estarían usando
!
constantemente, y por lo tanto desordenan su código sin beneficio.
Pero luego se agregan los métodos auxiliares como primero y último para cubrir casos comunes en los que las personas desean realizar una operación rápidamente sin tener que verificar primero el tamaño de la matriz, pero desean hacerlo de manera segura.
(Por otro lado, se espera que los diccionarios Swift sean accedidos regularmente a través de subíndices inválidos, por lo que su método
[key]
devuelve un opcional)
Aún mejor es si la posibilidad de falla se puede evitar por completo.
Por ejemplo, cuando el
filter
no coincide con ningún elemento, no devuelve un valor
nil
opcional.
Devuelve una matriz vacía.
"Bueno, obviamente lo haría", se podría decir.
Pero se sorprendería de la frecuencia con la que ve a alguien haciendo un valor de retorno como una matriz opcional cuando en realidad solo debería devolver uno vacío.
Por lo tanto, tiene toda la razón al decir que debe evitar los opcionales, excepto cuando sean necesarios: es solo una cuestión de lo que significa necesario.
En los ejemplos anteriores, diría que son necesarios y una mejor solución a las alternativas.