tslint-config-visibacare
v0.2.0
Published
VisibaCare's style guide
Downloads
5
Readme
This is for code going into 2019
Visiba Care's Style Guide 🌟
The bare minimum for clean code.
The point of these guides is to get a quick overview of how Visiba Care's code standard should look like!
JavaScript
This style guide is based on the standards of prevalent JavaScript - it's based on Airbnb's JavaScript Style Guide but it's written more encapsulated to highlight the basics. Its intent is to follow the conventional code standard that the JavaScript community is used to.
We're using primarily TypeScript but doing small libs in an es6+
standard is fine too.
CommonJS is only allowed when dealing with Node.js related issues or scripts.
Table of Contents
- Basic rules
- Performance
- Transpile
- Declaration & Naming Convention
- Object & Arrays
- Destructuring
- Blocks, Comparison Operators & Equality
- Module
Basic rules
- Simple code is good code.
Anything that gets complex becomes hard to understand. Code needs to be maintainable, testable and reusable. Refactoring with a reason is encourage.
What defines "good code"?
It's well-organized. Naming conventions represent its content. Properties and methods in classes fit together. The code should look visually clean and not like “spaghetti”.
It's testable. Tests serve as an executable specification of the code and examples of its use.
It's not “clever”. It does things in straightforward, obvious ways.
It's developed in small, easy to read modules and files. These modules are reused throughout the code. The DRY principle (Don't repeat yourself) should always be applied if seems fit.
It's taking performence into account. We want to deliver smooth applications for the users. Not having a performance budget hurts everyone.
Sometimes it's easier to cut corners to save time, but in reality it'll make the application less performant, less efficent and less readable. In the long run it'll cause more headache than if it was made in the right way from the begining.
Try to keep that in mind.
- Use tabs over spaces.
Why? Helps with developer happiness. Some developers want to have an indentation of two spaces, other four. Tabs allow both.
- Do group properties & variables with a line separation.
Why? If it makes sense for them to be grouped, then it'll do no harm and will most likely help with readability.
- Do initialize variables and properties that is meant to hold a value with
null
.Why? It helps to unify the code with nullable API responses. A value that's set to null is purposefully set to "no value".
- Break stuff into modules.
Once a something become too large or is complex enough that it is interfering with understanding the rest of the file, make a module out of it. It will easier to maintain, test and - if wrote well - reuse.
- TypeScrpt
- Don't/Avoid using
any
.Why? Encourages lazy patterns that will be hard to debug and test. Should only be used when something is truly any.
- Don't type what's already typed.
Why? Unnecessary to type stuff more than once. e.g.:
const baz: Bar = new Bar() as Bar;
- Avoid using
NameSpace
in code.Why? A file should be viewed as a module. Once each file is a module the need to NameSpace modules becomes irrelevant.
- Use strict mode
Why? Increases type safety.
- Don't/Avoid using
Performance
- Be aware of bundle size.
Why? Not everyone is sitting on a fiber connection, every byte matter.
- Animations should be 60 FPS.
Why? Looks good yo.
- Don't intruduce large node modules (> ~40Kb gzip) that doesn't support tree shaking.
Why? Bundle size matter.
- Avoid using third party framework components.
Why? Often they do not correspond with our setup or standard.
- Avoid intruducing large libs without a real purpose.
Why? You don't need jQuery. :-)
Transpile
To be researched
Declaration & Naming Convention
Naming:
- Be descriptive with your naming. Avoid single letter names and over prefixed/suffixed names.
Why? Readability. Other developers should not have to spend hours to contextualize the code.
- Use whole words when possible.
Why? Readability. Other developers should not have to spend hours to contextualize the code.
- To be consistent with naming conventions, do use the following:
- is
Boolean
- has
Boolean
- on
Function
- handle
Function
Why? Enforces consistency.
~ Exeptions:
When it comes to standard abbreviations, conventional stuff that's seen often, do use them. These are stuff like:init
,i in a loop
etc. - is
- Be descriptive with your naming. Avoid single letter names and over prefixed/suffixed names.
Files: When naming a file.
- A filename should exactly match the name of its default export.
Why? Logical. When entering a file that's called
foo.ts
thenclass Bar { }
shouldn't be there. - If there's no defualt exports then the naming should represent what being exported.
Why? Provides the intent with the file to other developers.
- A filename should exactly match the name of its default export.
Variable: When declaring a reference.
- Use
camelCase
for name.Why? Conventional JavaScript
- Don't use
var
.Why? Variables declared with
var
are not block-scoped. - Use
const
for all references.Why? This ensures that you can’t reassign your references. Const is also block-scoped.
- Use
let
if an references must be reassign.Why?
let
is block-scoped rather than function-scoped likevar
.
// ❌ var wowVariable = 'wow'; var sickVariable; sickVariable = 'sick'; // ✔️ const wowVariable = 'wow'; let sickVariable; sickVariable = 'sick';
~ Exeptions:
Some times you may occasionally uppercase a constant. But only if it's exported or used in the upper scope of a file.
Why? This is an additional tool to assist in situations where the programmer would be unsure if a variable might ever change. UPPERCASE_VARIABLES are letting the programmer know that they can trust the variable not to change.
// Example export const API_KEY = '1234567890';
- Use
Function: When declaring a function.
- Use
camelCase
for name.Why? Conventional JavaScript.
- Don't space a function signature or a function call.
Why? Conventional JavaScript. You shouldn’t have to add or remove a space when adding or removing a name.
- Use use arrow functions.
Why? (1) Arrow functions lexically bind their context, so
this
refers to the originating context. (2) Declaring functions like this with atype
also makes sense from aTypeScript
perspective. I.e.:const bar: Foo = () => foo;
(3) And sometimes it just results in cleaner code, especially when the function only takes an argument and returns it:const foo = value => something(value);
- Use arrow functions in callbacks.
Why? Less side effects. It will result in cleaner code and
this
will be lexically binded. - Do use named function expression.
Why? Names can be seen in stack traces, call stacks and lists of breakpoints.
- Don't declare a function in a non-block (
if
,for
,while
, etc).Why? Browsers will all interpret it differently, aka not nice.
- Don't use underscore to conventionally ignore parameters.
Why? There's no reason to. It will just cause confusion for other developers.
// ❌ [{ name: 'Luke' }].map(function (person) { return person.name }); function Foo () { } const Bar = _ => { }; Baz (); // ✔️ [{ name: 'Luke' }].map(person => person.name); function foo() { } const bar = () => { }; baz(); // ✔️ Also valid const short = function superLongNameThatIsReallyAdvanced() { };
~ Note:
If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module.
- Use
Class: When declaring classes.
- Use
PascalCase
for name.Why? Conventional JavaScript.
- Use
camelCase
for members. (properties and methods)Why? Naturally follows from variable and function naming convention.
- Don't space methods.
Why? Conventional JavaScript.
- Do use arrow functions if needed.
Why? Arrow functions lexically bind their context so
this
refers to the originating context. Should follow function rules.
// ❌ class foo { private Bar: string; public Baz () { } public Qux = () => { }; } // ✔️ class Foo { private bar: string; public baz() { } public qux = () => { }; }
- Use
Access Modifiers: When declaring public / private.
- Always declare the classes own members with
public or private
- Don't use leading underscore for private fields.
Why? TypeScript: There's no need to use an extra clarification for it, the IDE will show what fields are public anyhow. A developer will also have to make a lot of unnecessary renaming when changing a
public
toprivate
or vice versa, and the risk is that developers will forget or ignore about it.Regular EcmaScript: https://github.com/tc39/proposal-class-fields
Once class fields become a standard, the naming will be a clear indication from the start. - Don't use
public
when overwriting extended members.Why? Creates an distinction between inherited members and the current class's members.
- Always declare the classes own members with
Namespace: When declaring interfaces.
- Use
PascalCase
for name.Why? Convention followed by the TypeScript team and community.
// ❌ namespace foo { export class Bar { } } // ✔️ namespace Foo { export class Bar { } }
- Use
Interface: When declaring interfaces. tslint: semicolon
- Use
PascalCase
for name.Why? Similar to class. A class may implement an interface as if it were a class.
- Use
camelCase
for members.Why? Similar to class. A class may implement an interface as if it were a class.
- Use semicolon at end of statement.
Why? Similar to class. A class may implement an interface as if it were a class.
- Don't prefix with
I
.Why? Unconventional. The interface ends up as an extra declaration of the same class, it losses its meaning to exist.
// ❌ interface IFoo { Bar: string; } class Foo implements IFoo { } // ✔️ interface Foo { bar: string; } class Bas implements Foo { }
~ Exeptions:
When writing types for respones, match its convention exactly.
The most common case for us is to use
PascalCase
for members when mapping interfaces for.NET
responses.Why? A response should always be typed as how it will be fetched. We'll alternate the data that will be use onto another object.
- Use
Type: When declaring types.
- Use
PascalCase
for name.Why? Convention followed by the TypeScript team and community.
- Use
camelCase
for members.Why? Types are similar to primitives and complex types. If could have members they should be asigned like conventional JavaScript.
// ❌ type sickString = string; // ✔️ type SickString = string;
- Use
Enum: When declaring enums.
- Use
PascalCase
for name.Why? Convention followed by the TypeScript team and community.
- Use
camelCase
for properties.Why? Similar to object.
// ❌ enum sickEnum { }; // ✔️ enum SickEnum { };
- Use
Array: Declare primitives and type references as
foos: Foo[]
. tslint: array-typeWhy? It's the standard way to define it. It's used by the TypeScript team types generated for DefinitelyTyped repos. Consistency is good.
// ❌ const foos: Array<Foo> = []; const foos: Array<number> = []; // ✔️ const foos: Foo[] = []; const foos: number[] = []; // Do use a generic array for complex types, i.e. functions / objects / arrays: const foo: Array<() => boolean> = []; const bar: Array<Bar[]> = []; const baz: Array<{ name: 'Leia' }> = []; // Instead of: const foo: (() => boolean)[] = []; const bar: Bar[][] = []; const baz: { name: 'Leia' }[] = [];
Object & Arrays
- Use array spreads
...
to copy arrays.
Why? Reduces the amount of steps for doing this
- Use object spread operator over Object.assign
Why? Much easier way to handle shallow-copying.
- Group shorthand properties at the beginning of your object declaration.
Why? It’s easier to tell which properties are using the shorthand.
Destructuring
- Use object destructuring when accessing and using multiple properties of an object.
Why? Destructuring saves you from creating temporary references for those properties.
- Use array destructuring.
Why? Destructuring saves you from creating temporary references for those properties.
// For Objects
// ❌
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// ✔️
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// ✔️✔️
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
// For Arrays
// ❌
const first = arr[0];
const second = arr[1];
// ️✔️
const [first, second] = arr;
Blocks, Comparison Operators & Equality
Blocks:
- Use braces with all multi-line blocks.
Why? Enforce consistency. A new line after a staement should always result into a clear block.
- When on multi-line, don't put
else
under theif
's closing bracket.Why? Don't be evil. Conventional JavaScript.
// ❌ if (foo) return false; else return true; // ❌ if (foo) return false; // ❌ function bar() { return false; } // ❌ if (bar) { return 'Luke'; } else { return 'Vader'; } // ✔️ if (foo) { return false; } else { return true; } // ✔️ if (foo) return false; // ✔️ function bar() { return false; } // ✔️ if (bar) { return 'Luke'; } else { return 'Vader'; }
- Use braces with all multi-line blocks.
Comparison Operators. tslint: triple-equals
Use strict comparison operators, i.e.
===
and!==
over==
and!=
Why? Non type-safe equality operators does not account for type equality.
Use shortcuts for booleans, but explicit comparisons for strings and numbers.
Why? The intent of the comparison will be more clear for strings and numbers. For booleans it will essential mean
true === true/false
, instead of justtrue/false
.Use shortcuts to check if an object exist.
Why? The world of JavaScript have both undefined and null:
// ❌ instead of ending up with code like this: if (value !== undefined || value !== null) { } // value -> array // ✔️ just evaluate it like this: if (value) { }
~ Note:
Will not work for deep properties comparison that may not have been set.
// ❌ this will throw an exception if "doesNotExist" is undefined: if (value.doesNotExist) { } // ✔️ this will won't: if (typeof value.doesNotExist !== 'undefined') { }
// ❌ if (isValid === true) { // isValid -> boolean return true; } // ❌ if (name) { // name -> string return true; } // ❌ if (list.length) { // name -> array return true; } // ✔️ if (isValid) { // isValid -> boolean return true; } // ✔️ if (name !== '') { // name -> string return true; } // ✔️ if (list.length > 0) { // list -> array return true; }
~ Note:
Conditional statements such as the
if
statement evaluate their expression using coercion with theToBoolean
abstract method and always follow these simple rules:- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evaluate to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string '', otherwise true
Module
- Use
import/export
over any unconventional module system.Why? Conventional JavaScript.
- In a module with a single export, use default over named.
Why? To encourage more files that only ever export one thing, which is better for readability and maintainability.
- Don't use wildcard imports.
Why? This makes sure you have a single default export.
- Avoid exporting mutable bindings.
Why? It'll be a mess if.
- Do use following structure:
// Typical structure: import { v4 } from 'uuid'; // dependencies imports ↓ import VCareMediaStream, { getStream } from '../VCareMedia'; // relative imports ↓ export type VCareMediaStream = MediaTrack; // typings ↓ (may be exported over here) const API_KEY = '1234567890'; // variables in module scope ↓ function getTicket() { } // function ↓ class MediaStreamHook { } // class ↓ export { getTicket }; // named export ↓ export default MediaStreamHook; // default export (file name is MediaStreamHook.ts) // or export default getTicket; // default export (file name is getTicket.ts) // For a file that exports many constants or functions: export const oneConst = '123'; export const anotherConst = '456'; // ... etc export oneFunction() { } export anotherFunction() { } // ... etc