Best Practices

Code Style

Naming Conventions

// Classes: UpperCamelCase
class UserProfile {}

// Variables, functions: lowerCamelCase
String userName = 'Alice';
void calculateTotal() {}

// Constants: lowerCamelCase
const maxRetries = 3;
const apiEndpoint = 'https://api.example.com';

// Private members: _leadingUnderscore
class BankAccount {
  double _balance;
}

// Libraries: lowercase_with_underscores
import 'package:my_app/user_service.dart';

Use Type Annotations

// Good
String getName() => 'Alice';
List<int> getNumbers() => [1, 2, 3];

// Avoid
getName() => 'Alice';  // Type unclear

Prefer final over var

// Good
final name = 'Alice';
final numbers = [1, 2, 3];

// Less preferred
var name = 'Alice';

Null Safety

// Always handle nulls safely
String? getName() => null;

void main() {
  // Good
  String name = getName() ?? 'Guest';
  
  // Good
  String? maybeName = getName();
  if (maybeName != null) {
    print(maybeName.toUpperCase());
  }
  
  // Avoid
  // String name = getName()!;  // Risky!
}

Functions

Keep Functions Small

// Good - single responsibility
bool isValidEmail(String email) {
  return email.contains('@') && email.contains('.');
}

bool isAdult(int age) {
  return age >= 18;
}

// Avoid - doing too much
void processUser(Map user) {
  // 100 lines of code...
}

Use Named Parameters

// Good
void createUser({
  required String name,
  required String email,
  int age = 0,
}) {}

// Less clear
void createUser(String name, String email, int age) {}

Collections

Use Collection Literals

// Good
var list = <int>[];
var map = <String, int>{};
var set = <String>{};

// Avoid
var list = List<int>();
var map = Map<String, int>();

Use Collection If/For

// Good
var numbers = [
  1,
  2,
  if (includeThree) 3,
  for (var i in extras) i,
];

// Avoid
var numbers = [1, 2];
if (includeThree) numbers.add(3);
for (var i in extras) numbers.add(i);

Classes

Use Initializing Formals

// Good
class Point {
  final double x, y;
  Point(this.x, this.y);
}

// Avoid
class Point {
  final double x, y;
  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

Prefer Composition Over Inheritance

// Good
class Car {
  final Engine engine;
  Car(this.engine);
}

// Less flexible
class Car extends Engine {}

Async Programming

Always Use async/await

// Good
Future<String> fetchData() async {
  var response = await http.get(url);
  return response.body;
}

// Avoid
Future<String> fetchData() {
  return http.get(url).then((response) {
    return response.body;
  });
}

Handle Errors

Future<void> loadData() async {
  try {
    var data = await fetchData();
    processData(data);
  } catch (e) {
    print('Error: $e');
  }
}

Performance

Use const Constructors

// Good - created once
const icon = Icon(Icons.home);

// Less efficient - created every time
final icon = Icon(Icons.home);

Avoid Unnecessary Rebuilds

// Good - extract to const
class MyWidget extends StatelessWidget {
  static const title = Text('Hello');
  
  @override
  Widget build(BuildContext context) {
    return title;
  }
}

Documentation

Document Public APIs

/// Calculates the area of a circle.
///
/// The [radius] must be positive.
/// Returns the area as a double.
double calculateCircleArea(double radius) {
  assert(radius > 0, 'Radius must be positive');
  return 3.14159 * radius * radius;
}

Testing

Write Tests

import 'package:test/test.dart';

void main() {
  test('addition works', () {
    expect(2 + 2, equals(4));
  });
  
  test('string contains substring', () {
    expect('Hello, World', contains('World'));
  });
}

Summary

  1. Follow naming conventions
  2. Use type annotations for clarity
  3. Embrace null safety
  4. Keep functions small and focused
  5. Use named parameters for clarity
  6. Prefer const and final
  7. Handle errors properly
  8. Document public APIs
  9. Write tests
  10. Use linter (analysis_options.yaml)

Next Steps