Dart pedantic
En el package:pedantic de Dart, hemos estado recolectando lentamente una lista de lints precisamente correctos que puedes aplicar a tu código Dart.
Flutter y Flutter Web están generando mucho entusiasmo, y merecidamente; están empujando los límites del desarrollo de UI. Flutter está escrito en Dart, y Dart acaba de ganar varias funcionalidades bajo el estandarte “UI as Code” que traerán alegría al día de cada desarrollador de Flutter. Son tiempos emocionantes.
Pero, ¡espera! No todo debería moverse rápido. A veces vale la pena ser meticuloso, exigente, escrupuloso, puntilloso, o — me atrevo a decirlo — pedante. Así que, en el package:pedantic de Dart, hemos estado reuniendo lentamente una lista de lints precisamente correctos que puedes aplicar a tu código.
Por supuesto, para comprobar lints necesitas un linter. El linter de Dart está integrado en el dart analyzer, lo que significa que sus 146 lints (mientras escribo esto) están disponibles en todas partes donde los quieras: en la línea de comandos, en tu presubmit, y en tu IDE. Como desarrollador de Dart (o Flutter) tienes cientos de lints a tu alcance; el único problema es decidir cuáles lints habilitar.
Este es un problema más difícil de lo que parece. Si todo lo que quieres es usar los lints recomendados por package:pedantic, no necesitas seguir leyendo; simplemente sigue estas instrucciones.
¿Aún conmigo? Genial. Ahora, vamos a profundizar en por qué no puedes simplemente habilitar los 146 lints y empezar a programar. Empezaremos con las razones más simples, e iremos subiendo. Luego veremos exactamente cómo se evalúa un lint para su inclusión en package:pedantic, trabajaremos a través de un ejemplo de un lint que fue particularmente problemático de enforce, y terminaremos con algunas indicaciones sobre cómo podrías involucrarte.
Lints obsoletos
#Algunos lints ya no tienen sentido. Un ejemplo es super_goes_last, que requiere que
super se coloque al final si aparece en una lista de inicializadores. En realidad era un lint tan útil que con Dart 2 se convirtió en un requisito del lenguaje, así que el lint ya no es necesario.
// super_goes_last; now included in Dart 2, no need for a lint.
View(Style style, List children)
: super(style), // LINT
_children = children {
Lints contraindicados
#Algunos lints no están realmente destinados al consumo público. En particular, varios lints que están diseñados para usarse dentro del SDK de Flutter contradicen directamente la guía de estilo de Dart. Un ejemplo es always_specify_types; sobre-especificar tipos significa perder los beneficios de la inferencia de tipos. La guía de estilo explica cuidadosamente cómo lograr un buen equilibrio entre anotaciones de tipos e inferencia de tipos.
// always_specify_types; do not use; breaks with recommended style!
var foo = 10; // LINT
final bar = new Bar(); // LINT
const quux = 20; // LINT
Lints costosos
#Evaluar lints implica cómputo arbitrario sobre una estructura de datos compleja — tu código fuente. ¿Cómo sabes que no te van a ralentizar? Por supuesto, se hace todo lo posible al añadir lints para mantenerlos eficientes; pero pueden surgir sorpresas. El lint library_prefixes tenía un bug de rendimiento que solo aparecía en casos muy oscuros; ya está arreglado, por supuesto.
// library_prefixes; performance issue was fixed, now good to go!
import 'dart:math' as Math; // LINT
import 'dart:json' as JSON; // LINT
import 'package:js/js.dart' as JS; // LINT
Lints redundantes
#La mayoría de los lints son extremadamente rápidos de calcular, pero no son del todo gratis. Así que un lint necesita justificar su peso; necesita ofrecer suficiente valor. Por ejemplo, rechazamos el lint
empty_statements bajo el argumento de que usar
dartfmt hace que las sentencias vacías sean fáciles de detectar. Es poco probable que se escriban por error, y por lo tanto el lint es redundante.
// empty_statements; considered redundant with dartfmt.
if (complicated.expression.foo()) ; // LINT
Lints demasiado entusiastas
#Algunos lints no son lo suficientemente precisos para ser enforced. Por ejemplo, omitir tipos para variables locales en Dart es buen estilo, pero solo la mayoría del tiempo. Es una recomendación, no una regla estricta. Eso hace que el lint correspondiente omit_local_variable_types sea demasiado estricto para ser enforced en todas partes.
// omit_local_variable_types; too strict. Local variable types
// are good style where they improve readability.
void myMethod() {
MyType bar = expression.methodCall().otherMethodCall(); // LINT
}
Lints opinionados
#Algunos lints empujan el código en una dirección que no es incorrecta, pero que sin embargo es inusual. Un ejemplo es
prefer_final_locals, que requiere que las variables locales sean declaradas, si es posible, como
final. Este es un estilo que algunos desarrolladores prefieren, pero no es lo que la mayoría de desarrolladores de Dart prefieren, y por lo tanto por defecto el lint debería estar desactivado.
// prefer_final_locals; inconsistent with common style.
void myMethod() {
var label = 'foo'; // LINT
}
Evaluando todos los lints
#Puede parecer que, incluso teniendo todo esto en cuenta, alguien con un conocimiento cercano de Dart debería poder sentarse con la lista de 146 lints disponibles y producir una lista de lints recomendados sin demasiados problemas.
Pero eso no es lo que hemos encontrado; es simplemente una tarea demasiado grande. Así como cada lint puede hacer evaluación arbitraria en tu código, decidir si un lint en particular es tanto correcto como útil resulta ser casi arbitrariamente difícil.
Una buena forma de abordar una pregunta difícil es pedir datos difíciles. Así que al considerar cada lint, primero medimos su rendimiento y reunimos información sobre todas las violaciones del lint actualmente presentes en el código interno de Dart de Google.
Estos números nos dan un buen punto de partida para la discusión.
Si, por ejemplo, todo el código de Dart de Google contiene solo cinco violaciones del lint, entonces cada una mejor que sea un bug serio; de lo contrario, es poco probable que el lint justifique su peso. El lint recursive_getters fue un ejemplo raro de un lint que detectó un número muy pequeño de problemas serios; un getter que se llama a sí mismo es un stack overflow esperando a happen.
// recursive_getters; definitely not what you meant to write!
int get field => field; // LINT
Si, por otro lado, encontramos muchas violaciones del lint, la pregunta se invierte: el lint va a cambiar lo que muchos desarrolladores están haciendo, así que ¿estamos seguros de que es una mejora, tanto globalmente como en cada caso individual? Si el lint empeorará un pequeño número de casos, ¿podemos justificarlo? O, quizás, ¿podemos mejorar el lint?
El lint unrelated_type_equality_checks
es un buen ejemplo. Este lint requiere que, antes de que se te permita comparar dos objetos, deben ser de tipos estáticos compatibles. Así que no se te permite comprobar si
3 y foo son iguales; se asume que porque uno es un int y el otro es un
String que esta pregunta ni siquiera tiene sentido.
// unrelated_type_equality_checks; or, don't ask stupid questions!
void someFunction() {
var x = '1';
if (x == 1) print('surprise!'); // LINT
}
Esto suena bien, pero no es correcto por dos razones.
Falla en teoría por culpa de implements; un objeto puede ser de más de un tipo, y así dos objetos que aparecen estáticamente como no relacionados podrían resultar en tiempo de ejecución implementar el mismo tipo, y ser perfectamente válidos de comparar.
// unrelated_type_equality_checks; objects _can_ hold surprises.
void checkForSurprise(Foo foo, Bar bar) {
if (foo == bar) print('surprise!'); // LINT
}
abstract class Foo {}
abstract class Bar {}
class Baz implements Foo, Bar {}
void main() {
var baz = Baz();
checkForSurprise(baz, baz);
}
Falla en la práctica porque operator== se deja a cada autor de clase para implementar, y nada les fuerza a seguir su contrato. Encontramos, en particular, que
Int64 y Int32 de package:fixnum permiten comparaciones con int, pero solo cuando el
int está en el lado derecho del ==.
Así que, los datos mostraron tres resultados para el lint: muchos hallazgos correctos (bugs detectados), un pequeño número de hallazgos incorrectos debido a que los tipos en tiempo de ejecución eran compatibles cuando los tipos estáticos no lo eran, y un número relativamente grande de hallazgos incorrectos debido a que
Int64 era comparado con int.
¿Qué hicimos? Mejoramos el lint: ahora sabe sobre Int64 (y Int32) y te permite compararlo con
int como hacías antes. Esto dejó un número muy pequeño de falsos positivos debido a que los tipos estáticos eran incompletos; estos optamos por refactorizar, usando casts cuando fue necesario, para hacerlos cumplir con el lint.
Con estos cambios, pudimos llegar a un consenso sobre hacer que unrelated_type_equality_checks se enforce.
Con “nosotros” en este caso me refiero a “el conjunto de todos los desarrolladores de Google a quienes les importan los lints de Dart.” Cualquiera en Google que esté escribiendo Dart puede involucrarse en este proceso, y muchos lo hacen, así que obtenemos mucha retroalimentación — particularmente cuando un lint es controvertido!
Si y cuando hay consenso sobre un lint, lo siguiente que pasa es que la persona que propuso enforce el lint limpia todo el código interno de Dart de Google para pasar el lint. A veces aprendemos algo nuevo durante este proceso; si se nos pasó algo que haría que la limpieza sea un cambio difícil o que rompa cosas, la limpieza típicamente se pausa. Ahora mismo tenemos suficientes lints en los que trabajar como para simplemente saltar esos casos y volver a ellos más tarde.
Una vez que un lint se ha limpiado con éxito en todas partes, entonces se enforce en el presubmit, previniendo cualquier violación adicional en el código interno de Google, y se publica en la siguiente versión de package:pedantic.
El lint unawaited_futures fue un caso aún más difícil. Este lint aborda lo que solía ser una queja muy común de los desarrolladores: olvidar hacer
await a un Future, causando comportamiento impredecible en tiempo de ejecución y tests inestables.
// unawaited_futures; catching accidentally asynchronous behaviour.
Future<void> doSomething() => ...;
Future<void> doSomethingElse() => ...;
void main() async {
doSomething(); // LINT
doSomethingElse(); // LINT
}
Pero el lint es problemático porque sabemos que hay casos en los que sí quieres iniciar un
Future y luego continuar sin esperar a que se complete. Un ejemplo es el logging: típicamente está bien saber que el logging se completará en algún momento, sin necesidad de esperarlo.
El lint ofrecía un valor tremendo pero no podía ser hecho correcto. Discutimos durante mucho tiempo cuál era el mejor camino. Los lints de Dart se pueden ignorar escribiendo
// ignore: <lint_name> en la línea precedente, así que enforcear un lint nunca es
realmente bloqueante para los desarrolladores. Pero, realmente no queríamos entrenar a los desarrolladores para que escribieran ignore; la mayoría de los lints que enforceamos
son siempre correctos, y nunca deberían ser ignorados.
Esta discusión es en realidad lo que llevó a la creación de package:pedantic en primer lugar. Queríamos proporcionar la forma canónica de decir, “Sé que existe el lint unawaited_futures, y no aplica aquí.” Eso es ahora el método
unawaited en package:pedantic. Habiéndolo publicado, actualizamos el mensaje y la documentación del lint unawaited_futures para apuntar a
unawaited, y ahora estamos en lo que esperamos que sea un lugar razonablemente bueno: tenemos un lint que a veces podrías necesitar desactivar, y una forma canónica y legible de hacerlo.
// unawaited_futures; say `unawaited` if that's what you wanted.
Future<void> doSomething() => ...;
Future<void> doSomethingElse() => ...;
void main() async {
unawaited(doSomething());
unawaited(doSomethingElse());
}
Esto fue suficiente para llegar a un consenso sobre enforcear el lint unawaited_futures.
Paso a paso hacia un linting perfecto de Dart
#Y así el proceso continúa. Ahora estamos en 25 lints habilitados y ocho lints explícitamente deshabilitados, así que no estamos ni a un cuarto de camino de los 146 lints disponibles. Por supuesto, la gente sigue añadiendo nuevos lints — el diseño del linter hace esto fácil de hacer — así que es un objetivo en movimiento.
Me encantaría poder agitar una varita mágica y proporcionar la lista perfecta de lints; espero que este artículo haya explicado por qué eso no es posible, y que estamos trabajando en ello.
Finalmente, si ya existen lints que te gustaría ver habilitados sooner rather than later, eres bienvenido a usar el
issue tracker de pedantic para hacerlo saber; tenemos en cuenta los issues al mirar qué abordar a continuación. Desafortunadamente, dado que el código interno de Google es clave para decidir qué lints habilitar, no podemos hacer todo el proceso transparente, pero apuntamos a ser tan abiertos como sea posible en nuestras discusiones en GitHub. En particular, podemos dar retroalimentación, en la medida de lo posible, sobre qué es probable que entre en package:pedantic y cuándo.
Si estás interesado en contribuir con nuevos lints o mejorando los existentes, ¡involúcrate en GitHub!
Más de Dart
Dart en Google Summer of Code 2026
¡Estamos emocionados de anunciar que el proyecto Dart será mentor de contribuidores por séptima vez en Google Summer of Code 2026!
Resultados de Google Summer of Code 2025
El programa Google Summer of Code (GSoC) se enfoca en involucrar a nuevos desarrolladores de todo el mundo con el desarrollo de software de código abierto. Google…