This guide offers comprehensive insights into writing idiomatic, clean, and maintainable Dart code, aligning with Dart's conventions and best practices.
/// admonition | type: abstract Foundational Code Standards{:target="_blank"} provide the foundation, this guide extends them for Dart. /// //: # (@formatter:on)
The formatting rules for Dart adhere to our foundational formatting standards:
- Consistent Indentation: Use 2 spaces for indentation, 4 spaces for continuation lines.
- Line Length: Aim for 100 characters, but allow flexibility for readability.
- Whitespace: Use spaces around operators, parentheses, braces, colons, commas, and keywords.
- Brace Style: Follow K&R style (opening brace on same line, closing brace on new line).
- Blank Lines: Use 1 line to separate code sections.
- Alignment: Align elements in documentation comments and parameter lists.
Dart employs specific naming conventions to enhance readability and maintain consistency:
- PascalCase for classes, enum types, and type parameters.
- snake_case for libraries, packages, directories, and source files.
- camelCase for functions, variables, properties.
- Prefix booleans with
is
orhas
for clarity.
- Prefix booleans with
- camelCase for functions, variables, constants, and parameters.
- Prefix global constants with a lowercase
k
beforeCamelCase
.
- Prefix global constants with a lowercase
Example
/**
* This class demonstrates proper code formatting following the specified style guide.
*
* **Formatting Rules:**
* - 2 spaces for indentation (Dart standard).
* - Max line length of 100 characters.
* - Spaces around operators, control structures, and keywords.
* - K&R brace style.
* - Consistent spacing for parameter lists and constructor arguments.
* - Doc comments with aligned descriptions.
*/
class WellFormattedCode { // (1)!
/**
* This method calculates the factorial of a given positive integer.
*
* @param n The non-negative integer for which to calculate the factorial.
* @return The factorial of n, or throws an ArgumentError if n is negative.
* @throws ArgumentError If the provided number (n) is negative.
*/
int calculateFactorial(int n) { // (2)!
if (n < 0) { // (3)!
throw ArgumentError('Factorial is not defined for negative numbers.');
}
int result = 1;
for (int i = 2; i <= n; i++) { // (4)!
result *= i;
}
return result;
}
}
- Class name in PascalCase with a doc comment.
- Method name in camelCase with a doc comment.
- K&R brace style for blocks.
- Proper spacing around operators and control structures.
Use DartDoc to document public APIs. Comments should be clear and concise, providing valuable insights into the code's purpose and usage.
-
Use Sparingly: Inline comments should clarify complex algorithms, decisions not immediately obvious, or provide context not readily apparent from the code itself.
// Avoid excessive or obvious comments // Increment goatCount by 1 goatCount += 1;
-
Use
///
for Public APIs: Document classes, methods, variables, and parameters using Dart’s///
syntax. This aids consumers of your code and supports Dart's documentation generation tools./// Represents a goat in a farm management system. /// /// Stores information about the goat's name and feeding status. class Goat { /// The name of the goat. final String name; /// Creates a [Goat] with the given [name]. Goat(this.name); }
-
Describe Parameters and Return Types: For methods and functions, describe each parameter and the return type. Use square brackets
[]
around parameter names to link them within generated docs./// Feeds a [goat] with the specified [food]. /// /// Returns `true` if the goat was fed successfully. bool feedGoat(Goat goat, String food) { // Implementation }
- Reflect Code Changes: Ensure comments are updated with code changes. Outdated comments can mislead and confuse, diminishing code quality.
-
Track Future Enhancements with Ticket Numbers: Use
TODO:
comments to mark areas of the code requiring further work, including a brief description and a ticket number for tracking.// TODO: [TICKET-123] Implement the feed scheduling logic void scheduleFeeding() {}
-
Remove, Don’t Comment-Out: Commented-out code can clutter your codebase. Remove code that's no longer needed or store it elsewhere if it might be useful later.
// Removed outdated goat feeding algorithm
// Avoid leaving commented-out code // feedGoat(goat, "Hay"); // console.log("Feeding completed");
Adhere to the Effective Dart Guidelines for tips on writing clear, idiomatic Dart code.
-
Use
try-catch
for Exception Handling: Leverage Dart's exception handling features to gracefully handle errors and exceptions. Provide clear feedback or recovery options when possible.try { final goat = findGoatByName('Billy'); goat.feed(); } catch (e) { print('Failed to feed goat: $e'); }
try { final goat = findGoatByName('Billy'); goat.feed(); } catch (e) { // Silent catch }
-
Prefer Type Annotations in Public APIs: While Dart supports type inference, explicitly annotating types in public APIs and complex code enhances clarity and ensures that your intentions are clear.
// Good void feedGoat(Goat goat) { // Implementation }
// Avoid in public APIs var feedGoat = (Goat goat) { // Implementation };
-
Favor Immutability for Collections: When collections are not meant to change, use Dart’s built-in support for immutable collections to prevent accidental or unintended modifications.
final List<Goat> goats = const [Goat(name: 'Billy'), Goat(name: 'Daisy')];
List<Goat> goats = [Goat(name: 'Billy'), Goat(name: 'Daisy')]; // Mutable
-
Minimize Use of Global State: Global mutable state can lead to code that is hard to reason about and debug. Prefer passing objects explicitly through function parameters or using dependency injection.
class GoatFeeder { final GoatPen _goatPen; GoatFeeder(this._goatPen); void feedAll() { // Implementation } }
GoatPen globalGoatPen = GoatPen(); void feedAllGoats() { // Implementation using globalGoatPen }
-
Prefer
final
andconst
Where Possible: Usefinal
for variables that you only want to assign once, andconst
for compile-time constants. This practice enhances the predictability and safety of your code.final goatName = 'Billy'; const maxGoatsAllowed = 10; ` `` ```{.dart .bad-code title="Avoid unnecessary mutability"} var goatName = 'Billy'; // Could be final
-
Embrace Asynchronous Programming: Dart’s
async
andawait
keywords facilitate writing asynchronous code that is clean, straightforward, and maintainable.Future<void> feedAllGoats() async { final goats = await fetchGoats(); await Future.wait(goats.map((goat) => goat.feed())); }
void feedAllGoats() { fetchGoats().then((goats) { for (final goat in goats) { goat.feed().then((_) { // Nested callback }); } }); }
-
Use Extensions to Add Functionality: Dart’s extension methods allow you to add functionality to existing classes without modifying them or creating subclasses, keeping your codebase flexible and clean.
extension GoatExtensions on Goat { void feedAndClean() { feed(); clean(); } } // Usage final billy = Goat(name: 'Billy'); billy.feedAndClean();
-
Make Use of Null Safety Features: Dart’s sound null safety is designed to eliminate null dereference errors. Use nullable types (
?
) and default values to ensure your code is more predictable and safe.String? getGoatName(Goat? goat) => goat?.name;
String getGoatName(Goat goat) => goat.name; // Unsafe if 'goat' is null
-
Utilize Collection Literals: Dart supports list, map, and set literals. Use these for creating collections more succinctly and readably.
// Good final goats = ['Billy', 'Daisy']; final goatAges = {'Billy': 2, 'Daisy': 3};
final goats = List<String>.from(['Billy', 'Daisy']); final goatAges = Map<String, int>.from({'Billy': 2, 'Daisy': 3});
-
Embrace Functional Programming Constructs: Take advantage of Dart’s support for first-class functions and higher-order functions to write cleaner and more expressive code.
goats.forEach((goat) => print(goat)); final adultGoats = goats.where((goat) => goat.age > 1).toList();
for (var goat in goats) { print(goat); }
-
Extend Existing Classes: Use extension methods to add functionality to existing classes without modifying them or creating subclasses, keeping your codebase flexible and modular.
extension GoatExtensions on Goat { bool get isAdult => age > 1; } // Usage if (goat.isAdult) { print('Goat is an adult'); }
-
Use Cascades to Chain Operations: Dart’s cascade (
..
) operator allows you to perform a sequence of operations on the same object. This can make your code more fluent and less verbose.final goatPen = GoatPen() ..add(Goat(name: 'Billy')) ..add(Goat(name: 'Daisy')) ..close();
final goatPen = GoatPen(); goatPen.add(Goat(name: 'Billy')); goatPen.add(Goat(name: 'Daisy')); goatPen.close();
-
Prefer async/await Over Futures: Use
async
andawait
for handling asynchronous operations to write code that is clean, simple, and easy to understand.Future<void> feedAllGoats() async { final goats = await fetchGoats(); for (var goat in goats) { await goat.feed(); } }
fetchGoats().then((goats) { goats.forEach((goat) { goat.feed().then((_) { // Nested then() }); }); });
-
Generics for Type Safety and Flexibility: Use generics to write flexible and reusable code
-
components while maintaining type safety.
class GoatList<T extends Goat> { final List<T> _goats = []; void add(T goat) => _goats.add(goat); List<T> get all => _goats; } // Usage final myGoats = GoatList<Goat>(); myGoats.add(Goat(name: 'Billy'));
Adopting these Dart-specific idioms and patterns not only enhances code readability and efficiency but also ensures that your Dart codebase is robust, maintainable, and idiomatic. This approach leverages Dart’s full potential to create high-quality applications.
Setting up a productive development environment is crucial for Dart developers. This section guides on configuring tools and Integrated Development Environments (IDEs) to enhance productivity, ensure code quality, and align with Dart style guidelines.
-
Extensions: Install the Dart and Flutter (if using Flutter) extensions from the VS Code marketplace to get syntax highlighting, code completion, and debug support.
-
Format on Save: Enable "Format On Save" to automatically format your code according to Dart's formatting rules. Go to Settings → search for "Format On Save" → check the box.
-
Problem View: Use the Problems tab to quickly navigate and address issues identified by the Dart analyzer.
-
Dart and Flutter Plugins: Install the Dart and Flutter plugins via Preferences → Plugins. These provide comprehensive Dart support, including syntax highlighting, code completion, and debugging.
-
Code Style Configuration: Configure Dart formatting in Preferences → Editor → Code Style → Dart. You can adjust settings to match your team’s style guide.
-
Dart Analysis: Use the Dart Analysis window to view and navigate to issues in your codebase. Customize analysis options with
analysis_options.yaml
.
- dart format: Use the
dart format
command to automatically format your Dart code. Integrate this command into your version control pre-commit hooks to ensure consistent formatting.
- Effective Dart: Use the linter package to enforce Dart best practices. Customize
your
analysis_options.yaml
to enable preferred lint rules.
include: package:lints/recommended.yaml
- Pub: Use Dart’s package manager, pub, to manage dependencies. Regularly update your dependencies to get the latest features and security updates.
dart pub upgrade
Utilize dartfmt
to automatically format your code, ensuring consistency with Dart's style guide.
Use the Dart Analyzer to identify potential code issues, including syntax errors, type issues, and deprecated API usage.
Incorporate Dart Linter in your project to detect additional stylistic discrepancies and potential problems.
- Dart Language Tour: Offers a comprehensive overview of Dart's features and syntax.
- Dart Effective Dart: Provides a set of guides for styling, authoring, and using Dart effectively.