On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a Lint?
- 2. Where the Rules Come From
- 3. Core vs Recommended
- 4. Enabling Extra Rules
- 5. Two Rules That Catch Real Bugs
- 6. Turning a Rule Off
- 7. Changing a Rule's Severity
- 8. Effective Dart: Four Guides
- 9. Style — Naming
- 10. Documentation
- 11. Usage
- 12. Design
- 13. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 117 — Linting & Effective Dart
| Category | 19 — Dart Developer Tools |
| Lecture | 117 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Advanced |
A program can compile, run and print the right answer — and still be badly named, hard to read, or one forgotten
awaitaway from a bug. Lint rules catch those problems, and Effective Dart explains the style they enforce.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every message in this lecture is real. Each one was produced by running
dart runanddart analyzeon the code shown, on Dart 3.12.2. Every program lives inbin/main.dartof a project made withdart create.
Prerequisites
Students should already understand, from Lectures 001–116:
analysis_options.yamlanddev_dependenciesin a project (Lectures 002, 104)- running
dart analyzeand readingerror,warningandinfolines (Lecture 114) - naming variables, and
final(Lectures 004, 006);dynamic(Lecture 011) - exceptions versus errors (Lectures 083–084), and
await(Lecture 089)
Connection With Previous Lectures
In Lecture 114, dart analyze printed lines ending in a name like
- non_constant_identifier_names. Lecture 084 promised the optional rule
avoid_catching_errors; Lecture 089 switched on unawaited_futures. But where do these
rules come from, who chose them, and how do you choose your own?
Learning Objectives
By the end of this lecture you will be able to:
- Explain what a lint is and how it differs from a compile error
- Distinguish the core and recommended rule sets from
package:lints - Enable extra rules in
analysis_options.yaml - Disable a rule for a project, a file or a single line
- Change a rule's severity with
analyzer: errors: - Name Effective Dart's four guides: Style, Documentation, Usage and Design
- Apply the Dart naming rules for types, variables and files
- Write a program that passes
dart analyzewith a strict rule set
1. What Is a Lint?
A lint is a warning about code that is legal but probably wrong, unclear, or against the common style. Each check is a lint rule with a snake_case name.
Analogy — a word processor underlines broken spelling in red and grammar suggestions in blue. Compile errors are the red lines; lints are the blue ones.
A lint never stops dart run; it appears in dart analyze and your editor.
2. Where the Rules Come From
dart create puts two things in every project. In pubspec.yaml, a dev dependency
(Lecture 104):
dev_dependencies:
lints: ^6.0.0And in analysis_options.yaml, one line that switches on that package's rules:
include: package:lints/recommended.yamlThis program runs fine:
const MAX_MARKS = 100;
void main() {
var Total_marks = 80 + 75 + 85;
print('Total: ' + Total_marks.toString() + ' of ${MAX_MARKS * 3}');
}Total: 240 of 300But dart analyze finds three problems:
info - bin/main.dart:1:7 - The constant name 'MAX_MARKS' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - constant_identifier_names
info - bin/main.dart:4:7 - The variable name 'Total_marks' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - non_constant_identifier_names
info - bin/main.dart:5:9 - Use interpolation to compose strings and values. Try using string interpolation to build the composite string. - prefer_interpolation_to_compose_strings
3 issues found.The last word of each line is the rule's name.
3. Core vs Recommended
package:lints ships two sets:
| Set | Size | Contains |
|---|---|---|
core.yaml | about 35 rules | the most critical checks; pub.dev uses them to score packages |
recommended.yaml | core + about 60 more | adds style and readability checks |
Switch the same project to the smaller set:
include: package:lints/core.yaml info - bin/main.dart:4:7 - The variable name 'Total_marks' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - non_constant_identifier_names
1 issue found.Two lints vanished: they belong to recommended only. Keep recommended.
4. Enabling Extra Rules
The linter knows far more rules than either set includes. Add your own under linter: →
rules:, one per line:
include: package:lints/recommended.yaml
linter:
rules:
- prefer_final_locals
- always_declare_return_types
- avoid_catching_errors
- unawaited_futuresThis program forgets a return type and never changes marks:
average(List<int> marks) {
var total = 0;
for (final mark in marks) {
total += mark;
}
return total / marks.length;
}
void main() {
var marks = [80, 75, 85];
print('Average: ${average(marks)}');
} info - bin/main.dart:1:1 - Missing type annotation. Try adding a type annotation. - strict_top_level_inference
info - bin/main.dart:1:1 - The function 'average' should have a return type but doesn't. Try adding a return type to the function. - always_declare_return_types
info - bin/main.dart:10:3 - Local variables should be final. Try making the variable final. - prefer_final_locals
3 issues found.total is not flagged — it changes inside the loop. Write double average(...) and
final marks = ...:
No issues found!5. Two Rules That Catch Real Bugs
With the same configuration:
Future<void> saveMarks(String name) async {
await Future.delayed(Duration(milliseconds: 100));
print('Saved marks for $name');
}
Future<void> main() async {
final marks = [80, 75, 85];
try {
print(marks[3]);
} on RangeError {
print('No fourth mark');
}
saveMarks('Ali');
print('Done');
}No fourth mark
Done
Saved marks for Ali info - bin/main.dart:10:5 - The type 'RangeError' should not be caught because it is a subclass of 'Error'. Try removing the catch or catching an 'Exception' instead. - avoid_catching_errors
info - bin/main.dart:13:3 - Missing an 'await' for the 'Future' computed by this expression. Try adding an 'await' or wrapping the expression with 'unawaited'. - unawaited_futures
2 issues found.marks[3] is a bug to fix, not catch (Lecture 084), and Done printed before the save
finished (Lecture 089).
6. Turning a Rule Off
To switch off a rule — even one from recommended — write the rules as a map with
true or false:
include: package:lints/recommended.yaml
linter:
rules:
prefer_final_locals: true
prefer_interpolation_to_compose_strings: falseUse one form or the other: a list of - names, or a map of name: true/false. Never mix
them (see Common Mistakes).
To silence a rule for one line, put // ignore: rule_name on the line above:
void main() {
final marks = [80, 75, 85];
try {
print(marks[3]);
// ignore: avoid_catching_errors
} on RangeError {
print('No fourth mark');
}
}No issues found!// ignore_for_file: rule_name at the top silences the whole file. Use both rarely.
7. Changing a Rule's Severity
Every lint reports as info. The analyzer: → errors: section changes that to warning,
error, or ignore:
include: package:lints/recommended.yaml
analyzer:
errors:
non_constant_identifier_names: error
constant_identifier_names: warning
prefer_interpolation_to_compose_strings: ignoreOn the program from section 2:
error - bin/main.dart:4:7 - The variable name 'Total_marks' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - non_constant_identifier_names
warning - bin/main.dart:1:7 - The constant name 'MAX_MARKS' isn't a lowerCamelCase identifier. Try changing the name to follow the lowerCamelCase style. - constant_identifier_names
2 issues found.dart run still runs the program. But dart analyze now fails — with only info lines
it succeeds. Teams use this to block code that breaks their key rules.
8. Effective Dart: Four Guides
The rules follow Effective Dart, the official style guide on dart.dev. It has four parts:
| Guide | Answers |
|---|---|
| Style | How should code look? Names, formatting, order of imports. |
| Documentation | How do I write comments and doc comments? |
| Usage | Which language feature fits best here? |
| Design | How should my classes and functions look to people who use them? |
Each guideline starts with DO, DON'T, PREFER, AVOID or CONSIDER; many have a matching lint.
9. Style — Naming
| Kind of name | Style | Examples |
|---|---|---|
| classes, enums, typedefs, extensions | UpperCamelCase | StudentReport, OrderStatus |
| variables, functions, parameters, constants | lowerCamelCase | totalMarks, maxMarks |
| files, packages, import prefixes | lowercase_with_underscores | student_report.dart, as math |
DON'T write constants as MAX_MARKS — Dart uses maxMarks. Breaking the other two:
class student_report {}
void main() {} info - bin/main.dart:1:7 - The type name 'student_report' isn't an UpperCamelCase identifier. Try changing the name to follow the UpperCamelCase style. - camel_case_typesA file named Student_Report.dart gets a file_names lint. Formatting is part of Style
too — dart format (Lecture 114) handles it.
10. Documentation
DO use /// for doc comments (Lecture 003), DO start with a one-sentence summary,
and DO put names from your code in [square brackets]. The old /** */ form is flagged:
/** Returns the average of [marks]. */
double average(List<int> marks) {
var total = 0;
for (final mark in marks) {
total += mark;
}
return total / marks.length;
}
void main() {
print(average([80, 75, 85]));
}80.0 info - bin/main.dart:1:1 - Use the end-of-line form ('///') for doc comments. Try rewriting the comment to use '///'. - slash_for_doc_commentsWrite /// Returns the average of [marks]. instead. Use // for notes that explain why.
11. Usage
Three Usage guidelines, all in recommended:
- DON'T use
.length == 0to test emptiness — useisEmpty/isNotEmpty. - DON'T initialize a variable to
null— nullable variables start asnullanyway. - PREFER interpolation over
+to build strings.
void main() {
List<int> marks = [];
String? teacher = null;
if (marks.length == 0) {
print('No marks for ' + (teacher ?? 'Sara'));
}
}No marks for Sara info - bin/main.dart:3:11 - Redundant initialization to 'null'. Try removing the initializer. - avoid_init_to_null
info - bin/main.dart:4:7 - Use 'isEmpty' instead of 'length' to test whether the collection is empty. Try rewriting the expression to use 'isEmpty'. - prefer_is_empty
info - bin/main.dart:5:11 - Use interpolation to compose strings and values. Try using string interpolation to build the composite string. - prefer_interpolation_to_compose_strings
3 issues found.Fixed — same output, no issues:
void main() {
final marks = <int>[];
String? teacher;
if (marks.isEmpty) {
print('No marks for ${teacher ?? 'Sara'}');
}
}Usage also says: be consistent with var or final for locals. prefer_final_locals
enforces a team's choice of final.
12. Design
Design guidelines shape what other code sees: DO name classes with nouns and methods with
verbs, PREFER positive names for booleans (isPassed, not isNotFailed), DO declare
return types, and AVOID dynamic unless you really want to switch off type checks.
Here is why:
class StudentReport {
final List<int> marks;
StudentReport(this.marks);
}
void main() {
dynamic report = StudentReport([80, 75]);
print(report.mark);
}Unhandled exception:
NoSuchMethodError: Class 'StudentReport' has no instance getter 'mark'.The typo mark compiled and crashed at runtime. The optional rule avoid_dynamic_calls
flags that line before you run it (excerpt):
info - bin/main.dart:8:9 - Method invocation or property access on a 'dynamic' target. Try giving the target a type. - avoid_dynamic_callsWith final report = ..., Dart knows the type and the typo becomes a compile error: The
getter 'mark' isn't defined for the type 'StudentReport'.
13. A Real-World Example
Sara's team uses this analysis_options.yaml:
include: package:lints/recommended.yaml
analyzer:
errors:
unawaited_futures: warning
linter:
rules:
- prefer_final_locals
- always_declare_return_types
- avoid_catching_errors
- unawaited_futures
- avoid_dynamic_callsAnd code that follows all four guides:
/// One student's exam result.
class StudentReport {
/// Creates a report for [name] with their [marks].
StudentReport(this.name, this.marks);
final String name;
final List<int> marks;
/// The average of all marks, or 0 when there are none.
double get average {
if (marks.isEmpty) {
return 0;
}
return marks.reduce((a, b) => a + b) / marks.length;
}
/// Whether the average is 50 or more.
bool get isPassed => average >= 50;
}
Future<void> saveReport(StudentReport report) async {
await Future.delayed(Duration(milliseconds: 100));
print('Saved report for ${report.name}');
}
Future<void> main() async {
final reports = [
StudentReport('Ali', [80, 75, 85]),
StudentReport('Zayan', [40, 35, 50]),
];
for (final report in reports) {
final result = report.isPassed ? 'passed' : 'failed';
print('${report.name}: ${report.average.toStringAsFixed(1)} ($result)');
await saveReport(report);
}
}Ali: 80.0 (passed)
Saved report for Ali
Zayan: 41.7 (failed)
Saved report for ZayanNo issues found!All four guides at work: Style names, /// summaries, isEmpty and interpolation (Usage),
typed values and a positive isPassed (Design).
Code Examples
Every example passes dart analyze with the configuration from section 13.
Example 1 — Naming Styles
const maxMarks = 100;
class ExamResult {
final int score;
ExamResult(this.score);
}
void main() {
final aliResult = ExamResult(85);
print('${aliResult.score} / $maxMarks');
}85 / 100Example 2 — An Import Prefix
import 'dart:math' as math;
void main() {
print(math.max(80, 95));
}95Example 3 — final Unless It Changes
void main() {
final marks = [70, 90, 80];
var best = 0;
for (final mark in marks) {
if (mark > best) {
best = mark;
}
}
print('Best: $best');
}Best: 90Example 4 — A Declared Return Type
int totalMarks(List<int> marks) {
var total = 0;
for (final mark in marks) {
total += mark;
}
return total;
}
void main() {
print(totalMarks([80, 75, 85]));
}240Example 5 — A Doc Comment
/// Returns [price] reduced by [percent] percent.
double discounted(double price, double percent) {
return price - price * percent / 100;
}
void main() {
print(discounted(1500, 20));
}1200.0Example 6 — isEmpty and isNotEmpty
void main() {
final cart = <String>[];
print(cart.isEmpty);
cart.add('Notebook');
print(cart.isNotEmpty);
}true
trueExample 7 — Student Marks
void main() {
const passMark = 50;
final marks = {'Ali': 80, 'Sara': 92, 'Zayan': 45};
marks.forEach((name, mark) {
final result = mark >= passMark ? 'pass' : 'fail';
print('$name: $mark ($result)');
});
}Ali: 80 (pass)
Sara: 92 (pass)
Zayan: 45 (fail)Example 8 — Shopping Total Without dynamic
void main() {
final Map<String, double> prices = {'Pen': 50, 'Bag': 1500};
var total = 0.0;
for (final price in prices.values) {
total += price;
}
print('Total: $total');
}Total: 1550.0Example 9 — Bank Balance: Catch Exceptions, Not Errors
void main() {
var balance = 1000.0;
try {
balance += double.parse('250x');
} on FormatException {
print('Invalid amount');
}
print('Balance: $balance');
}Invalid amount
Balance: 1000.0Example 10 — Temperature Log with await
Future<void> logReading(double celsius) async {
await Future.delayed(Duration(milliseconds: 100));
print('Logged $celsius°C');
}
Future<void> main() async {
await logReading(36.6);
print('All readings saved');
}Logged 36.6°C
All readings savedCommon Beginner Mistakes
1. Mixing the list and map forms
include: package:lints/recommended.yaml
linter:
rules:
- prefer_final_locals
prefer_interpolation_to_compose_strings: false # ✗ mixed with "-" entries error - analysis_options.yaml:6:5 - While parsing a block collection, expected '-'. - parse_error2. Misspelling a rule name
include: package:lints/recommended.yaml
linter:
rules:
- prefer_final_local # ✗ missing "s"warning - analysis_options.yaml:5:7 - 'prefer_final_local' isn't a recognized lint rule. Try using the name of a recognized lint rule. - undefined_lint3. Enabling rules that contradict each other
unnecessary_final wants var where prefer_final_locals wants final:
warning - analysis_options.yaml:6:7 - The rule 'unnecessary_final' is incompatible with ''prefer_final_locals''. Try removing all but one of the incompatible rules. - incompatible_lintPick one style per project.
4. Expecting lints to stop dart run
Even a lint raised to error only affects dart analyze.
Best Practices
- Keep
include: package:lints/recommended.yamlin every project. - Add the extra rules your team agrees on, and raise bug-catching ones to
warning. - Run
dart formatanddart analyzebefore every commit; aim forNo issues found!. - Fix rather than
// ignore:— and ignore one line, never the whole file. - Give public classes and functions a
///one-sentence summary.
Mini Challenge
With the configuration from section 13, this program prints the right answer but has
lint issues. Make dart analyze report No issues found! without changing the output:
const START_AMOUNT = 150;
piggyBank(int spent) {
var Balance = START_AMOUNT + 50 - spent;
return 'Zayan has ' + Balance.toString() + ' rupees';
}
void main() {
print(piggyBank(30));
}Zayan has 170 rupeesTry it yourself before opening the solution.
Show solution
const startAmount = 150;
/// Returns Zayan's balance after spending [spent] rupees.
String piggyBank(int spent) {
final balance = startAmount + 50 - spent;
return 'Zayan has $balance rupees';
}
void main() {
print(piggyBank(30));
}Zayan has 170 rupeesNo issues found!Practice Questions
They run from beginner to application to challenge. Use the configuration from section 13.
Beginner
- Which file configures lint rules, and which line turns on the recommended set?
- Give the correct Dart name for: a class "bank account", a variable "total marks", a constant "max marks", and a file "shopping cart".
- A program has three
infolints. Doesdart runstill run it? Doesdart analyzesucceed?
Application
-
Write a
linter:section that enablesprefer_final_localsandavoid_dynamic_calls. -
Write a
linter:section that keepsprefer_final_localson and turnsprefer_interpolation_to_compose_stringsoff. -
Write an
analyzer:section that makesnon_constant_identifier_namesan error and ignoresconstant_identifier_names. -
Fix this program so it has no issues, and keep the output
Cart is empty:void main() { List<String> cart = []; if (cart.length == 0) { print('Cart is ' + 'empty'); } } -
Which lint reports the
on StateErrorline below, and what should you write instead?void main() { final names = ['Ali', 'Sara']; try { print(names.firstWhere((n) => n == 'Zayan')); } on StateError { print('Not found'); } }
Challenge
-
Write a documented function
celsiusToFahrenheitwith a declared return type, and print the result for25. The output must be77.0, with no issues. -
Rewrite this program without
dynamicso that the typo is caught before it runs. Then fix the typo and printBalance: 2000.0.class Account { double balance = 2000; } void main() { dynamic account = Account(); print('Balance: ${account.balanse}'); }
Practice Answers
Question 1 — Solution
analysis_options.yaml, with the line:
include: package:lints/recommended.yamlQuestion 2 — Solution
BankAccount, totalMarks, maxMarks, and shopping_cart.dart.
Question 3 — Solution
Yes, it runs. dart analyze lists three issues but succeeds — they are only info.
Question 4 — Solution
linter:
rules:
- prefer_final_locals
- avoid_dynamic_callsQuestion 5 — Solution
linter:
rules:
prefer_final_locals: true
prefer_interpolation_to_compose_strings: falseQuestion 6 — Solution
analyzer:
errors:
non_constant_identifier_names: error
constant_identifier_names: ignoreQuestion 7 — Solution
void main() {
final cart = <String>[];
if (cart.isEmpty) {
print('Cart is empty');
}
}Cart is emptyNo issues found!Question 8 — Solution
avoid_catching_errors — StateError is an Error. Fix the code instead with orElse
(Lecture 042):
void main() {
final names = ['Ali', 'Sara'];
print(names.firstWhere((n) => n == 'Zayan', orElse: () => 'Not found'));
}Not foundQuestion 9 — Solution
/// Converts [celsius] to degrees Fahrenheit.
double celsiusToFahrenheit(double celsius) {
return celsius * 9 / 5 + 32;
}
void main() {
print(celsiusToFahrenheit(25));
}77.0Question 10 — Solution
With final account = Account();, the typo becomes a compile error: The getter 'balanse'
isn't defined for the type 'Account'. Fixed:
class Account {
double balance = 2000;
}
void main() {
final account = Account();
print('Balance: ${account.balance}');
}Balance: 2000.0Quick Revision
- What is the difference between a lint and a compile error?
- Under which key do you add extra lint rules?
- How do you turn off one rule for the whole project? For one line?
- Which section changes a lint's severity, and what are the four values?
- Name Effective Dart's four guides.
Key Takeaways
- A lint flags legal code that is risky or off-style; it never stops
dart run. package:lintsofferscore(critical) andrecommended(core + style, the default).- Add rules under
linter: rules:, as a list or as atrue/falsemap — never both. analyzer: errors:sets a rule toinfo,warning,errororignore; warnings and errors makedart analyzefail.- Effective Dart has four guides: Style, Documentation, Usage, Design.
- Types are
UpperCamelCase, members and constantslowerCamelCase, fileslowercase_with_underscores. - Avoid
dynamic: typed code turns runtime crashes into compile errors.
What's Next?
Lecture 118 — Introduction to Testing. That closes Category 19. But No issues found!
only means the code is clean — not that it gives the right answers. Next we learn how
automated tests prove that it does.