May 23, 2021 Dart Code style guide
For public APIs, it is a good idea to provide type annotations.
Type annotations are a very important document that explains how the appropriate library should be used. Return type annotations for parameters and public methods help consumers understand what parameters the API needs and what functionality it provides.
However, if you have an API that can receive any parameters, or a value that cannot be represented in Dart, the APi can not add annotations.
For code inside the library, even private, or nested functions, add annotations where you think they are helpful, but don't think you have to provide them.
install(id, destPath) { // bad
// ...
}
In the code above, we don't know
id
is. S
tring? S
o
destPath
S
tring or file object?
Is this function synchronized or asynchronous?
Future<bool> install(PackageId id, String destPath) { // good
// ...
}
When you add a type, the information about this function becomes clear.
For local variables, it
is
best to declare
var
instead of type annotations.
Now, we prefer to keep the code of the function body as concise as possible, and the type of the local variable can be inferred in the initialization expression, in which case it is not necessary to display a declaration of its type. A good editor can infer the type of local variable, so auto-complement is still available, and other features you expect will work.
Map<int, List<Person>> groupByZip(Iterable<Person> people) { // good
var peopleByZip = new Map<int, List<Person>>();
for (var person in people) {
peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
peopleByZip[person.zip].add(person);
}
return peopleByZip;
}
Map<int, List<Person>> groupByZip(Iterable<Person> people) { // bad
Map<int, List<Person>> peopleByZip = new Map<int, List<Person>>();
for (Person person in people) {
peopleByZip.putIfAbsent(person.zip, () => <Person>[]);
peopleByZip[person.zip].add(person);
}
return peopleByZip;
}
If there are requirements for the running performance of
your code, it is best to replace num with double or
double
int
passing
num
A single call point (with a stable input type) is easier to optimize than a polymorphic call point, whose input type may change.
For number types, you should indicate the specific number type when you add type annotations. E
xplicitly declaring
double
or
int
people who use your method pass the appropriate parameters.
Do not make type annotations when formally initializing.
If a constructor's argument uses this. to initialize a
this.
the argument must be of the same type as this field, so you don't have to use type annotations.
class Point { // good
int x, y;
Point(this.x, this.y);
}
class Point { // bad
int x, y;
Point(int this.x, int this.y);
}
You should avoid using type annotations in function expressions.
One of the great advantages of a function expression is its simplicity. I f a function is so complex that it needs an annotation type to understand it, it should be a function statement or a method. Accordingly, if a function is simple enough to be used as an expression, it does not require type annotations.
var names = people.map((person) => person.name); // good
var names = people.map((Person person) { // bad
return person.name;
});
You should
avoid
using
dynamic
to add type annotations when you don't need them.
In Dart, type annotations are negligible in most cases because they are automatically treated as
dynamic
Therefore, not adding type annotations is semantically completely no problem, and the code appears more concise.
lookUpOrDefault(String name, Map map, defaultValue) { // good
var value = map[name];
if (value != null) return value;
return defaultValue;
}
dynamic lookUpOrDefault(String name, Map map, dynamic defaultValue) { // bad
var value = map[name];
if (value != null) return value;
return defaultValue;
}
For any object received, object
should be used
Object
of
dynamic
indicate that the argument is an object.
Some operations may apply to any object. F
or example,
toString()
used to output information and can be used for any object. T
here are two types in Dart that can match all objects:
Object
and
dynamic
However, they represent two different things.
Object
annotations indicate that "I can receive any object as long as it contains a method defined by the related object itself."
dynamic
annotations indicate that there are no annotations to indicate what type of object you actually allow.
(There may be, but you don't care if you want to declare)
//good
// Accepts any object.
void log(Object object) {
print(object.toString());
}
// Only accepts bool or String, which can't be expressed in a type annotation.
bool convertToBool(arg) {
if (arg is bool) return arg;
if (arg is String) return arg == 'true';
throw new ArgumentError('Cannot convert $arg to a bool.');
}