Dart encoding specification: handle null correctly

Dart encoding specification: handle null correctly

preface

In Dart coding, we often encounter situations where we need to deal with null. After the introduction of null safety in Dart version 2.12, there is a new specification for null processing. For the null safety feature, you can read another article: Upgrade stepping pit and talk about Dart's null safety , this article describes how to handle null correctly.

Specification 1: do not explicitly initialize variables to null

If a variable is declared non null, the compiler will report an error when it is assigned null; If a variable is declared nullable, it will be implicitly initialized with null. Therefore, it is unnecessary to assign null again. There is no uninitialized memory problem in Dart, so there is no need to initialize to null.

// Correct example
Item? bestDeal(List<Item> cart) {
  Item? bestItem;
  // ...
}

// Error example
Item? bestDeal(List<Item> cart) {
  Item? bestItem = null;
  // ...
}
Copy code

Specification 2: do not set null default values for function parameters

If the parameter of a function is nullable, null will also be copied implicitly, so there is no need to set the default value repeatedly.

// Correct example
void error([String? message]) {
  print(message ?? 'unknown error');
}

// Error example
void error([String? message = null]) {
  print(message ?? 'unknown error');
}
Copy code

Specification 3: use?? Operator converts null to Boolean

We sometimes deal with null in conditional expressions. At this time, a safer way is to use?? Converts an empty object to a Boolean value. Although using the = = operator can also achieve the goal, it is not recommended. Comparing the following two examples, we can find that the code converted to Boolean has the following advantages:

  • It clearly indicates that this code handles null values.
  • Because it is dealing with Boolean variables, using = = true will feel a little redundant at first glance, and it seems that it can be deleted.
  • use?? false or?? True can clearly indicate how to handle null. Using = = true is a bit of a detour, and it will take more time to read.
// Correct example
// Turn null to false
if (optionalThing?.isEnabled ?? false) {
  print('Have enabled thing.');
}

// Change null to true
if (optionalThing?.isEnabled ?? true) {
  print('Have enabled thing or nothing.');
}

//Error example
// Treat null as false
if (optionalThing?.isEnabled == true) {
  print('Have enabled thing.');
}

// Treat null as true
if (optionalThing?.isEnabled != false) {
  print('Have enabled thing or nothing.');
}
Copy code

One exception is that if the null judgment is made in advance, it is not necessary to use?? Operators, which will increase the confusion of the code. For example, in the following case, it is obvious that the first method is more readable.

// Correct example
int measureMessage(String? message) {
  if (message != null && message.isNotEmpty) {
    // message is promoted to String.
    return message.length;
  }

  return 0;
}

// Error example
int measureMessage(String? message) {
  if (message?.isNotEmpty ?? false) {
    // message is not promoted to String.
    return message!.length;
  }

  return 0;
}
Copy code

Spec 4: if your code checks whether variables are initialized, don't use late

The scenario of using late is that you clearly know that this variable will be initialized before use. If a late variable is not initialized and used directly, an exception will be thrown. Sometimes, we may use another Boolean value to identify whether the late variable is initialized, but this is a little redundant. Since you can use Boolean values to track whether a variable is initialized, you can use null to initialize, and then check whether the variable is null to achieve the same effect. There is only one case, that is, null itself is also a meaningful assignment, so you need to use a Boolean value to identify whether to initialize. For example, if we request a nonexistent object from the background, the back end may directly return null, which is meaningful.

Specification 5: copy nullable member attribute as local variable to promote type

When we detect whether a variable is null, nullable objects will be promoted to non null type if they are not empty. The advantage is that when we use this variable again, the code will be much simpler. For example, when accessing object properties, we don't need to use it! To force nullable objects to be non null objects.

For the class member attribute, if you directly reference the nullable class member attribute, you still need to use it even if you make a non empty judgment! To force nullable objects to be non null objects. At this time, using local variables to copy can avoid this problem. This benefit is evident through examples.

// Correct example
class NullablePromotionTest {
  Offset? offset;

  printOffset() {
    var offset = this.offset;
    if (offset != null) {
      print('${offset.dx}, ${offset.dy}');
    }
  }
}

// Error example
class NullablePromotionTest {
  Offset? offset;

  printOffset() {
    if (offset!= null) {
      print('${offset!.dx}, ${offset!.dy}');
    }
  }
}
Copy code

This is actually a trick, but you should pay special attention when using local variables for non null promotion. If it is possible to update the member property, you need to assign a value to the member property after the local variable is changed. Therefore, this is generally used in read-only situations. If it involves changing member properties, a slight carelessness may lead to a bug.

summary

This article introduces the recommendation for handling null in Dart code. Failure to follow these specifications will not affect the normal operation of the code, but it may bring hidden dangers leading to bug s or affect the readability of the code. Therefore, when you encounter null processing, you can think about how to write code to better understand the code logic and simplify repeated mandatory conversion.

Posted by studio805 on Sun, 21 Nov 2021 12:28:35 -0800