Nulo no siempre es nulo

Nulo no siempre es nulo


"¿Qué? ¿Qué está escrito aquí?" usted pregunta Ahora lo sacaré todo.


Cuando comencé a aprender el idioma, no pensé que llegaría a este caso estrecho. Tampoco es racional modificar una colección iterable.


Por ejemplo:

func Foo() error { var err *os.PathError = nil return err } func main() { err := Foo() fmt.Println(err) // <nil> fmt.Println(err == nil) // false } 

WAT!


¿Qué es una interfaz?

Vaya al archivo de paquete go runtime / runtime2.go y vea:


 type itab struct { // 40 bytes on a 64bit arch inter *interfacetype _type *_type ... } 

Una interfaz almacena el tipo de interfaz y el tipo de valor en sí.


El valor de cualquier interfaz, no solo el error, es nulo en el caso en que los valores y el tipo AND son nulos.


La función Foo devuelve nil de tipo * os.PathError, comparamos el resultado con nil de tipo nil, de donde se deriva su desigualdad.


Quizás muchos sabían sobre esto, pero pocas personas piensan cómo entrar en práctica.


Mi ejemplo

 type Response struct { Result ResponseResult `json:"result,omitempty"` Error *ResponseError `json:"error,omitempty"` } type ResponseError struct { Message string `json:"message"` } func (e *ResponseError) Error() string { return e.Message } ... func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res handlers.Response _ = json.Unmarshal(body, &res) if res.Error == nil { return } return s.NotifyError(err) } 

La respuesta siempre tiene un resultado o error.


Si hay un error, lo enviamos cuando sea necesario a través del servicio de notificación.
Dentro del servicio, se llama al método Error, y dado que nuestro valor es nulo, tenemos pánico.


Que hacer

Devuelve una interfaz estrictamente de un tipo de interfaz.


En caso de error, tipo de error.


  • Agregar una declaración de error de tipo

 func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res Response _ = json.Unmarshal(body, &res) var err error = res.Error return s.NotifyError(err) } 

Esta técnica, para mi sorpresa, tampoco funciona.


Resulta que al asignar un valor a la variable err, también le pasamos la información inicial sobre el tipo, que no es nula.


  • Tratemos de obtener nuestro tipo de fuente del tipo de interfaz y verifiquemos su valor.

 func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... if e, ok := err.(*ResponseError); ok && e == nil { return s.NotifyError(err) } return nil } 

Sí, esta técnica funciona.


Pero, para ser sincero, no podemos permitirnos verificar todos los tipos de errores que transmitiremos.


Pueden ser todos los errores del controlador de la base de datos, todos nuestros errores internos y otros elementos basura.


¿Cuál es la opción más racional que veo?

 func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { var err error ... var res Response _ = json.Unmarshal(body, &res) if res.Error != nil { return s.NotifyError(err) } return nil } 

Primero, declaramos una variable de tipo error, ya que resulta con el valor y tipo nil.
Y antes de pasar nuestro tipo y valor a esta variable, verifiquemos nuestro tipo y su valor en cero.


Esto nos permitirá no caer en pánico.


Al final

Puede ir aún más lejos e implementar un error "opcional" con el tipo Response, OpcionalError o ErrorOrNil, como este:


 func (r *Response) ErrorOrNil() error { if r.Error == nil { return nil } return r.Error } 

Nota

En las notas de Go wiki, la revisión del código es una nota en el tema sobre la interfaz: en su lugar, devuelva un tipo concreto y deje que el consumidor se burle de la implementación del productor.


Observo que los bailes anteriores no son sobre esto.


Mis notas le permiten no entrar en pánico cuando sabe que desea devolver el interés y, en caso de errores, siempre desea devolver la interfaz.


Pero si puede permitirse devolver un cierto tipo, devuélvalo.


Referencias

go-internals


Yo soy

LinkedIn
Telegrama
Twitter
Github

Source: https://habr.com/ru/post/449714/


All Articles