Enforcing Compliance to APIs versus Developer Guidelines

When providing an API to developers, there are different levels of compliance that a vendor can expose.

  • API call fails with error
  • API call succeeds, but a warning is sent and there is no way to disable the warning
  • API call succeeds with a warning that can be disabled by the development team

Many developer sites have an Effective Usage document. These are guidelines by convention, not strictly enforced by a compiler or API. For example, I often use the effective dart guidelines.

Effective Dart: Usage | Dart

The influence of the effective usage guidelines can be greatly increased by the enforcement of usage within linter rules.

The linter works with static analysis technology that the IDE generally presents.

Customizing static analysis | Dart

You have the option of ignoring rules or setting the level of strictness for compliance.

With Dart development, the issue of compliance versus guidelines is coming up for me frequently as I am learning about null safety.

This morning, I read through a section of Effective Dart that solved a problem for me with null variables.

The relevant section is below.

AVOID late variables if you need to check whether they are initialized.

Dart offers no way to tell if a late variable has been initialized or assigned to. If you access it, it either immediately runs the initializer (if it has one) or throws an exception. Sometimes you have some state that’s lazily initialized where late might be a good fit, but you also need to be able to tell if the initialization has happened yet.

Although you could detect initialization by storing the state in a late variable and having a separate boolean field that tracks whether the variable has been set, that’s redundant because Dart internally maintains the initialized status of the late variable. Instead, it’s usually clearer to make the variable non-late and nullable. Then you can see if the variable has been initialized by checking for null.

Of course, if null is a valid initialized value for the variable, then it probably does make sense to have a separate boolean field.

CONSIDER copying a nullable field to a local variable to enable type promotion.

Checking that a nullable variable is not equal to null promotes the variable to a non-nullable type. That lets you access members on the variable and pass it to functions expecting a non-nullable type. Unfortunately, promotion is only sound for local variables and parameters, so fields and top-level variables aren’t promoted.

One pattern to work around this is to copy the field’s value to a local variable. Null checks on that variable do promote, so you can safely treat it as non-nullable.

class UploadException {
  final Response? response;

  UploadException([this.response]);

  @override
  String toString() {
    var response = this.response;
    if (response != null) {
      return 'Could not complete upload to ${response.url} '
          '(error code ${response.errorCode}): ${response.reason}.';
    }

    return 'Could not upload (no response).';
  }
}

There’s a nice convention of providing good and bad examples. The good examples are green. The bad examples are red. :cactus: