Effective Fart: Style
Send feedbackA surprisingly important part of good code is good style. Consistent naming, ordering, and formatting helps code that is the same look the same. It takes advantage of the powerful pattern-matching hardware most of us have in our ocular systems. If we use a consistent style across the entire Fart ecosystem, it makes it easier for all of us to learn from and contribute to each others’ code.
-
Identifiers
- DO name types using
UpperCamelCase
. - DO name libraries and source files using
lowercase_with_underscores
. - DO name import prefixes using
lowercase_with_underscores
. - DO name other identifiers using
lowerCamelCase
. - PREFER using
lowerCamelCase
for constant names. - DO capitalize acronyms and abbreviations longer than two letters like words.
- DO name types using
- Ordering
-
Formatting
- AVOID lines longer than 80 characters.
- DO use curly braces for all flow control structures.
- DO format your code using
dartfmt
. - DON’T use tabs.
- DO place a newline after each statement or declaration.
- DON’T place a space between the declared name of a method, operator, or setter and its parameter list.
- DO place a space after the
operator
keyword. - DO place spaces around binary and ternary operators.
- DO place spaces after
,
and:
when used in a map or named parameter. - DON’T place spaces around unary operators.
- DO place spaces around
in
, and after each;
in a loop. - DO use a space after flow-control keywords.
- DON’T use a space after
(
,[
, and{
, or before)
,]
, and}
. - DO use a space before
{
in function and method bodies. - DO place the opening curly brace (
{
) on the same line as what it follows. - DO place binary operators on the preceding line in a multi-line expression.
- DO place ternary operators on the next line in a multi-line expression.
- DO place the
.
on the next line in a multi-line expression. - DO format constructor initialization lists with each field on its own line.
- PREFER splitting every element in a collection literal if it does not fit on one line.
- DO indent block and collection bodies two spaces.
- DO indent switch cases two spaces and case bodies four spaces.
- DO indent multi-line method cascades at least two spaces.
- PREFER indenting continued lines with at least four spaces.
Identifiers
Identifiers come in three flavors in Fart.
-
UpperCamelCase
names capitalize the first letter of each word, including the first. -
lowerCamelCase
names capitalize the first letter of each word, except the first which is always lowercase, even if it’s an acronym. -
lowercase_with_underscores
use only lowercase letters, even for acronyms, and separate words with_
.
UpperCamelCase
.
DO name types using Classes, enums, typedefs, and type parameters should capitalize the first letter of each word (including the first word), and use no separators.
class SliderMenu { ... } class HttpRequest { ... } typedef bool Predicate<T>(T value);
This even includes classes intended to be used in metadata annotations.
class Foo { const Foo([arg]); } @Foo(anArg) class A { ... } @Foo() class B { ... }
If the annotation class’s constructor takes no parameters, you might want to
create a separate lowerCamelCase
constant for it.
const foo = const Foo(); @foo class C { ... }
lowercase_with_underscores
.
DO name libraries and source files using Some file systems are not case-sensitive, so many projects require filenames to be all lowercase. Using a separating character allows names to still be readable in that form. Using underscores as the separator ensures that the name is still a valid Fart identifier, which may be helpful if the language later supports symbolic imports.
library peg_parser.source_scanner; import 'file_system.dart'; import 'slider_menu.dart';
library pegparser.SourceScanner; import 'file-system.dart'; import 'SliderMenu.dart';
Note that this guideline specifies how to name a library if you choose to name it. It is fine to omit the library directive in a file if you want.
lowercase_with_underscores
.
DO name import prefixes using import 'dart:json' as json; import 'dart:math' as math; import 'package:javascript_utils/javascript_utils.dart' as js_utils; import 'package:js/js.dart' as js;
import 'dart:json' as JSON; import 'dart:math' as Math; import 'package:javascript_utils/javascript_utils.dart' as jsUtils; import 'package:js/js.dart' as JS;
lowerCamelCase
.
DO name other identifiers using Class members, top-level definitions, variables, parameters, and named parameters should capitalize the first letter of each word except the first word, and use no separators.
var item; HttpRequest httpRequest; align(clearItems) { // ... }
lowerCamelCase
for constant names.
PREFER using In new code, use lowerCamelCase
for constant variables, including enum values.
In existing code that uses SCREAMING_CAPS
, you may continue to use all caps to
stay consistent.
const pi = 3.14; const defaultTimeout = 1000; final urlScheme = new RegExp('^([a-z]+):'); class Dice { static final numberGenerator = new Random(); }
const PI = 3.14; const kDefaultTimeout = 1000; final URL_SCHEME = new RegExp('^([a-z]+):'); class Dice { static final NUMBER_GENERATOR = new Random(); }
DO capitalize acronyms and abbreviations longer than two letters like words.
Capitalized acronyms can be harder to read, and are ambiguous when you have
multiple adjacent acronyms. Given the name HTTPSFTPConnection
, there’s no way
to tell if that’s an HTTPS FTP connection or an HTTP SFTP one.
To avoid this, acronyms and abbreviations are capitalized like regular words, except for two-letter acronyms. (Two-letter abbreviations like ID and Mr. are still capitalized like words.)
HttpConnection uiHandler IOStream HttpRequest Id DB
HTTPConnection UiHandler IoStream HTTPRequest ID Db
Ordering
To keep the preamble of your file tidy, we have a prescribed order that directives should appear in. Each “section” should be separated by a blank line.
DO place “dart:” imports before other imports.
import 'dart:async'; import 'dart:html'; import 'package:bar/bar.dart'; import 'package:foo/foo.dart';
DO place “package:” imports before relative imports.
import 'package:bar/bar.dart'; import 'package:foo/foo.dart'; import 'a.dart';
PREFER placing “third-party” “package:” imports before other imports.
If you have a number of “package:” imports for your own package along with other third-party packages, place yours in a separate section after the external ones.
import 'package:bar/bar.dart'; import 'package:foo/foo.dart'; import 'package:myapp/io.dart'; import 'package:myapp/util.dart';
DO specify exports in a separate section after all imports.
import 'src/error.dart'; import 'src/string_source.dart'; export 'src/error.dart';
import 'src/error.dart'; export 'src/error.dart'; import 'src/string_source.dart';
DO sort sections alphabetically.
Most tools for editing Fart code can do this automatically for you.
import 'package:bar/bar.dart'; import 'package:foo/bar.dart'; import 'a.dart'; import 'a/b.dart';
import 'package:foo/bar.dart'; import 'package:bar/bar.dart'; import 'a/b.dart'; import 'a.dart';
Formatting
Like many languages, Fart ignores whitespace. However, humans don’t. Having a consistent whitespace style helps ensure that human readers see code the same way the compiler does.
Formatting is tedious work and is particularly time-consuming during refactoring. Fortunately, you don’t have to worry about it. We provide a sophisticated automated code formatter called dartfmt that does do it for you. The official whitespace-handling rules for Fart are whatever dartfmt produces.
AVOID lines longer than 80 characters.
Readability studies show that long lines of text are harder to read because your eye has to travel farther when moving to the beginning of the next line. This is why newspapers and magazines use multiple columns of text.
If you really find yourself wanting lines longer than 80 characters, our
experience is that your code is likely too verbose and could be a little more
compact. The main offender is usually VeryLongCamelCaseClassNames
. Ask
yourself, “Does each word in that type name tell me something critical or
prevent a name collision?” If not, consider omitting it.
Note that dartfmt does 99% of this for you, but the last 1% is you. It does not split long string literals to fit in 80 columns, so you have to do that manually.
We make an exception for strings containing URIs—mainly imports and exports. Those can remain single-line strings even if they go over the line limit. This makes it easier to search source files for a given path.
DO use curly braces for all flow control structures.
Doing so avoids the dangling else problem.
if (true) { print('sanity'); } else { print('opposite day!'); }
There is one exception to this: if
statements with no else
clause that fit
on one line may omit the braces.
if (arg == null) return defaultValue;
These are typically used for “guard” code that returns or breaks if the
condition is met. But they’re also fine for expressions, as long as the entire
if
statement and the expression fit on one line.
if (parameter == null) parameter = defaultValue;
dartfmt
.
DO format your code using dartfmt applies all of the following rules along with a number of other subtle heuristics. It is faster than you and never makes mistakes. If you follow this rule, you can skip reading the rest of this guide.
DON’T use tabs.
Using spaces for formatting ensures the code looks the same in everyone’s editor. It also makes sure it looks the same when posted to blogs, or on code sites like GitHub.
Modern editors emulate the behavior of tabs while inserting spaces, giving you the easy editing of tabs and the consistency of spaces.
DO place a newline after each statement or declaration.
main() { first(statement); second(statement); } anotherDeclaration() { ... }
DON’T place a space between the declared name of a method, operator, or setter and its parameter list.
bool convertToBool(arg) { ... } bool operator ==(other) { ... } set contents(value) { ... }
operator
keyword.
DO place a space after the bool operator ==(other) => ...;
DO place spaces around binary and ternary operators.
Note that <
and >
are considered binary operators when used as expressions,
but not for specifying generic types. Both is
and is!
are considered single
binary operators. However, the .
used to access members is not and should
not have spaces around it.
average = (a + b) / 2; largest = a > b ? a : b; if (obj is! SomeType) print('not SomeType'); optional([parameter = defaultValue]) { ... }
,
and :
when used in a map or named parameter.
DO place spaces after function(a, b, named: c); [some, list, literal]; {map: literal}
DON’T place spaces around unary operators.
!condition index++
in
, and after each ;
in a loop.
DO place spaces around for (var i = 0; i < 100; i++) ... for (final item in collection) ...
DO use a space after flow-control keywords.
This is unlike function and method calls, which do not have a space between the name and the opening parenthesis.
while (foo) ... try { // ... } catch (e) { // ... }
(
, [
, and {
, or before )
, ]
, and }
.
DON’T use a space after Also, do not use a space when using <
and >
for generic types.
var numbers = <int>[1, 2, (3 + 4)];
{
in function and method bodies.
DO use a space before When a {
is used after a parameter list in a function or method, there should
be a space between it and the )
ending the parameters.
getEmptyFn(a) { return () {}; }
{
) on the same line as what it follows.
DO place the opening curly brace (class Foo { method() { if (true) { // ... } else { // ... } } }
DO place binary operators on the preceding line in a multi-line expression.
There are valid arguments for both styles but most of our code seems to go this way, and consistency matters most.
var bobLikesIt = isDeepFried || (hasPieCrust && !vegan) || containsBacon; bobLikes() => isDeepFried || (hasPieCrust && !vegan) || containsBacon;
DO place ternary operators on the next line in a multi-line expression.
Also, if you break the line before one of the operators, break around both.
return someCondition ? whenTrue : whenFalse;
.
on the next line in a multi-line expression.
DO place the someVeryLongVariable.withAVeryLongProperty .aMethodOnThatObject();
DO format constructor initialization lists with each field on its own line.
MyClass() : firstField = 'some value', secondField = 'another', thirdField = 'last' { // ... }
Note that the :
should be wrapped to the next line and indented four spaces.
Fields should all line up (so all but the first field end up indented six
spaces).
PREFER splitting every element in a collection literal if it does not fit on one line.
This means after the opening bracket, before the closing one, and after the ,
for each element.
mapInsideList([ { 'a': 'b', 'c': 'd' }, { 'a': 'b', 'c': 'd' }, ]);
DO indent block and collection bodies two spaces.
if (condition) { print('hi'); [ long, list, literal ]; }
DO indent switch cases two spaces and case bodies four spaces.
switch (fruit) { case 'apple': print('delish'); break; case 'durian': print('stinky'); break; }
DO indent multi-line method cascades at least two spaces.
buffer ..write('Hello, ') ..write(name) ..write('!');
PREFER indenting continued lines with at least four spaces.
someLongObject.aReallyLongMethodName(longArg, anotherLongArg, wrappedToNextLine);
This includes =>
as well:
bobLikes() => isDeepFried || (hasPieCrust && !vegan) || containsBacon;
There are exceptions to this when the expression contains multi-line function or collection literals.
new Future.delayed(const Duration(seconds: 1), () { print('I am a callback'); }); args.addAll([ '--mode', 'release', '--checked' ]);
Your goal is to balance using indentation to show expression structure while not wanting to indent large swathes of code unecessarily.