Un linter es una herramienta de programación cuyo nombre original -lint- era el nombre de una herramienta de programación utilizada para detectar código sospechoso, confuso o incompatible entre distintas arquitecturas en programas escritos en C, es decir, errores de programación que escapan al habitual análisis sintáctico que hace el compilador.
Actualmente, el término linter es usado para hacer referencia a herramientas que realizan estas tareas de análisis estático del código fuente, indistintamente del lenguaje de programación.
Al fin y al cabo, probablemente la mayoría de los que estáis leyendo estas líneas ya os habéis peleado anteriormente con algún linter, o por lo menos habéis visto alguno en funcionamiento. Así que, no hace falta que nos esforcemos en intentar definir qué es un linter, nos podemos servir con la anterior definición proporcionada por la Wikipedia y pasar a un enfoque más práctico intentando responder a las siguientes preguntas:
- ¿Vale la pena usar linters?
- ¿Qué linters podemos usar para Go?
Entonces, allá vamos.
¿Vale la pena usar linters?
Es muy probable que algún compañero os haya comentado alguna vez que los linters son un engorro, y que terminan siendo más molestos que útiles, representando un excelente ejemplo de la teoría de las ventas rotas (o como los falsos positivos pueden terminar siendo peor que no tener alertas).
Sin embargo, como muy bien explica Denis Isaev en su charla de la GopherCon 2019 titulada “Go Linters: Myths and Best Practices” (que os recomendamos encarecidamente que veais), hay muchos ejemplos de proyectos reales (incluso del propio core de Go) en los que se han encontrado bugs gracias a los linters. Además, todos tenemos claro el incremento exponencial del coste que supone para nuestras empresas el hecho de encontrar un fallo en producción que podría haber sido encontrado previamente. Finalmente, los linters también nos pueden resultar una fuente de conocimiento, es decir, una forma de aprender a mejorar nuestros desarrollos, pues, al final, no dejan de ser un componente que nos dirá qué cosas estamos haciendo mal o qué cosas podríamos mejorar.
Así qué, ahora que tenemos claro que deberíamos tener, por lo menos, un pequeña batería de análisis estáticos de nuestro código que nos ayuden a detectar posibles errores que nunca nos detectará un compilador, veamos que opciones tenemos para realizar dichos análisis en nuestros proyectos Go.
¿Qué linters podemos usar para Go?
Como sucede con la mayoría de lenguajes de programación, en el ecosistema de Go también se nos plantean múltiples opciones
cuándo nos planteamos buscar un linter. Y, también como es habitual en el ecosistema Go, el runtime del propio lenguaje
nos proporciona dicha herramienta: go vet
.
Como veremos a continuación, go vet
es una herramienta que a pesar de su sencillez, es bastante completa. Pero si queremos
ir aún más allá, veremos que tenemos muchísimas más opciones: errcheck, staticcheck,
deadcode, check, y mucho más.
De hecho, incluso tenemos algún que otro agrupador de linters (también conocidos como linters runners) como golangci-lint,
popularmente conocido por ser utilizado por empresas como Google, Facebook, Red Hat, Yahoo, IBM, Xiaomi, etc.
En definitiva, el listado de alternativas es tan extenso que incluso podemos encontrar opciones en formato SaaS, es decir, aplicaciones web que tienen ese propósito específico, como por ejemplo GolangCI. Además, también nos podemos encontrar con alternativas como la presentada por Julie Qiu en la pasada GopherCon UK: Go Report Card, directamente desarrollada por el equipo de Go en Google.
Go Vet
Como decíamos, Vet se trata del linter por defecto en el ecosistema Go, al igual que sucede con gofmt
o con go fmt
y desde Google la definen basicamente como una herramienta que examina el
código fuente Go e informa sobre sentencias sospechosas. Internamente usa diferentes heurísticas sobre las que podemos
obtener más información ejecutando el comando go tool vet help
, el cuál nos mostrará todos los analyzers usados internamente:
asmdecl report mismatches between assembly files and Go declarations
assign check for useless assignments
atomic check for common mistakes using the sync/atomic package
bools check for common mistakes involving boolean operators
buildtag check that +build tags are well-formed and correctly located
cgocall detect some violations of the cgo pointer passing rules
composites check for unkeyed composite literals
copylocks check for locks erroneously passed by value
httpresponse check for mistakes using HTTP responses
loopclosure check references to loop variables from within nested functions
lostcancel check cancel func returned by context.WithCancel is called
nilfunc check for useless comparisons between functions and nil
printf check consistency of Printf format strings and arguments
shift check for shifts that equal or exceed the width of the integer
stdmethods check signature of methods of well-known interfaces
structtag check that struct field tags conform to reflect.StructTag.Get
tests check for common mistaken usages of tests and examples
unmarshal report passing non-pointer or non-interface values to unmarshal
unreachable check for unreachable code
unsafeptr check for invalid conversions of uintptr to unsafe.Pointer
unusedresult check for unused results of calls to some functions
Para ejecutarlo nos bastaría con lanzar el comando go vet ./...
desde el directorio raíz de nuestro proyecto.
GolangCI-lint
Sin embargo, si lo que queremos es ponernos serios e ir más allá, entonces tenemos dos opciones:
- empezar a investigar las múltiples opciones que planteamos anteriormente como complemento a
go vet
- o ir a por el linter por excelencia en Go: golangci-linter, el linter runner anteriormente mencionado
Como dijimos, este último se trata de un software utilizado por muchas de las compañías tecnológicas más reconocidas, y es que se trata de un proyecto muy ambicioso que en realidad se compone de infinidad de linters:
$ golangci-lint help linters
Enabled by default linters:
deadcode: Finds unused code [fast: true, auto-fix: false]
errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
gosimple: Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
structcheck: Finds unused struct fields [fast: true, auto-fix: false]
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
unused: Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
varcheck: Finds unused global variables and constants [fast: true, auto-fix: false]
Disabled by default linters:
bodyclose: checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
dogsled: Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
dupl: Tool for code clone detection [fast: true, auto-fix: false]
funlen: Tool for detection of long functions [fast: true, auto-fix: false]
gochecknoglobals: Checks that no globals are present in Go code [fast: true, auto-fix: false]
gochecknoinits: Checks that no init functions are present in Go code [fast: true, auto-fix: false]
goconst: Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
gocritic: The most opinionated Go source code linter [fast: true, auto-fix: false]
gocyclo: Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
godox: Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
gosec (gas): Inspects source code for security problems [fast: true, auto-fix: false]
interfacer: Linter that suggests narrower interface types [fast: false, auto-fix: false]
lll: Reports long lines [fast: true, auto-fix: false]
maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
stylecheck: Stylecheck is a replacement for golint [fast: false, auto-fix: false]
unconvert: Remove unnecessary type conversions [fast: true, auto-fix: false]
unparam: Reports unused function parameters [fast: false, auto-fix: false]
whitespace: Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
Así que la mejor opción es que paséis por su repositorio dónde tenéis un más que interesante “Quick start” que os dejará con ganas de más, y que si salís convencidos, véais como instalar la herramienta y cómo integrarla con vuestro IDE favorito.
Finalmente, podéis echar un vistazo a esta web y/o a su repositorio, dónde encontraréis un extenso listado de linters y otras herramientas que nos ayudarán a realizar análisis de nuestro código tan extensivos como deseemos.
¿Y vosotros? ¿Usáis linters en vuestros proyectos Go? ¿Cuál sería vuestra recomendación personal?
Como siempre, estaremos encantados de recibir vuestro feedback en los comentarios del blog o vía Twitter.