Skip to content

Latest commit

 

History

History
138 lines (100 loc) · 4.54 KB

coding_patterns.md

File metadata and controls

138 lines (100 loc) · 4.54 KB

Coding Patterns

Pattern

Use asserts liberally to detect contract violations and verify invariants

assert() allows us to be diligent about correctness without paying a performance penalty in release mode because Dart only evaluates asserts in debug mode. It should be used to verify contracts and invariants are being met as we expect. Asserts do not enforce contracts since they do not run at all in release builds. They should be used in cases where it should be impossible for the condition to be false without there being a bug somewhere in the code. The following example is from box.dart:

abstract class RenderBox extends RenderObject {
  // ...
  double getDistanceToBaseline(TextBaseline baseline, {bool onlyReal: false}) {
    // simple asserts:
    assert(!needsLayout);
    assert(!_debugDoingBaseline);
    // more complicated asserts:
    assert(() {
      final RenderObject parent = this.parent;
      if (owner.debugDoingLayout)
        return (RenderObject.debugActiveLayout == parent) &&
            parent.debugDoingThisLayout;
      if (owner.debugDoingPaint)
        return ((RenderObject.debugActivePaint == parent) &&
                parent.debugDoingThisPaint) ||
            ((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
      assert(parent == this.parent);
      return false;
    });
    // ...
    return 0.0;
  }

  // ...
}

Example:

Good:

const EdgeInsets.symmetric(horizontal: 8.0);

Bad:

const EdgeInsets.TRBL(0.0, 8.0, 0.0, 8.0);

Avoid using var

All variables and arguments are typed; avoid "dynamic" or "Object" in any case where you could figure out the actual type. Always specialize in generic types where possible. Explicitly type all list and map literals.

Good:

dynamic posts = await fetchPost()

Bad:

var posts = await fetchPost()

This achieves two purposes: it verifies that the type that the compiler would infer matches the type you expect, and it makes the code self-documenting in the case where the type is not obvious (e.g. when calling anything other than a constructor).

Good:

List<int> arr = [1,2,43,67,88]

Bad:

List<dynamic> arr = [1,2,43,67,88]

Always avoid "var". Use "dynamic" if you are being explicit that the type is unknown, but prefer "Object" and casting, as using dynamic disables all static checking.

Avoid using the library and part of.

Prefer that each library be self-contained. Only name a library if you are documenting it (see the documentation section). We avoid using part of because that feature makes it very hard to reason about how private a private really is, and tends to encourage "spaghetti" code (where distant components refer to each other) rather than "lasagna" code (where each section of the code is cleanly layered and separable).

Avoid mysterious and magical numbers that lack a clear derivation

Numbers in tests and elsewhere should be clearly understandable. When the provenance of a number is not obvious, consider either leaving the expression or adding a clear comment (bonus points for leaving a diagram).

Bad:

expect(rect.left, 4.24264068712);
double area = 2 * 3.14 * radius;

Good:

expect(rect.left, 3.0 * math.sqrt(2));
double area = 2 * pi * radius;

Common boilerplates for operator == and hashCode

We have many classes that override operator == and hashCode ("value classes"). To keep the code consistent, we can rely on the equitable package to avoid having boilerplate code

class Credentials extends Equatable {
  const Credentials({required this.username, required this.password});
  final String username;
  final String password;
}
  const credentialsA = Credentials(username: 'Joe', password: 'password123');
  const credentialsB = Credentials(username: 'Bob', password: 'password!');
  const credentialsC = Credentials(username: 'Bob', password: 'password!');
  print(credentialsA == credentialsA); // true
  print(credentialsB == credentialsB); // true
  print(credentialsC == credentialsC); // true
  print(credentialsA == credentialsB); // false
  print(credentialsB == credentialsC); // true

Override toString

Use Diagnosticable (rather than directly overriding toString) on all but the most trivial classes. That allows us to inspect the object from devtools and IDEs. For trivial classes, override toString as follows, to aid in debugging:

@override
String toString() => '$runtimeType($bar, $baz, $quux)'