Contribuya a Go con el analizador go-critico


Quizás recuerde el reciente anuncio de un nuevo analizador estático para Go llamado go-critical .


Verifiqué el proyecto golang / go con él y envié algunos parches que solucionan algunos problemas encontrados allí.


En este artículo, analizaremos el código corregido, y también estaremos motivados para enviar aún más cambios de este tipo a Go.


Para los más impacientes: una lista actualizada de trofeos .



dupSubExpr


Todos cometemos errores y, muy a menudo, por falta de atención. Ir, siendo un idioma en el que a veces tienes que escribir código aburrido y repetitivo, a veces contribuye a errores tipográficos y / o copiar / pegar.


CL122776 contiene una solución para un error encontrado por dupSubExpr :


 func (a partsByVarOffset) Less(i, j int) bool { - return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[i]]) + return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[j]]) // ^__________________________________^ } 

Presta atención al índice a la izquierda y a la derecha. Antes de la corrección, el LHS y el RHS del operador < eran idénticos, y dupSubExpr trabajó para dupSubExpr .


comentóOutCode


Si su proyecto está patrocinado por un sistema de control de versiones , en lugar de deshabilitar el código envolviéndolo en un comentario, vale la pena eliminarlo por completo. Hay excepciones, pero más a menudo un código "inactivo" interfiere, confunde y puede ocultar errores.


commentOutCode podría encontrar un fragmento tan interesante ( CL122896 ):


 switch arch.Family { // ...  case clause. case sys.I386: return elf.R_386(nr).String() case sys.MIPS, sys.MIPS64: // return elf.R_MIPS(nr).String() // <- 1 case sys.PPC64: // return elf.R_PPC64(nr).String() // <- 2 case sys.S390X: // return elf.R_390(nr).String() // <- 3 default: panic("unreachable") } 

Hay un comentario un poco más alto:


 // We didn't have some relocation types at Go1.4. // Uncomment code when we include those in bootstrap code. 

Si cambia a la rama go1.4 y elimina estas 3 líneas del comentario, el código no se compilará, sin embargo, si las descomenta en el asistente, todo funcionará.


Por lo general, el código oculto en un comentario requiere eliminación o activación inversa.


Es útil, de vez en cuando, visitar esos ecos del pasado en su código.


Sobre las dificultades de detección

Este es uno de mis controles favoritos, pero es uno de los más "ruidosos".


Una gran cantidad de falsos positivos para paquetes que usan math/big y dentro del compilador. En el primer caso, estos suelen ser comentarios explicativos sobre las operaciones realizadas, y en el segundo, una descripción del código que describe el fragmento AST. Distinguir tales comentarios del código real "muerto" sin introducir falsos negativos no es trivial.


Esto da lugar a la idea: ¿qué pasa si acordamos de alguna manera especializar el código dentro de los comentarios, lo cual es explicativo? Entonces el análisis estático se simplificará. Esto puede ser un truco que facilitará la definición de dicho comentario explicativo o lo invalidará en el código Go (por ejemplo, si agrega un signo de # , # al comienzo de la línea).


Otra categoría son los comentarios con TODO explícitos. Si el código se elimina para hacer comentarios, pero hay una descripción clara de por qué se hace esto y cuándo se planea arreglar este código, entonces es mejor no dar una advertencia. Esto ya se ha implementado, pero podría funcionar de manera más confiable.


boolExprSimplify


A veces las personas escriben códigos extraños. Tal vez me parece, pero las expresiones lógicas ( booleanas ) a veces se ven especialmente extrañas.


Go tiene un excelente backend de ensamblador x86 (aquí cayó una lágrima), pero ARM realmente hizo mal:


 if !(o1 != 0) { break } 

"Si no, o1 no es igual a 0" ... La doble negación es un clásico. Si te gustó, te invito a que te familiarices con el CL123377 . Allí puedes ver la versión corregida.


Opción corregida (para aquellos que no pueden ser atraídos para revisar)
 - if !(o1 != 0) { + if o1 == 0 { 

boolExprSimplify está dirigido a simplificaciones que aumentan la legibilidad (y el optimizador Go habría hecho frente al problema del rendimiento sin él).


underef


Si usa Go de sus versiones anteriores, puede recordar los puntos y comas obligatorios, la falta de desreferencia automática de punteros y otras características que hoy en día son casi imposibles de ver en el nuevo código.


En el código anterior, aún puede ver algo como esto:


 // -    : buf := (*bufp).ptr() // ...     : buf := bufp.ptr() 

Varios desencadenantes del analizador underef solucionados en CL122895 .


appendCombine


Puede saber que append puede tomar varios argumentos como elementos para agregar al segmento objetivo. En algunas situaciones, esto le permite mejorar ligeramente la legibilidad del código, pero, lo que puede ser más interesante, también puede acelerar su programa, ya que el compilador no suprime las llamadas de append compatibles ( cmd / compile: combine las llamadas de anexos ).


En Go, la comprobación appendCombine encontró la siguiente sección:


 - for i := len(ip) - 1; i >= 0; i-- { - v := ip[i] - buf = append(buf, hexDigit[v&0xF]) - buf = append(buf, '.') - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, '.') - } + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF], + '.', + hexDigit[v>>4], + '.') + } 

 name old time/op new time/op delta ReverseAddress-8 4.10µs ± 3% 3.94µs ± 1% -3.81% (p=0.000 n=10+9) 

Detalles en CL117615 .


rangeValCopy


No es ningún secreto que los valores iterados en un bucle de range se copian. Para objetos pequeños, digamos, menos de 64 bytes, es posible que ni siquiera note esto. Sin embargo, si dicho ciclo se encuentra en una ruta "activa" o, en la que usted repite, contiene una gran cantidad de elementos, la sobrecarga puede ser tangible.


Go tiene un enlazador bastante lento (cmd / enlace), y sin cambios significativos en su arquitectura, no se puede lograr un fuerte aumento de rendimiento. Pero luego puede reducir ligeramente su ineficiencia con la ayuda de microoptimizaciones. Cada porcentaje o dos cuenta.


La verificación rangeValCopy encontró varios ciclos con copia de datos no deseados a la vez. Aquí están los más interesantes de ellos:


 - for _, r := range exports.R { - d.mark(r.Sym, nil) - } + for i := range exports.R { + d.mark(exports.R[i].Sym, nil) + } 

En lugar de copiar R[i] en cada iteración, solo recurrimos al único miembro que nos interesa, Sym .


 name old time/op new time/op delta Linker-4 530ms ± 2% 521ms ± 3% -1.80% (p=0.000 n=17+20) 

La versión completa del parche está disponible en: CL113636 .


namedConst


En Go, desafortunadamente, las constantes con nombre, incluso ensambladas en grupos, no están interconectadas y no forman una enumeración ( propuesta: especificación: agregar soporte de enumeración escrita )


Un problema es emitir constantes sin tipo al tipo que le gustaría usar como enumeración.


Supongamos que define un tipo de Color , tiene un valor de const ColDefault Color = 0 .
¿Cuál de estos dos fragmentos de código te gusta más?


 // (A) if color == 0 { return colorBlack } // (B) if color == colorDefault { return colorBlack } 

Si (B) parece más apropiado, la comprobación de namedConst lo ayudará a rastrear el uso de valores constantes con nombre, omitiendo la constante con nombre en sí.


Así es como se transformó el método context.mangle del paquete html/template :


  s := templateName + "$htmltemplate_" + c.state.String() - if c.delim != 0 { + if c.delim != delimNone { s += "_" + c.delim.String() } - if c.urlPart != 0 { + if c.urlPart != urlPartNone { s += "_" + c.urlPart.String() } - if c.jsCtx != 0 { + if c.jsCtx != jsCtxRegexp { s += "_" + c.jsCtx.String() } - if c.attr != 0 { + if c.attr != attrNone { s += "_" + c.attr.String() } - if c.element != 0 { + if c.element != elementNone { s += "_" + c.element.String() } return s 

Por cierto, a veces en los enlaces a los parches puedes encontrar discusiones interesantes ...
CL123376 es uno de esos casos.


cortar


Una característica de la expresión de corte es que x[:] siempre x[:] idéntico a x si el tipo x es un corte o una cadena. En el caso de cortes, esto funciona para cualquier tipo de elemento []T


Todo en la lista a continuación es lo mismo ( x - slice):


  • x
  • x[:]
  • x[:][:]
  • ...

unslice encuentra expresiones de corte redundantes similares. Estas expresiones son perjudiciales, en primer lugar, con una carga cognitiva adicional. x[:] tiene una semántica bastante significativa en el caso de tomar una porción de la matriz. Un segmento de corte con rangos predeterminados no hace nada excepto ruido.


Pido un parche en CL123375 .


switchTrue


En CL123378 , " switch true {...} " se reemplaza por " switch {...} ".
Ambas formas son equivalentes, pero la segunda es más idiomática.
Encontrado marcando switchTrue .


La mayoría de los controles estilísticos revelan precisamente casos en los que ambas opciones son aceptables, pero una de ellas es más común y familiar para más programadores de Go. Siguiente cheque de la misma serie.


tipoUnparen


Go, como muchos otros lenguajes de programación, ama los paréntesis. Tanto es así que estoy listo para aceptar cualquier número de ellos:


 type ( t0 int t1 (int) t2 ((int)) // ... ,  . ) 

Pero, ¿qué pasa si ejecutas gofmt ?


 type ( t0 int t1 (int) // <- !   . t2 (int) // <-    ... ) 

Por eso existe typeUnparen . Encuentra en el programa todas las expresiones de tipo en las que puede reducir el número de paréntesis. Intenté enviar CL123379 , veamos cómo la comunidad lo aceptará.


A Lisp no le gustan los frenillos

A diferencia de los lenguajes tipo C, en Lisp no es tan fácil insertar paréntesis inútiles en ningún lugar. Entonces, en lenguajes cuya sintaxis se basa en expresiones S , escribir un programa que no hace más que tener una gran cantidad de paréntesis es más difícil que en otros lenguajes.


go-crítico en marcha



Examinamos solo una pequeña parte de los controles implementados. Al mismo tiempo, su cantidad y calidad solo evolucionarán con el tiempo, incluso gracias a aquellas personas que se unieron al desarrollo .


Go-Critic es absolutamente gratuito para cualquier uso ( licencia MIT ), y también abierto para su participación en el desarrollo del proyecto. Envíenos ideas para cheques, puede de inmediato con la implementación, informar sobre errores y deficiencias encontradas, compartir sus impresiones. También puede proponer proyectos para auditoría o informar sobre la revisión de su código Go, esta experiencia es invaluable para nosotros.


Ir tema contribuyente


¿Has visto el artículo Ir taller de contribución en Rusia ? Este otoño será la segunda ronda. Y esta vez, además de un formato y patrocinadores más exitosos, tendremos un arma secreta: un maravilloso analizador estático. ¡Habrá suficientes contribuciones en absoluto!


Pero, de hecho, puede comenzar en este momento (aunque es mejor, un poco más tarde, después de la congelación del código ). Si logras sentirte cómodo antes del próximo taller, será genial, porque tenemos muy pocos mentores en Rusia.

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


All Articles