Variables
AVOID
AVOID storing what you can calculate.
When designing a class, you often want to expose multiple views into the same underlying state. Often you see code that calculates all of those views in the constructor and then stores them:
class Circle {
num radius;
num area;
num circumference;
Circle(num radius)
: radius = radius,
area = pi * radius * radius,
circumference = pi * 2.0 * radius;
}
This code has two things wrong with it. First, it’s likely wasting memory. The area and circumference, strictly speaking, are caches. They are stored calculations that we could recalculate from other data we already have. They are trading increased memory for reduced CPU usage. Do we know we have a performance problem that merits that trade-off?
Worse, the code is wrong. The problem with caches is invalidation—how do you know when the cache is out of date and needs to be recalculated? Here, we never do, even though radius
is mutable. You can assign a different value and the area
and circumference
will retain their previous, now incorrect values.
To correctly handle cache invalidation, we need to do this:
class Circle {
num _radius;
num get radius => _radius;
set radius(num value) {
_radius = value;
_recalculate();
}
num _area;
num get area => _area;
num _circumference;
num get circumference => _circumference;
Circle(this._radius) {
_recalculate();
}
void _recalculate() {
_area = pi * _radius * _radius;
_circumference = pi * 2.0 * _radius;
}
}
That’s an awful lot of code to write, maintain, debug, and read. Instead, your first implementation should be:
class Circle {
num radius;
Circle(this.radius);
num get area => pi * radius * radius;
num get circumference => pi * 2.0 * radius;
}
This code is shorter, uses less memory, and is less error-prone. It stores the minimal amount of data needed to represent the circle. There are no fields to get out of sync because there is only a single source of truth.
In some cases, you may need to cache the result of a slow calculation, but only do that after you know you have a performance problem, do it carefully, and leave a comment explaining the optimization.
DON'T
DON’T explicitly initialize variables to null
.
In Dart, a variable or field that is not explicitly initialized automatically gets initialized to null
. This is reliably specified by the language. There’s no concept of “uninitialized memory” in Dart. Adding = null
is redundant and unneeded.
int _nextId;
class LazyId {
int _id;
int get id {
if (_nextId == null) _nextId = 0;
if (_id == null) _id = _nextId++;
return _id;
}
}
int _nextId = null;
class LazyId {
int _id = null;
int get id {
if (_nextId == null) _nextId = 0;
if (_id == null) _id = _nextId++;
return _id;
}
}