Red de conocimiento informático - Conocimiento sistemático - Análisis en profundidad: un conjunto de soluciones para pasar, devolver y exponer errores en Go para facilitar la revisión

Análisis en profundidad: un conjunto de soluciones para pasar, devolver y exponer errores en Go para facilitar la revisión

Autor: andruzhang, ingeniero de desarrollo backend de Tencent IEG

En el desarrollo backend, hay tres dimensiones de problemas que deben resolverse para el manejo de errores:

A orientada a procesos Una función necesita manejar diferentes mensajes de error en diferentes procesos de procesamiento; una función orientada a objetos puede necesitar manejar diferentes tipos de errores devueltos por una operación. Además, cuando se encuentra un error, también se pueden utilizar aserciones para finalizar rápidamente el flujo de funciones, lo que mejora en gran medida la legibilidad del código.

Muchos lenguajes de alto nivel proporcionan sintaxis try...catch. Esta solución se puede utilizar para implementar una lógica de manejo de errores unificada dentro de la función. Aunque no existe un "lenguaje de nivel intermedio" como C, los programadores pueden utilizar definiciones de macros para lograr un cierto grado de afirmación de errores.

Sin embargo, la situación con Go es más embarazosa.

Veamos primero las afirmaciones. Nuestro objetivo es poder comprobar si hay errores y finalizar la función actual con una sola línea de código. Dado que no hay lanzamiento ni macro, si desea implementar una afirmación de una línea, existen dos métodos.

El primer método es escribir el juicio de error de if en una línea, por ejemplo:

El segundo método es tomar prestada la función de pánico y combinarla con recuperación para lograrlo:

Ambos enfoques son cuestionables.

En primer lugar, los problemas con la escritura si están en la misma línea son:

En cuanto al segundo método, tenemos que verlo caso por caso;

Pero use el pánico para afirmar. Aunque la solución básicamente no se usa en lógica empresarial, es muy común en escenarios de prueba. Para realizar la prueba, ¿por qué no utilizar un cuchillo? Una sobrecarga del sistema ligeramente mayor no supone ningún problema. Para Go, goconvey, un marco de prueba unitario muy popular, utiliza el mecanismo de pánico para implementar afirmaciones en las pruebas unitarias, y todos los que lo usan dicen que es bueno.

En resumen, en Go, para el código comercial, el autor no recomienda el uso de aserciones. Cuando se encuentran errores, se recomienda usar este formato honestamente:

Y en pruebas individuales. el código, puede utilizar libremente aserciones basadas en mecanismos de pánico como goconvey.

Como todos sabemos, Go no tiene try...catch y, a juzgar por la actitud oficial, no hay ningún plan para considerarlo a corto plazo. Pero los programadores tienen esta necesidad. El método adoptado por el autor es globalizar la variable err que debe devolverse dentro de la función y luego combinarla con defer para manejarla de manera uniforme:

Esta solución requiere atención especial al problema del alcance de la variable. Por ejemplo, la línea anterior if err = DoSomething( ); err != nil {, si cambiamos err = ... a err := ..., entonces la variable err en esta línea no es la misma variable que la anterior. definido al comienzo de la función (error de error), por lo que incluso si ocurrió un error aquí, la variable de error no se puede capturar en la función de aplazamiento.

En términos de try...catch, el autor en realidad no tiene una forma particularmente buena de simularlo. Incluso el método anterior tiene un problema muy problemático: el método de escritura diferida provoca que se preceda el procesamiento de errores. , y la lógica normal Publicada, que es muy hostil desde la perspectiva de la legibilidad. Por tanto, también espero que los lectores puedan darme algún consejo. Al mismo tiempo, todavía esperamos que los funcionarios de Go puedan continuar iterando y respaldando esta sintaxis.

Este punto en Go parece estar relativamente unificado al principio. Este es el tipo de error definido por Go al principio, que unifica el modo de devolución de error a nivel de función dentro del proceso de una manera estándar del sistema. La persona que llama utiliza el patrón unificado de if err != nil para determinar si una llamada fue exitosa.

Sin embargo, con la promoción gradual de Go, debido al alto grado de libertad de la interfaz de error, los programadores tienen opiniones diferentes sobre "cómo determinar cuál es el error".

Antes de Go 1.13, había tres modos comunes para pasar tipos de error:

Este género es muy simple, es decir, varios mensajes de error se definen directamente como un modo de enumeración de clase. , como por ejemplo:

Cuando encuentre el mensaje de error correspondiente, simplemente devuelva directamente el valor de enumeración de la clase de error correspondiente. También es muy conveniente para la persona que llama. Puede usar switch-case para determinar el tipo de error:

Personalmente, creo que este patrón de diseño es esencialmente el patrón de código de error de C.

Este género hace pleno uso de la función "el error es una interfaz" y redefine un tipo de error. Por un lado, se utilizan diferentes tipos para representar diferentes categorías de error y, por otro lado, el mismo tipo de error se puede proporcionar a la persona que llama con información mejor y más detallada. Por ejemplo, podemos definir varios tipos de errores diferentes de la siguiente manera:

Para la persona que llama, se utiliza el siguiente código para determinar diferentes errores:

En este modo, por un lado, Transmite de forma transparente los errores subyacentes y, por otro lado, puedes agregar información personalizada. Pero para la persona que llama, el desastre es que si desea determinar el tipo específico de un error, solo puede usar strings.Contains(), y el texto de descripción específico del error no es confiable y el mismo tipo de información puede tener Expresión diferente; en el proceso de fmt.Errorf, la información adicional agregada por cada negocio también puede tener texto diferente, lo que genera una gran falta de confiabilidad y mejora el acoplamiento entre módulos.

Después del lanzamiento de la versión go 1.13, se agregó la función de ajuste a fmt.Errorf y las funciones Is() y As() se agregaron al paquete de errores. Hay muchos artículos sobre el principio y el uso de este modo, por lo que no entraré en detalles en este artículo.

Esta función fusiona y transforma los llamados géneros "== género" y "fmt.Errorf" mencionados anteriormente, y utiliza la función errores.Is () de manera uniforme, además, también se puede considerar; como reconocimiento oficial del género de aserción de tipo (especialmente respaldado por la función As()).

En aplicaciones reales, cuando las funciones/módulos transmiten errores de forma transparente, se debe utilizar el modo de ajuste de errores de Go, es decir, fmt.Errorf() se utiliza junto con w. La parte empresarial puede agregar el suyo propio de forma segura. información de error Siempre que la persona que llama utilice errores.Is() y errores.As() de manera uniforme.

Para los mensajes de error devueltos a nivel de servicio/sistema, la mayoría de los protocolos pueden considerarse como el modo código-mensaje o sus variantes:

Las características de este modo son: el código es es utilizado por el código del programa. El código determina qué tipo de error es e ingresa a la rama correspondiente para su procesamiento. El mensaje es para que la gente lo vea.

¿Cuáles son los problemas a este nivel? El código para computadora, mensaje para usuario, parece ser bastante bueno.

Pero a veces, podemos recibir comentarios de usuarios/clientes: "XXX informó un error, ¿puedes ayudarnos a descubrir cuál es el problema?". ¿No pueden los usuarios entender nuestro mensaje de error?

Según la experiencia del autor, cuando utilizamos el mecanismo de mensaje de código, especialmente en las primeras etapas del negocio, es inevitable que la copia del diseño de front-end y back-end no cubra completamente todos los errores. casos, o los errores son extremadamente raros. Por lo tanto, cuando ocurre un error, el mensaje es ambiguo (o incluso muestra directamente el mensaje de error), lo que hace que el usuario encuentre la solución en el mensaje de error

En este caso, definitivamente es lo más perfecto cubra todas las rutas de error tanto como sea posible. Pero antes de hacer esto, los programadores suelen tener las siguientes soluciones:

Para ocultar y exponer información, ¿puedo tirar el plato...?

Aquí, el autor se inspiró en el Código de verificación por SMS cada vez más popular: la memoria humana a corto plazo es relativamente fuerte para 4 caracteres, por lo que podemos considerar acortar el código de error a 4 caracteres; no distingue entre mayúsculas y minúsculas, porque si las personas también necesitan registrar la parte superior e inferior En este caso, al memorizar, la dificultad aumentará mucho.

¿Cómo utilizar 4 caracteres para representar la mayor cantidad de datos posible? Hay un total de 36 caracteres en números y letras. En teoría, se puede utilizar hexadecimal de 4 dígitos para representar 36x36x36x36 = 1679616 valores. Entonces solo necesitamos encontrar un algoritmo hash para la cadena del mensaje de error y limitar el valor de salida al rango de 1679616.

Aquí utilizo MD5 como ejemplo. La salida de MD5 es de 128 bits. En teoría, puedo tomar la salida de MD5 y el módulo 1679616 para obtener un resultado simple. De hecho, para reducir las operaciones de división, utilicé un método simple para tomar los 20 bits superiores (0xFFFFF) (el valor máximo del binario de 20 bits es 1048575) y luego convertí este número en una cadena de 36 dígitos para la salida. .

Cuando ocurre un error anormal, podemos mostrar la siguiente información del mensaje: "Error desconocido, código de error 30EV, si necesita ayuda, comuníquese con XXX". Por cierto, 30EV es el resultado del cálculo de "Acceso denegado para el usuario 'db_user'@'127.0.0.1'". De esta manera, oculto información confidencial a la persona que llama.

En cuanto al backend, todavía es necesario registrar el valor hash y la información de error específica en registros u otros canales que admitan la búsqueda. Cuando el usuario proporciona este código, podrá localizarlo rápidamente.

Las ventajas de esta solución son obvias:

El código de generación de código de error simple es el siguiente:

Por supuesto, esta solución también tiene limitaciones. Lo que el autor puede pensar es Es necesario tener en cuenta los siguientes dos puntos:

Además, el autor debe enfatizar que durante el desarrollo, varios casos de uso de errores formales aún deben cubrirse por completo y tanto como sea posible. a través de mensaje de código existente El mecanismo comunica información suficientemente clara a la persona que llama. Este método de generación de código de error de código hash solo es adecuado para casos de uso de errores faltantes o es una solución temporal para descubrir y depurar casos de uso de errores perdidos durante una iteración rápida.