Skip to content

Documentation about how I believe code should look like, good practice and tips.

Notifications You must be signed in to change notification settings

aeberdinelli/js-guidelines

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 

Repository files navigation

This is my set of a few good practice guidelines for JS/TS. In my experience, applying these rules will make the code a lot easier to maintain.

Table of contents {

}

Guidelines for JS {

🠹 General rules

This applies to any language not just JS!

  • Avoid using libraries when possible, especially if you can replicate the methods easily (lodash users I'm talking to you). Reasons? Over-sizing your app, and adding code you don't know.
  • Always focus on readability rather than "looking smart". Avoid using shorthands or hacks, or using single-character named variables. At the end of the day, the language is not what will make your app slow. If you need to optimize your app, try to optimize images and http requests.
  • Prevent duplicated code. You can create any number of reusable functions and it will be far better than re-writing code. Why? If you have to refactor later, you will have to find every copy of the logic while you could just update the function instead.
  • Keep functions small. Your function should not work for more than 1 use case. Avoid using a lot of if/else that changes the way it works, create several functions instead. The only place where you could change the implementation based on conditions is within your controllers. In that case, you would call the correct function based on your desired condition.

🠹 General style rules

  • Always use ; at the end of each line
  • Always include spaces between brackets: import { Thing, OtherThing } from './stuff';
  • Always declare arrays in single line when it has only a few elements: [ 'thing', 'thing2' ];
  • Keep your code easy to understand, if you're using ternary operators, use it only for one condition - do not chain them
  • No matter what kind of indentation you use (spaces/tabs) as long as you use width of 4, this keeps the code clean and very easy to read
  • Use spaces after each parameter in a function or method and between different parts of the declaration: function something(param1, param2, param3) {
  • Use spaces to keep conditions and loops easy to read: if (condition) { / for (let i = 0; i < 5; i++) {

🠹 Always prefer const

I prefer this for easy debugging and preventing errors. Remember, const means you cannot re-assign that variable, but you can change it's value if it is an array or an object.

Updating an array

const rows = [];

// This is a React example, but it's valid for other cases too
people.forEach(person => {
    rows.push(
        <Row>
            <Col>{person.name}</Col>
        </Row>
    );
});

Updating an object

const settings = {};

settings.darkMode = true;

🠹 Exports

Export only what you need

If you have a module with the main method being something like lowercase, export only that method.

function internal(str) {
    return str.toLowerCase();
}

function lowercase(str) {
    return internal(str);
}

// Do not export everything
module.exports = { internal, lowercase };

// Instead, export the useful methods
module.exports = { lowercase };

Export named methods

This also applies to classes. Is better to export a named method instead of a default export. This way, you will force everyone to use your method with the name that you specified and will make maintaince a lot easier, knowing exactly each place the exported method is used. Let me show you an example:
// This is a default export
module.exports = function() {}

// This is a named export
function something() {}

module.exports = { something };

// Example for default export usage
// Both will work and they will have different name while they are the same method
const something = require('./something');
const notSomething = require('./something');

// This will make the method use the same name across your project (unless changing it on purpose)
// Plus instead of importing the whole module, you just import the method you need
const { something } = require('./something');

🠹 Arrow functions?

I know, arrow functions are sexy. However, they are not always needed. Considerate using them only when:

  • You need to maintain context for this
  • You only have a single-line logic, like when using .map() or .filter()

This will make also the previous rule easier to keep.

🠹 Null checks

Always add nullchecks to prevent errors in your console and breaking the flow.
You can use the falsy check or defaulting some value with the || operator. For example:

Defaulting a value

const name = user.name || 'Unavailable';

Using the new optional property operator

const name = user?.details?.name || 'Unavailable';

🠹 Looping through arrays

Try to not use the same iterator method everytime. Instead, use whichever is the better for your case.

Removing items based on condition

const adults = people.filter(person => person.age >= 18);

Performing an operation for each element

Use .forEach() if you don't need to create a new array

people.forEach(person => registerPerson(person));

Customizing each element of the array

In this case, you can use .map() since you need a new array with elements changed

// This way you add new properties to array
const people = peopleArray.map(person => ({
    ...person,
    isAdult: (person.age >= 18)
});

Using a certain item from array

If you know what items you have in an array and want to use them, for instance the result of a .split() operation, you can do something like this:

// Instead of
const parts = fullname.split(' ');
const name = parts[0];
const lastname = parts[1];

// You can do
const [ name, lastname ] = fullname.split(' '); 

🠹 Spread operator

I know sometimes it looks fancy but if you need to grab 8 properties from a 10 properties object, then it's more convenient to just use the object instead of spreading each property from it:

// Instead of doing
const { prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8, prop9, prop10, prop11, prop12 } = obj;

// Refence the object itself
<p>Name: {obj.prop1}</p>
<p>Lastname: {obj.prop2}</p>

Your code will look cleaner with a lot less lines while getting the same result.

🠹 Conditions

Do not use redundant checks

If you have a condition that always returns either true or false, you don't need to compare it:

// Do not use
if (something == true) {

// Instead just do
if (something) {

This applies to ternary operators as well

// Do not use
result = (condition != "") ? true : false;

// Just do this
result = (condition != "");

And in functions too

// Do not do this
function isSomethingTrue(something) {
    if (something) {
        return true;
    } else {
        return false;
    }
}

// Instead do this
function isSomethingTrue(something) {
    return (something);
}

You don't need else

Sounds weird, but trust me your code will start to look a lot cleaner and easier to understand.
You can use return or throw to stop the flow. There's a good article right here ↗️ if you'd like to read more about this.

function getName() {
    if (!person.name) {
        return 'Name not available';
    }
    
    return person.name;
}

🠹 Typescript

"The whole point of using JS is that I don't have to care about types". I know, most JS developers when first presented TS hate it for this reason. I can assure you, it will help you prevent a lot of common errors. There's no need to use it but if you do, here's some useful tips.

Type everything!!

Types are not only useful to prevent errors and fail compilation if there's a typo somewhere. They're also extremely useful for new developers. You can define the interface of your API (and other things) and welcome new developers to your project.

interface CreateUserRequest {
    name: string;
    lastname: string;
    age?: number;
}

// This way people just need to check CreateUserRequest interface to find out what properties you accept
// Also if the IDE supports it, they will get autocomplete to make the DX delightful.
export function CreateUser(body: CreateUserRequest) {

Keep it simple

Let's say your app depends on a service that returns a huge object and you only need a few properties. You don't need to type every property in your project, instead, you can use an interface with the properties that you know and use.

Avoid duplicated types

You don't need to rewrite types if you only have to discard one of two properties. Imagine you have a REST API and you have a product entity. When you create an object for that entity, you don't know the Id, but when you make a GET request you do. In that case you can do something like this:

export type Product {
    name: string;
    price: number;
    id: string;
}

// This is the same as Product, without the id
export type ProductCreate = Omit<Product, 'id'>;

Dynamic types

Most TS developers don't take full advantage of the features available. There are a lot of places where any should not be an option. Usually they use any when they want to reuse a method across different entities. But you don't need to do that!

// ResponseType is dynamic and you can send TS what type you are expecting from the method
export function Request(userId: number): Response<ResponseType> {
    return { name: 'Alan' } as ResponseType;
}

// You use it like this:
type User = { name: string };

// response will be of type User and the IDE will get full autocomplete options too
const response = Request<User>(1);

Monorepo?

If you have a React/Node app and a monorepo, you can share your types between both parts of the app. This way, you only have to update the types in one place and is easier to maintain.

};

About

Documentation about how I believe code should look like, good practice and tips.

Topics

Resources

Stars

Watchers

Forks