Skip to content
ExponaBLOG
FLUTTER · DAY 097 / 100

Strict lints: concepts, trade-offs and production lessons

Let the analyzer review your code.

Expona Admin6 min read

Strict lints: concepts, trade-offs and production lessons

Code quality tools exist for a reason: they catch patterns before they become production incidents. In Flutter teams working at scale—especially in e-commerce, SaaS, and ride-hailing—the difference between a permissive linter configuration and a strict one often shows up as technical debt, inconsistent patterns, and onboarding friction. This article walks through strict lints: concepts, trade-offs and production lessons we've learned running analyzers across teams of 5 to 50+ engineers.

Understanding lints and why they matter

A lint is a static analysis rule that flags code patterns the Dart analyzer considers risky, inefficient, or inconsistent with conventions. Unlike compile errors, lints are warnings. You can ignore them, suppress them, or act on them. In early-stage projects, this flexibility feels good. In production systems with multiple teams, that flexibility becomes a liability.

We've seen systems where:

  • Developers suppress lints individually without team consensus, creating inconsistency.
  • New joiners don't know the project's implicit rules and ship code that passes the local setup but fails CI.
  • Null safety violations hide in plain sight because the team never enabled strict null checks.
  • Performance anti-patterns accumulate—unused imports, unnecessary rebuilds, inefficient widget hierarchies—because nobody ran the analyzer.

Strict lints are a forcing function. They make the team's expectations explicit and automated.

flutter_lints as the baseline

When you run flutter create myapp, Flutter scaffolds your project with a pubspec.yaml that includes flutter_lints. This is the official, Google-maintained lint set for Flutter projects. It covers the essentials: null safety, unused code, async best practices, and common pitfalls.

For most small to medium teams, flutter_lints is sufficient. It's conservative, widely adopted, and doesn't impose opinionated style rules that vary by team. Here's a typical setup:

dart
dev_dependencies:
  flutter_lints: ^3.0.0

Then, in analysis_options.yaml:

dart
include: package:flutter_lints/flutter.yaml

Running flutter analyze will now flag issues across your codebase. The rules cover:

  • Null safety: unnecessary null checks, null-unsafe code.
  • Async: unawaited futures, missing async/await.
  • Dead code: unused variables, unused imports, unreachable statements.
  • Performance: inefficient patterns, like building widgets in tight loops.
  • Consistency: naming conventions, trailing commas (for formatting).

Many teams stop here. In a codebase of 50K–200K lines, flutter_lints catches maybe 80% of what strict analysis would. That remaining 20% often surfaces in code reviews or, worse, production bugs.

Very_good_analysis for stricter rules

If flutter_lints is the guardrail, very_good_analysis is the full safety harness. Maintained by VeryGood Ventures, this package layers on additional rules that enforce stricter patterns. It's especially useful for teams working on high-risk, high-velocity projects like financial trading platforms, ride-hailing apps, or enterprise SaaS systems where code consistency directly impacts reliability.

To use it:

dart
dev_dependencies:
  very_good_analysis: ^5.0.0

And update analysis_options.yaml:

dart
include: package:very_good_analysis/analysis_options.yaml

This adds rules that catch patterns like:

  • Missing documentation on public APIs.
  • Overly complex functions (cyclomatic complexity).
  • Unused parameters and local variables.
  • Invariant violations in widget construction.
  • Type annotations on all public methods (no implicit dynamic).
  • Proper error handling in futures and streams.

Here's a real example: a team building a CRM platform enabled very_good_analysis and immediately discovered 40+ functions with no documentation, 15 methods with implicit return types, and three places where exceptions were silently swallowed. In code review, these might have been addressed eventually. In production, they become onboarding friction and maintenance debt.

Trade-off: strict analysis is noisier. Your IDE will show more warnings. Initial setup takes longer. But that noise is signal; it's preventing decisions that compound.

Fix warnings, don't ignore them

The most dangerous habit is the suppress comment:

dart
// ignore: lines_longer_than_80_chars
final String veryLongVariableNameThatExceedsLineLimit = "...";

// ignore: todo
// TODO: handle this case later

// ignore: unused_element
void _internalHelper() { }

Each ignore is a small technical debt token. It says, "we acknowledge this is a problem, but we're not fixing it." In isolation, one or two ignores are fine. But in a team of 10+ engineers, if everyone treats warnings as optional, you end up with hundreds of ignores scattered across the codebase.

We worked with a travel platform that had 287 ignore comments. When we audited them:

  • 143 were legitimate suppression of overly strict rules (e.g., documentation for private helpers).
  • 89 were outdated and no longer needed.
  • 55 masked real issues: unused code that had been refactored elsewhere, async operations that weren't properly awaited.

The fix: make it a rule that every ignore comment requires a PR review justification. Or better, configure your lints to allow legitimate exceptions without requiring ignore comments. For example, you can exclude private methods from documentation requirements:

dart
linter:
  rules:
    - public_member_api_docs
    
analyzer:
  exclude:
    - "**/*.g.dart"
    - "**/*.freezed.dart"

This approach forces a conversation: "Do we really need this suppression, or should we fix the code?"

Run analysis in CI

The most impactful step is automation. On your local machine, flutter analyze is optional. It's easy to skip when you're rushing to ship a feature. But if analysis runs in CI and blocks merge, it becomes non-negotiable.

Here's a GitHub Actions workflow excerpt:

dart
- name: Run analysis
  run: flutter analyze --no-pub
  
- name: Check formatting
  run: dart format --output=none --set-exit-if-changed .

Set it up so that:

  • Analysis must pass before merging.
  • The PR shows analysis warnings as comments, so reviewers see them.
  • Formatting is checked automatically (use dart format, not manual review).
  • New lint violations cause the build to fail (no regression).

In practice, this shifts the burden from code review (where it's expensive and human) to automation (where it's instant and consistent). Code reviewers focus on logic, architecture, and team patterns. The analyzer handles the mechanical stuff.

A ride-hailing team we advised implemented this and saw two effects: first, PR cycle time dropped 15% because reviewers didn't spend cycles on formatting and null safety. Second, onboarding time for new engineers dropped from 3 days to 1, because the CI feedback loop was immediate and clear.

Common mistakes

  • Enabling strict analysis mid-project without a fix plan. If you have 500 warnings, enabling very_good_analysis all at once is demoralizing. Instead, turn on one rule at a time, fix the violations, and commit. Or use lint rules that allow gradual adoption.
  • Customizing rules without team consensus. Every team member should understand why a lint exists. If half the team thinks a rule is pointless, it will be ignored. Discuss it in a meeting, document the decision, and make it a team norm.
  • Ignoring the analyzer output locally. If you're not running flutter analyze regularly, you'll discover issues late. Make it a pre-commit hook or a habit before pushing.
  • Suppressing rules that affect readability. For example, some teams disable line-length rules. This creates inconsistency. Instead, configure a reasonable limit (100–120 chars) and stick to it.
  • Forgetting to update analysis_options.yaml when upgrading packages. New versions of flutter_lints sometimes add rules. Review the changelog and decide if you want to adopt them or explicitly disable them.

Key takeaways

  • Start with flutter_lints to establish a baseline. It's official, stable, and widely supported.
  • Use very_good_analysis for higher-risk projects where consistency and maintainability are critical. The trade-off is worth it for teams of 5+ engineers.
  • Treat lint warnings as genuine feedback, not noise. Fix them, don't suppress them. If a rule feels pointless, discuss it as a team and disable it explicitly.
  • Run analysis in CI and make it a blocker. Automation is your friend; it removes decision fatigue and ensures consistency across the team.
  • Document your lint configuration so new team members understand the "why" behind the rules. A well-reasoned, transparent linting strategy builds trust and reduces friction.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles