List of rules and conventions I follow when writing code #3
thetutlage
started this conversation in
Show and tell
Replies: 2 comments 2 replies
-
Thanks, @thetutlage very helpful |
Beta Was this translation helpful? Give feedback.
0 replies
-
What's your view on inheritance in general? In my team we've started using class HttpExceptionHandler {
public async handle() {}
}
class AuthExceptionHandler extends HttpExceptionHandler {
public override async handle() {}
} |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
In this post, I share the coding style, naming conventions, and the file structure naming conventions I will be using. Let's group all these under the Effective TypeScript name (borrowed from Effective Dart).
The following sources inspire the rules I have set for myself.
Naming conventions
This is not an extensive list of all the naming conventions I follow. Instead, it is a collection of the trickiest ones, which took some time to figure out.
Feel free to leave comments on the ones you want to discuss. I will try my best to answer :)
Avoid abbreviations
Avoid abbreviations and use complete words. Unless, the abbreviation is a standardised term. For example:
IO
,Id
,DB
,HTTP
.req -> request
,res -> response
,e -> error
, orbtn -> button
.Here's a great conversation on the same.
Use a non-imperative verb phrase for a boolean property
Non-imperative words are anything that is not a command. So for boolean properties, it is recommended to use non-imperative verb phrases.
Dart language design guide has a few more excellent examples of the same.
Use language (JavaScript) conventions
The readable/good naming conventions can often go against the grain of the language itself.
For example, it is more readable to write
users.where((age) => age > 18)
. However, the JavaScript users are already familiar with thefilter
method and its expected output.Therefore, it is recommended not to invent helpers who have the same utility as the functions already available in the language.
Another example is the
get
and theset
prefixes. Many languages guide against using them as a prefix on method names.However, JavaScript native APIs uses these prefixes quite a lot. For example:
new Date().getDate()
,new Date().setHours()
and therefore you can follow the language conventions in your APIs as well.File structure naming conventions
I used all
lowercase
names for the root level directories andPascalCase
names for sub-directories for a long time. The file name was based upon the contents of the file.PascalCase
.snake_case
, ordash-case
.There is no consensus on how files should be named in JavaScript. Each sub-community has its own rules or no rules altogether.
Also,
PascalCase
naming can have issues when the file system on your development machine is not case-sensitive. Therefore, an import statement might work fine on your computer but will fail on the server (if the server has a case-sensitive file system).After following the Dart guide, Google JavaScript guide, and looking at some of the angular core codebases, I have decided always to use
snake_case
for the entire file structure.snake_case
.snake_case
.Class properties vs. getters vs. methods
This is something I have struggled with for a long time. When to use class
properties
,getters
, andmethods
. I think I have a good mental model around them now.Never use getters and setters
Getters and setters never convey the operation's intent; they run behind the scenes. Also, I see little to no benefit in using them over a method. Whereas a method also allows me to accept the arguments in the future (possibility to evolve the API without breaking changes).
Use properties when they are defined at the time of instantiating a class
I use class properties only when they are defined at the time of instantiating the class. For example:
You can access the session id and the
readonly
property directly from the session class instance.However, the session data is only available as a method because it is not computed when creating the class instance.
As you can notice, the
#data
property is populated lazily. Therefore, I hide it behind a method call (which also allows populating the property if not done already).This can be a subjective choice. But, to silence my inner battle, I follow a simple rule. Anything lazily evaluated cannot be accessed as a property and must require a method call.
And do not confuse it something this is initially set to
null
, but can be mutated afterward. For example, Theconnection
property on some database model.Instead of exposing methods like
setConnection
andgetConnection
, you can allow mutating theconnection
property directly. Because the value is this property is never computed and is always defined manually.If the model has a chain-able API, I might introduce the
useConnection
method to allow fluently setting a connection.EsLint rules
The ESLint rules I follow to keep my codebase consistent.
@typescript-eslint/semi
Use two spaces for indentation. Rule documentation →
@typescript-eslint/quotes
Enforce consistent use of either backtick, double, or single quotes. Preference is given to single quotes. Rule documentation →
@typescript-eslint/space-before-function-paren
Enforce a single consistent space before the function parenthesis. Rule documentation →
@typescript-eslint/indent
Enforce consistent indentation throughout the code. This rule is partly broken for TypeScript. The rule's authors acknowledge this since it is tough to get it right for all different situations.
I have used this rule for years now and didn't have any issues. So please ignore this rule if you are fighting its edge cases. Rule documentation →
no-irregular-whitespace
Disallow irregular whitespaces. A mix of tabs and spaces will make this rule complain. Rule documentation →
no-multiple-empty-lines
Enforces a maximum of one empty line between two code blocks. Rule documentation →
one-var
Enforce each variable to be defined in its separate line. Rule documentation →
no-cond-assign
Disallow defining variables inside conditional blocks. Except for
while
blocks. Rule documentation →comma-dangle
Force multiline objects, arrays, imports, and exports to have trailing commas. There is a great article on the same, explaining why having trailing commas are helpful.
Rule documentation →
eqeqeq
Enforces to use of type-safe equality operators. Rule documentation →
eol-last
Enforces a new empty line at the end of the file. Rule documentation →
new-parens
Enforces construct class instances using the
new
keyword. Rule documentation →no-caller
Disallows using
arguments.caller
andarguments.callee
properties. Rule documentation →no-constant-condition
Disallows constant expressions inside conditionals. Rule documentation →
no-control-regex
Disallows control characters in regular expressions. Rule documentation →
no-debugger
Disallows the use of the
debugger
keyword. Rule documentation →You might find yourself using this rule quite often during development. Therefore, we recommend ignoring the error inside your editor and not turning off the rule.
no-duplicate-case
Disallows duplicate
case
blocks inside a switch statement. Each case block should handle a unique value. Rule documentation →no-eval
Disallow usage of the
eval
method. Rule documentation →no-ex-assign
Disallows re-assigning value to the error object inside the `try/catch block. Rule documentation →
no-extra-boolean-cast
Disallows unnecessary value casting to a boolean. Rule documentation →
no-fallthrough
Disallows case statement to fall through. Each case statement should either break or return from its block. Rule documentation →
no-inner-declarations
Disallows inner declarations of functions and variables. They should be defined at the top level or inside the top level of a given block. Rule documentation →
no-invalid-regexp
Disallows invalid regular expression strings in
RegExp
constructors. Rule documentation →no-proto
Disallows use of
__proto__
property. It has been deprecated. Rule documentation →@typescript-eslint/no-shadow
Disallows re-declaring variables already declared in the outer scope. The rule makes it harder to name things but ensures that wrong variable are not used during refactoring. For example:
The rule will fail because the
error
variable of thereadFile
method andwriteFile
methods can conflict.If we remove the
error
property from thewriteFile
callback, our code will still be valid. However, now it is referencing the outer scopevariable.
Therefore, this rule is essential to avoid these subtle inconsistencies in the code.
Rule documentation →
no-regex-spaces
Disallows multiple spaces in regular expression literals. Rule documentation →
no-self-compare
Disallows comparisons where both sides are the same. Rule documentation →
no-sparse-arrays
Disallow arrays with empty slots. Ideally, no one does it on purpose. Rule documentation →
no-mixed-spaces-and-tabs
Disallows mixed spaces and tabs for indentation. Rule documentation →
no-unsafe-negation
Disallow unsafe negated expressions.
Rule documentation →
no-new-wrappers
There is a good history behind
string
,number
, andboolean
primitives. This rule disallows creating these primitives instances usingnew String
,new Boolean
, etc. Rule documentation →no-self-assign
Disallows assignments where both sides are the same. Rule documentation →
no-this-before-super
Disallows the use of
this
before callingsuper()
in constructors. Rule documentation →no-with
Disallows
with
statements. No one anyways uses it. Rule documentation →rest-spread-spacing
Disallow space between rest/spread operators and their expressions. Rule documentation →
no-trailing-spaces
Disallows trailing whitespace at the end of lines (except comments).Rule documentation →
no-undef-init
Disallows initializing variables to `undefined. Rule documentation →
no-unsafe-finally
Disallows control flow statements inside
finally
blocks. This is a great one, do read the ESLint documentation. Rule documentation →padded-blocks
Disallows padding within blocks. Rule documentation →
space-in-parens
Disallows spaces inside of parentheses. Rule documentation →
use-isnan
Requires calls to
isNaN()
when checking forNaN
. Rule documentation →valid-typeof
Enforces using a valid data type string when using the
typeOf
expression. Rule documentation →brace-style
Enforces consistent brace style for blocks, i.e., one true brace style. Rule documentation →
curly
Enforces curly braces always to be present. Rule documentation →
@typescript-eslint/naming-convention
camelCase
,UPPER_CASE
, orPascalCase
.PascalCase
.PascalCase
.Interfaces should be in
PascalCase
and should not be prefixed to beI
.Rule documentation →
handle-callback-err
Enforces to handle the callback error. Rule documentation →
max-len
Enforces the code lines to max 100 characters long and comments to be 120 characters long. Except for template literals and string literals containing URLs. Rule documentation →
no-array-constructor
Disallows creating array using the
Array
constructor. However, you can create a sparse array of specified size by giving the array's length. Rule documentation →no-unreachable
Disallows unreachable code after
return
,throw
,continue
, andbreak
statements. Rule documentation →no-multi-spaces
Disallows multiple consecutive spaces. Rule documentation →
unicorn/prefer-module
Enforces the use of ES modules over CJS. This rule disallows using
__filename
,__dirname
, and many similar global properties. Rule documentation →unicorn/prefer-node-protocol
Enforces the
node:
prefix when importing native node modules. Rule documentation →unicorn/consistent-destructuring
Enforces the use of already destructured objects and their variables over accessing each property individually. Previous destructuring is easily missed, which leads to an inconsistent code style. Rule documentation →
unicorn/filename-case
Enforces all source files to be in snake case. Rule documentation →
unicorn/no-array-for-each
Enforces to use
for of
loop overforEach
loop. Rule documentation →unicorn/no-await-expression-member
When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. Rule documentation →
unicorn/no-for-loop
Enforces to use
for of
loop over traditionalfor
loop. Rule documentation →unicorn/no-instanceof-array
Disallows using
instanceof Array
. One must useArray.isArray
. Rule documentation →unicorn/prefer-number-properties
Enforces using methods like
parseInt
from theNumber
constructor and not using the global methods. Rule documentation →typescript-eslint/explicit-member-accessibility
Turn off explicit accessibility modifiers on class properties and methods. For a long time, they are on, but as we have got private members in JavaScript classes (with runtime safety), there is little or no value in using the
private
modifier of TypeScript.Since, I am not using
private
, there is also no need to use thepublic
modifier, because everything is public by default. Therefore, the only modifier of use isprotected
.The following rule forces you to never prefix methods and properties with the
public
modifier. The same setting for theprivate
does not exist for now, but there is an open issue for the same.How I used to define private and public properties earlier
How I will define private and public properties moving forward
Beta Was this translation helpful? Give feedback.
All reactions