Skip to content

Instantly share code, notes, and snippets.

@bobjackman
Created January 4, 2018 22:57
Show Gist options
  • Save bobjackman/c0375d21bbe743d75687ff387ac99d6e to your computer and use it in GitHub Desktop.
Save bobjackman/c0375d21bbe743d75687ff387ac99d6e to your computer and use it in GitHub Desktop.
LN Coding Standards - Dart
# Dart Coding Standards - LiveNinja Development
## Conventions
The key words `MUST`, `MUST NOT`, `REQUIRED`, `SHALL`, `SHALL NOT`, `SHOULD`, `SHOULD NOT`, `RECOMMENDED`, `MAY`, and `OPTIONAL` in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) and summarized as follows:
1. `MUST` This word, or the terms `REQUIRED` or `SHALL`, mean that the definition is an absolute requirement of the specification.
1. `MUST NOT` This phrase, or the phrase `SHALL NOT`, mean that the definition is an absolute prohibition of the specification.
1. `SHOULD` This word, or the adjective `RECOMMENDED`, mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course.
1. `SHOULD NOT` This phrase, or the phrase `NOT RECOMMENDED` mean that there may exist valid reasons in particular circumstances when the particular behavior is acceptable or even useful, but the full implications should be understood and the case carefully weighed before implementing any behavior described with this label.
1. `MAY` This word, or the adjective `OPTIONAL`, mean that an item is truly optional. One author may choose to heed the option, while another might omit the item if he feels that it improves readability and/or future maintenance of the code.
## Code Formatting
In general we adhere to the same [formatting standards as officially defined by the Dart authors](https://www.dartlang.org/guides/language/effective-dart/style#identifiers), however there are a few items below that conflict with those rules. Such items below are marked with a dagger symbol (†)
Many of the standards defined below are optional. When usig tnhese optional styles, always consider and decide on your own, which option will be most clear and readable to people unfamiliar with your code.
### Indenting and Whitespace
* Indenting `MUST` be done using 2 space and no tabs.
* Lines `SHOULD` have no trailing whitespace at the end.
* Files `SHOULD` be formatted with `\n` as the line ending (Unix line endings), and `SHOULD NOT` use `\r\n` (Windows line endings).
* All text files `SHOULD` end in a single newline (`\n`).
> ###### This avoids the verbose "No newline at end of file" patch warning and makes patches easier to read since it's clearer what's being changed when lines are added to the end of a file.
### Operators
* All binary operators (operators that come between two values), such as `+`, `-`, `=`, `!=`, `==`, `>`, etc. `MUST` have a space before _and after_ the operator. e.g. `foo = bar;` rather than `foo=bar;`
* Unary operators (operators that operate on only one value), such as `++`, `MUST NOT` have a space between the operator and the variable or number they are operating on. e.g. `foo++;` rather than `foo ++;`
* The cascade operator (`..` or "double dot") `SHOULD` be on a new line and indented 1 level deeper than the parent statement on which it operates. For example:
```javascript
foo = new List()
..add(newValue1)
..add(newValue2);
```
* The member access operator (`.` or "dot") `MAY` be placed on a new line in the same manner as the cascade operator if the author feels that it improves readability of the code.
### Casting
* All casts `SHOULD` be wrapped within their own set of parenthesis. e.g. `foo = (bar as List).first;`
### Control Structures and Conditionals
Control structures include `if`, `for`, `while`, `switch`, etc. Here is a sample if statement, since it is the most complicated of them:
```javascript
if (condition1 || condition2) {
action1();
} else if (condition3 && condition4) {
action2();
} else {
defaultAction();
}
```
* Control statements `MUST` have one space between the control keyword and opening parenthesis.
* Curly braces `MUST` be used even in situations where they are technically optional.
> ###### Having them increases readability and decreases the likelihood of logic errors being introduced when new lines are added.
* The opening curly `MUST` be on the same line as the opening statement and `MUST` be preceded by one space.
* The closing curly `MUST` be on a line by itself and indented to the same level as the opening statement.
* For switch statements:
```javascript
switch (condition) {
case 1:
action1();
break;
case 2:
action2();
break;
default:
defaultAction();
}
```
* For do-while statements:
```javascript
do {
actions();
} while (condition);
```
* All conditionals (including ternary statements)`MUST` be wrapped in parenthesis, even if not being used in a control structure.
```javascript
name = (user.name.isNotEmpty ? user.name : 'Guest User');`
// OR
name = ((user.name.isNotEmpty) ? user.name : 'Guest User');`
// BUT NOT
name = (user.name.isNotEmpty) ? user.name : 'Guest User';
```
### Line Length and Wrapping
* In general, all lines of code `SHOULD` be limited to a maximum length of 140 characters.**†**
* Lines containing longer function names, function/class definitions, variable declarations, etc are allowed to exceed this limit.
* Inline documentation comments at the end of a line `MAY` be allowed to exceed this limit. But they `SHOULD` be wrapped into multiple lines.
```javascript
// ACCEPTABLE
var foo = doAthing(); // -- Here, we bartificate the foobibular into separate, distict mistibulations. This is needed in order to avoid over-delating the lamotorical combobulation.
// BETTER
var foo = doAthing(); // -- Here, we bartificate the foobdibular into separate, distinct mistibulations.
// This is needed in order to avoid over-delating the lamotorical combobulation.
```
----------
* Function/Class definitions `MUST NOT` be wrapped into multiple lines.
```javascript
// DON'T DO THIS!
String myFunction(
String arg1,
String arg2,
String arg3,
String arg4,
[String optional1,
String optional2,
String optional3]) {
...;
}
```
* Control conditionals `MUST NOT` be wrapped into multiple lines. If these conditions are becoming unweildy, split them up into separate variables and use those in the conditional (_which also permits documenting the underlying reasons for the conditions_).
```javascript
// DON'T DO THIS
if ((user != null && user.id.isNotEmpty && targetId == user.id) || (user.name.isNotEmpty ? user.name : '') == targetName || (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber)) {
...
}
// DON'T DO THIS
if (
(user != null && user.id.isNotEmpty && targetId == user.id)
|| (user.name.isNotEmpty ? user.name : '') == targetName
|| (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber)) {
...
}
// YES, DO THIS
var hasUser = (user != null);
var isIdMatch = (hasUser && user.id == targetId);
var isNameMatch = ((user.name.isNotEmpty ? user.name : '') == targetName);
var isPhoneMatch = (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber);
if (isIdMatch || isNameMatch || isPhoneMatch) {
...
}
```
### Function Calls and Class Constructors
* `MUST` be called with _no spaces_ between the function name, the opening parenthesis, and the first parameter.
* `MUST` be called _with spaces_ between commas and each parameter.
* `MUST` be called with _no space_ between the last parameter, the closing parenthesis, and the semicolon.
```javascript
var foo = getFoo($bip, $baz, $zip);
var bar = new Bar($foo, $baz, $bip);
```
* `MUST` include parenthesis even if no arguments are required.
```javascript
var zip = findDefaultZip();
var zap = new Zap();
```
### Functions, Classes, and Control Blocks (including closures)
* The opening curly brace `MUST` be on the same line as the opening statement and `MUST` be preceded by one space.
* The closing curly brace `MUST` be on a line by itself and indented to the same level as the opening statement.
* Functions/methods using the fat-arrow syntax `MAY` be declared on a single line.
### Classes
* Non-getter/setter class properties/constants `MUST` be declared at the top of the class.
* Class constructors `SHOULD` be grouped together
* Class constructors `MUST` come after non-getter/setter properties/constants and before method declarations.
* Class methods `MUST` be placed after all class constructors
* Simple, single-line class getters/setters `SHOULD` be grouped at the top of the class with the non-getter/setter properties.
* Multi-line class getters/setters `SHOULD` be grouped along with other class methods _after_ the class constructors.
### Arrays and Maps
* Arrays `SHOULD` be declared using short array syntax
* When using this style there `MUST` be a space separating each element (after the comma). e.g. `var foo = ['bar', 'bap', 'zoo'];`
* Arrays `MAY` be declared using long array syntax. e.g. `var foo = new List();`
* Maps `SHOULD` be declared using short map syntax. e.g. `var foo = {};`
* Maps `MAY` be declared using long map syntax. e.g. `var foo = new Map();`
* Map literals `MUST` have a space separating each key from its value (after the colon). e.g. `var foo = {'name': 'bar'};`
* Map literals with multiple key/value pairs `MAY` be delcared on either a single or multiple-lines. Use whichever style is most clear to read/understand.
```javascript
// ACCEPTABLE
var user = {'name': 'John Doe', 'age': 35, 'gender': 'male', 'email': '[email protected]', 'favoriteColor': 'pink'};
// BETTER
var user = {
'name' : 'John Doe',
'age' : 35,
'gender': 'male',
'email' : '[email protected]',
'phone' : '867-5309'
};
```
### Quotes
* There is no hard standard for the use of single quotes vs. double quotes. Where possible, keep consistency within each module, and respect the personal style of other developers.
### String Concatenation
* `SHOULD` be done using string interpolation. When using string interpolation for complex insertions, there `SHOULD` be no spaces between the opening `${` and the start of the next statement, and no spaces between the end of the last statement and the closing `}`
```javascript
// COMPLEX INTERPOLATION
print('Hello, ${user.isActor ? user.stageName : user.realName}!');
```
* `MAY` be done using the `+` operator. When using this style there `MUST` be a space between the `+` operator and each component of the concatenation.
```javascript
// ACCEPTABLE
print('Hello, ' + name + '! I thought you were dead!');
// BETTER
print('Hello, $name! I thought you were dead!');
```
### Variable Declarations
* Variables `MUST` each be declared on their own line and `MUST NOT` use multiple declaration syntax.
```javascript
// DON'T DO THIS
var blam, foo = 'bar', zip = 'zap';
// YES, DO THIS
var blam;
var foo = 'bar';
var zip = 'zap';
```
### Aesthetic Alignment
Aesthetics can be a very subjective matter and all developers have their opinions about what looks best. The following standards are `OPTIONAL`, but *strongly* `RECOMMENDED` and encouraged. The end goal with these aesthetics is to maximize readability and clarity. As always use your best judgement for the situation at hand.
* When declaring multiple variables in close proximity to each other, their types and names `SHOULD` be aligned horizontally.
```javascript
int foo;
bool bar;
```
* When initializing multiple variables in close proximity to each other, their assignment operators `SHOULD` be aligned horizontally.
* When initialzing multiple variables in close proximity to each other, they `MAY` be broken into relevant groups with their own alignments.
```javascript
String category1_foo = 'Foo';
int category1_bar = 0;
bool category1_zippy = false;
foo += 0;
bar = true;
zippy = 'Whee!';
```
> ###### Note the alignment of the `+=` in relation to the other `=` operators.
* When declaring map literals on multiple lines, their colons `SHOULD` be aligned horizontally.
* When declaring map literals on multiple lines, their elements `MAY` be broken into relevant groups with their own alignments.
```javascript
var foo = {
'category1': 'a',
'category2': 'b',
'category3': 'c',
'bar' : 0,
'zip' : 'zap',
'blam': 'boom'
};
```
* When several statements in close proximity have very similar code, add alignment in order to make the similarities (and differences) more prominent.
```javascript
if (includeTypes.isNotEmpty) { query.addAll({'types' : includeTypes.join(',')}); }
if (excludeTypes.isNotEmpty) { query.addAll({'notTypes' : notEventTypes.join(',')}); }
if ( authors.isNotEmpty) { query.addAll({'author_id': authors.join(',')}); }
```
> ###### Note that in locations where the lines _differ_, the code is _un-aligned_ (when possible) to visually highlight differences.
* When several closures, functions, or methods using the fat-arrow syntax are declared in close proximity, add alignment in order to make the similarities (and differences) more prominent.
``` dart
class Foo {
String get name => '${firstName} ${lastname}';
bool get hasName => name.isNotEmpty
String get value => this.render();
void set value(newValue) => (this.input = newValue);
}
```
### Comments
We use a slightly more specific form of commenting in our codebase depending on what's being described. Please try to choose the most appropriate style for each situation.
#### DocBlocks
Docblock comments are parsed to generate automatic documentation. They should be used above all class, function, and method definitions and are denoted by starting them with either `/**` or `///` (triple slash).
```javascript
/**
* This is a docblock that will be picked up by automated tools
* It should follow the dartdoc spec: https://github.com/dart-lang/dartdoc
*/
String foobar(String foo) {
return bar;
}
/// This is an alternate docblock syntax that works the same way as the above
/// It should follow the dartdoc spec: https://github.com/dart-lang/dartdoc
String foobar(String foo) {
return bar;
}
```
#### Section-Level Comments
Section-level (or block-level) comments are used to describe a section of code inside of a class or method. These comments generally describe several lines at once. Think of these kind of like `<h1>` blocks in HTML. They `SHOULD` use Title Case and `MUST` start with `// ======== ` (double slash, followed by 1 space, 8 equals signs, and 1 space)
```javascript
// ======== Initialize
var phoneNumber = httpRequest.get('phone');
var userName = httpRequest.get('name');
var userId = httpRequest.get('id');
// ======== Decide If This Request is Allowed
var token = new Token.fromRequest(httpRequest);
var user = token.requestingUser;
var hasUser = (user != null);
var isIdMatch = (hasUser && user.id == targetId);
var isNameMatch = ((user.name.isNotEmpty ? user.name : '') == targetName);
var isPhoneMatch = (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber);
```
#### Subsection-Level Comments
Subsection-Level comments are used to add further description to sub-blocks of code within a section-level comment. These comments generally describe several lines at once. Think of these kind of like `<h2>` blocks in HTML. They `SHOULD` use all lower case and `MUST` start with `// -------- ` (double slash, followed by 1 space, 8 dashes, and 1 space)
```javascript
// ======== Decide If This Request is Allowed
// -------- fetch our user
var token = new Token.fromRequest(httpRequest);
var user = token.requestingUser;
// -------- check permissions
var hasUser = (user != null);
var isIdMatch = (hasUser && user.id == targetId);
var isNameMatch = ((user.name.isNotEmpty ? user.name : '') == targetName);
var isPhoneMatch = (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber);
```
#### EOL Comments
Comments at the end of a line are used to describe what a single line of code is doing and `MUST` describe *ONLY* the line on which they reside. This style of comment should be used anywhere that additional clarification is needed *including* most if/switch/while conditions and `MUST` be used for *all* `else` and `else if` conditions. They `SHOULD` start with `// -- ` (double slash, followed by 1 space, 2 dashes, and 1 space).
If these comments need to be wrapped onto multiple lines, they should keep their slashes and their body aligned as shown below.
```javascript
var hasUser = (user != null); // -- we have a user
var isIdMatch = (hasUser && user.id == targetId); // -- our id matches the requesting user
var isNameMatch = ((user.name.isNotEmpty ? user.name : '') == targetName); // -- we have a name AND it matches our user
var isPhoneMatch = (phoneNumber.isNotEmpty && phoneNumber == user.phoneNumber); // -- we have a phone number AND it matches our user
if (isIdMatch || isNameMatch || isPhoneMatch) { // -- this request is allowed
...
} else { // -- request not allowed
if (isIdMatch && !isNameMatch) { // -- id matches, but name doesn't. This is important
// because if the encabulator is ever misaligned,
// it could cause total protonic reversal.
...
}
}
```
#### Other Comments
Sometimes other comments are needed to indicate `TODO` items, bug tracker ticket numbers, or StackOverflow references. These `MAY` be placed _either_ as EOL comments or as a section-level comment as shown below.
* Single Line
```javascript
// TODO: refactor this to work with the unilateral phase detractors instead
String foo = new Encabulator();
```
* Multi-Line
```javascript
// NOTE: until we achieve inverse reactive current, we're required to resort
// to using the modial interaction of magneto reluctance
String foo = new TurboEncabulator();
```
* EOL
```javascript
String foo = new TurboEncabulator.encabulate(); // SEE: https://www.computerworld.com/article/2474570/computer-hardware/computer-hardware-the-long-and-glorious-history-of-the-turbo-encabulator.html
```
### Naming Conventions
* Functions and variables `MUST` be named in `camelCase` starting with a lowercase letter.
* Variables `MAY` occasionally be "namespaced" by using an underscore to separate the camelCased "namespace" and the camelCased variable name.
```javascript
var originalRequest_firstName = 'Batman';
var originalRequest_lastName = 'wouldntyouliketoknow';
var actualFound_firstName = 'Robin';
var actualFound_lastName = 'Twinkletoes';
```
* Classes and types `MUST` be named in `CapitalCase` starting with an uppercase letter.
* Constants (including class constants)`MUST` be named in `CAPITAL_SNAKE_CASE`, with underscores to separate words.**†**
* Function, class, and variable names `MUST` be named in a way that clearly describes what they do or what they are. It should be completely clear when reading through code what each variable represents and what each function call will do.
* Function, class, and variable names `MUST NOT` be abbreviated or acronym-ized _unless_ said abbreviation/acronym is standard and well-known. (e.g. HTML, XML, JSON)
* Variables and functions that, respectively, hold and return boolean values, `SHOULD` be named to start with a "boolean-izer" prefix such as `is`, `has`, `was`, `will`, `did`, etc.
```javascript
// BAD
var auth = token.userId.isNotEmpty;
// GOOD
var isAuthenticated = token.userId.isNotEmpty;
```
## Coding Best Practices
Generally, we try to adhere to the [best practices outlined by the Dart authors](https://www.dartlang.org/guides/language/effective-dart/usage) which are largely great patterns to learn. Below are a few additions and modifications to that wisdom:
### Variable Declaration Location
* With the exception of class properties, variables `SHOULD NOT` be declared in one large lump at the beginning of each function/method. Rather, they `SHOULD` be declared at the beginning of the _section_ of code that first uses them (if they're literally used throughout the function, declaring them at the top is fine).
> ###### This makes it much easier to keep track of what each variable holds in each section of code and prevents us from needing to scan the entire function checking for references to variables that may not even be used, yet.
###### DON'T DO THIS
```javascript
var foo = new Foo();
var bars;
var zip = false;
var zap;
// ======== Initialize Foo
if (foo.hasAThing) {
foo.doAThing();
} else if (foo.hasADifferentThing) {
foo.doADifferentThing();
}
// ======== Check for Bars
if (foo.isReady) {
bars = foo.getTheBars();
bars.forEach((bar) {
if (bar.id == foo.id) {
zip = true;
}
});
if (zip) {
foo.runAway();
}
}
// ======== Finish Up
zap = (foo.wasSuccessful && bars.isNotEmpty);
```
###### YES, DO THIS
```javascript
// ======== Initialize Foo
var foo = new Foo();
if (foo.hasAThing) {
foo.doAThing();
} else if (foo.hasADifferentThing) {
foo.doADifferentThing();
}
// ======== Check for Bars
var bars;
if (foo.isReady) {
var zip = false;
bars = foo.getTheBars();
bars.forEach((bar) {
if (bar.id == foo.id) {
zip = true;
}
});
if (zip) {
foo.runAway();
}
}
// ======== Finish Up
var zap = (foo.wasSuccessful && bars.isNotEmpty);
```
### Magic Numbers
The use of magic numbers other than 0 or 1 (and sometimes those, too), should be exceedingly rare. If such a number is needed, it is almost always appropriate to declare a new global or class constant to represent it.
```javascript
// DON'T DO THIS
if (httpResponse.statusCode == 403) {
log.info('request failed: unauthorized user');
}
// YES, DO THIS
if (httpResponse.statusCode == HttpStatus.UNAUTHORIZED) {
log.info('request failed: unauthorized user');
}
```
### Logging
#### Log Message Format
todo
#### Log Severity
todo
#### When to Log (and when not to)
todo
### Error Handling
#### Exception Message Format
todo
#### When to Throw (and when not to)
todo
#### Errors vs Exceptions
todo
#### How Much to Wrap in a Single Try/Catch Block
todo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment