Coding Standard - melcholign/harvest-cart GitHub Wiki

Coding Standard

A coding standard is a set of guidelines and best practices that developers follow to ensure consistency, readability, and maintainability in their code. It defines rules on naming conventions, code structure, formatting, and other aspects of writing code, ensuring that all contributors write code in a similar, clear, and organized way. Adhering to a coding standard helps reduce errors, facilitates collaboration across teams, and makes it easier to review, debug, and scale software projects.

Naming Conventions

General Rules

  • Use descriptive names to avoid ambiguity.
// DON'T:
// Avoid using generic or unclear names

let x = 100;

// DO
let playerScore = 100;

  • Avoid uncommon abbreviations to prevent confusion.
// DON'T:

let cnt = 0;

// DO:

let counter = 0;

// Recognized abbreviation
let websiteUrl = "https://example.com";

Identifier Type Rules

Variables

  • Use camelCase to name variables declared with let.
  • Use nouns or noun phrases for variables containing primitive values or object references.
  • Use verb or verb phrases for variables that hold function references.
// Variable (Noun)
let userProfile = {
    name: 'John',
    age: 30
};

// Function reference (Verb)
let calculateScore = function(playerScore) {
    return playerScore * 2;
};

Constants

  • Use camelCase for constants (variables declared with const) when the value is dynamic.
  • Use UPPER_SNAKE_CASE for constants initialized with literal values.
  • Use nouns or noun phrases for constants containing primitive values or object references.
  • Use verbs or verb phrases for constants that store function references.
// Constant (Dynamic, camelCase)
 const maxUserAge = 120;

// Constant (Static, UPPER_SNAKE_CASE)
const PI = 3.14;

// Constant (Function reference, Verb)
const calculateTax = (amount) => amount * 0.1;

Booleans

  • Prefix boolean variables and functions with is, has, or can to clearly indicate that they represent true/false values.
// DON'T:

let active = true;

function validUser(user) {
    return user.age >= 18;
}

// DO:

let isActive = true;
let hasAccess = false;
let canEdit = true;

function isValidUser(user) {
    return user.age >= 18;
}

Event Handlers

  • Use the on prefix for variables holding event handlers or event listener references.
  • Follow the prefix with a verb or verb phrase.
// DON'T:

let clickHandler = () => { /* do something */ };
let onFormSubmission = () => { /* do something */ };

// DO:

let onClick = () => { /* do something */ };
let onSubmitForm = (event) => { /* form submission logic */ };

Error Objects

  • Use err or error as part of the name for variables representing error objects or responses.
// DON'T:

let e = new Error('Something went wrong');

// DO:

let errorResponse = new Error('Something went wrong');

Collections

  • Use plural names for variables representing arrays or collections to indicate multiple values.
// DON'T:

let item = [1, 2, 3];

// DO:

let items = [1, 2, 3];

Functions/Methods

  • Use camelCase for function/method names.
  • Use verbs or verb phrases to describe what a function/method does.
// DON'T:

function CalculateSum(a, b) {
    return a + b;
}

// DO:

function calculateSum(a, b) {
    return a + b;
}

Classes

  • Use PascalCase for class names.
  • Use nouns or noun phrases for class names, as classes represent objects or entities.
// DON'T:

class useraccount {
    constructor(name) {
        this.name = name;
    }
}

// DO:

class UserAccount {
    constructor(name) {
        this.name = name;
    }
}

Imports

  • When they are not renamed, match the names of named imports MUST correspond exactly with their exported names.
  • Follow the rules associated with their identifier types when renaming named imports, and naming default imports.
// DON'T
// Incorrect import statements

// Assuming retrieveData is actually named fetchData in api.js
import { retrieveData } from './api.js';  

// Not following naming conventions for clarity
import { calcSum as sum } from './mathUtils.js'; 

// Assuming that the module is exporting a class, 
// it should be a noun or noun phrase in PascalCase
import getUserData from './userService.js'; 

// DO
// Correct import statements

// Named import matches the export exactly
import { fetchData } from './api.js';  

// Renaming follows naming conventions for readability
import { calcSum as calculateSum } from './mathUtils.js'; 

// Class name is a noun phrase in PascalCase
import UserService from './userService.js'; 

Formatting

Indentations

Blocks

  • Add 4 whitespaces of indentation to the code inside it, on top of the indentation of its surrounding block.
// 0 whitespaces (ws)
function arraySum(arr) {
    // 0 + 4 = 4 ws
    for(let i = 0; i < arr.length; ++i) {
        // 0 + 4 + 4 = 8 ws
    }
}

Multiline Statements

For multiline statements, indented by 4 whitespaces for each line after the first.

console.log('Your full name is ' + firstName + ' ' + lastName
    + ' and your age is ' + age);

const results = func1() + func2() + func3() + func4()
    + func5(); 

Nested Structures

  • Apply indentation rules for blocks when writing nested structures of arrays and objects that are laid out over multiple lines.
const nestedObj = {
    // 4 ws
    prop1: {
        // 8 ws
        prop2: {
            // 12 ws
            ...
        },
    },
}

const nestedArr = [
    // 4 ws
    [
        // 8 ws
        [
            // 12 ws
            ...
        ],
    ],
]

const complexStructure = [
    // 4 ws
    {
        // 8 ws
        prop1: [
            // 12 ws
            ...
        ]
    },
    [
        // 8 ws
        ...
    ]
]

Whitespaces

General

  • Follow the guidelines on indentations for leading whitespaces.
  • Do not use trailing whitespaces.

Blank Lines

  • Leave a blank line after the end of a block, and before the start of the next statement.
// DON'T
if (condition) {
    // ...
}
const x = 5;

// DO

if (condition) {

}

const x = 5;
  • Avoid multiple blank lines.
// DON'T

const a = 10;


const b = 20;

// DO

const a = 10;
const b = 20;
  • Add a single blank line between two methods, and between an attributes and a method in a class, or object literal.
// DON'T

class Model {
    attr1;
    attr2;
    constructor() {
        // ...
    }
    method1() {
        // ...
    }
    method2() {
        // ...
    }
}

const obj = {
    prop1: 'val1',
    prop2: 'val2',
    method1() {

    },
    method2() {

    },
}

// DO

class Model {
    attr1;
    attr2;

    constructor() {
        // ...
    }

    method1() {
        // ...
    }

    method2() {
        // ...
    }
}

const obj = {
    prop1: 'val1',
    prop2: 'val2',

    method1() {

    },

    method2() {

    },
}

Single Space

Use a single space:

  • to separate any reserved word in a control statements
    1. such as if, for, or catch, from an open parenthesis (() that follows it on that line.
    2. such as else or catch, from a closing curly brace (}) that precedes it on that line.
// DON'T

// (1) 
if(conditon) {

// (2)
}else {

}

// DO

// (1)
if (condition) {

// (2)
} else {

}
  • before any leading brace ({) of blocks, and object literals (except when they are first in function arguments, and array literals).
// DON'T

function foo(){
    //...
}

// (exception)
foo( { prop: 0});

// DO

function bar() {

}

// (exception)
bar({ prop: 1})
  • on both sides of a binary or ternary operator.
// DON'T

const a=b+c;

const res = condition?100:-999;

// DO

const a = b + c;

const res = condition ? 100 : -999;
  • after a comma (,) or (;) but not before them.
// DON'T
const arr = [1 ,2 ,3] ; 

let x ;

for (let i ;i < n ;i++) ;

// DO
const arr = [1, 2, 3]; 

let x;

for (let i = 0; i < n; i++);
  • not before, but after the colon (:) in an object literal.
// DON'T

const obj = {
    prop :1,
}

// DO

const obj = {
    prop: 1,
}
  • after the leading and before the trailing braces of a single-line object literal and import statement.
// DON'T

import {i1, i2, i3} from 'module';

foo({prop: 1});

// DO

import { i1, i2, i3 } from 'module';

foo({ prop: 1 });
  • in comments according to the comment guidelines.

Line Breaks

General

  • Limit each line of code to 80 characters. Shorter lines are easier to read, especially on smaller windows.
  • Break a long line into multiple smaller lines.
// DON'T:

const result = someVeryLongFunctionNameThatCouldBeShortened(param1, param2, param3, param4, param5, param6);

// DO:

const result = someVeryLongFunctionNameThatCouldBeShortened(
    param1, param2, param3, param4, param5, param6
);
  • When breaking long lines, maintain proper indentation to indicate that the line is a continuation of the previous one.
// DON'T:

const result = calculateSomethingVeryComplex(firstParam, secondParam, thirdParam, fourthParam,
fifthParam, sixthParam);

// DO:

const result = calculateSomethingVeryComplex(
    firstParam, secondParam, thirdParam, fourthParam,
    fifthParam, sixthParam
);

Particulars

  • When breaking a long line, place the operator at the beginning of the new line to improve readability and avoid confusing indentation.
// DON'T:

const isValid = (user.isActive && user.isVerified && user.age > 18 && user.hasConsented);

// DO:

const isValid = (
    user.isActive 
    && user.isVerified
    && user.age > 18
    && user.hasConsented
);
  • If an object or array literal is too long for one line, place each key-value pair or array element on a new line.
// DON'T:

const user = { name: 'John Doe', age: 30, email: '[email protected]', isActive: true };

// DO:

const user = {
    name: 'John Doe',
    age: 30,
    email: '[email protected]',
    isActive: true
};
  • If a function call or definition has many parameters that exceed the line length limit, break them onto multiple lines for better readability.
// DON'T:

function registerUser(name, email, password, age, country, phoneNumber, termsAccepted) { ... }

// DO:

function registerUser(
    name, 
    email, 
    password, 
    age, 
    country, 
    phoneNumber, 
    termsAccepted
) {
    // Function logic
}
  • Break long import statements if they exceed the line length limit, especially when importing multiple items from the same module.
// DON'T:

import { calculateDiscount, applyCoupon, validatePromoCode, processPayment, sendEmailReceipt } from './utils/payment';

// DO:

import {
    calculateDiscount,
    applyCoupon,
    validatePromoCode,
    processPayment,
    sendEmailReceipt
} from './utils/payment';
  • If using arrow functions in chainable methods (e.g., .map(), .filter()), break each method call onto a new line.
// DON'T:

const results = users.filter(user => user.isActive).map(user => user.name).sort();

// DO:

const results = users
    .filter(user => user.isActive)
    .map(user => user.name)
    .sort();

Semicolons

  • Terminate all statements with a semicolon, except those that finish with a closing brace.
  • End variable declarations (using const and let), as well as import and export declarations, with a semicolon.
import { something } from `module.js`;

const a;
x = y + z;
func();

if (cond) {
    ...
}

Braces

  • Always use braces for blocks, block-like constructs, and object literals, even for single-line statements. This ensures clarity and prevents future errors when expanding the code.
// DON'T:

if (isActive) doSomething();

// DO:

if (isActive) {
    doSomething();
}
  • Open a brace on the same line as the statement it belongs to, and close the brace a new line, aligned with the statement that started the block.
// DON'T:

if (isLoggedIn)
{
    displayUserProfile();
}

// DO:

if (isLoggedIn) {
    displayUserProfile();
}

Comments

Purpose

  • Comments should explain the intent or rationale behind the code, not describe obvious functionality. Code should be self-explanatory. Comments should focus on the why, not the what.

// DON'T:

// Add price and tax
let total = price + tax; // Redundant comment

// DO:

// Total price includes the base price and the applied tax
let total = price + tax; // Explains why price and tax are combined

General

  • Avoid excessive commenting. Most of the code should be self-documenting through clear variable names and function definitions.
// DON'T:  
// Over-commenting trivial logic

// Set initial user count to zero
let userCount = 0; 

// Increase user count by one
userCount++;

// DO: 
// Self-explanatory code with meaningful identifiers

let userCount = 0; 
userCount++;
  • Align comments with the code they refer to, and start each comment with a space.
// DON'T:

//This is a comment with no space
let orderTotal = 100;

if (orderTotal > 50) {
// Comment alignment is off
    applyDiscount();
}

// DO:

// This comment is properly formatted
let orderTotal = 100;

if (orderTotal > 50) {
    // This comment is properly aligned
    applyDiscount();
}

Block Comments

  • Use /* ... */ for multi-line comments to describe complex logic or algorithm explanations.
  • Avoid using block comments for single-line comments.
  • Start subsequent lines with * aligned with the * on the previous line, to make comments obvious with no extra context.
  • Leave the opening and ending lines empty.
// DON'T:
// Misuse of block comments for single lines

/* This is a single-line comment */
let orderDiscount = calculateDiscount(orderTotal);

// Poorly formatted block comment

/*Apply discount:
*If the order exceeds $50.
* Calculation is based on total order value. */
let discount = calculateDiscount(orderTotal);

// DO:
// Use block comments for multi-line explanations, with consistent formatting

/*
 * Apply a discount based on the order total.
 * Orders above $50 get a 10% discount,
 * while orders above $100 get a 15% discount.
 */
let discount = calculateDiscount(orderTotal);

Inline Comments

  • Use // for single-line comments for short clarifications.
  • Place them on a new line above the code they describe, not at the end of a line, so that they do not clutter the code.
// DON'T:
// Inline comment on the same line as code

let shippingCost = 15;  // Add default shipping cost

// DO:
// Inline comment on a new line for better readability

// Add default shipping cost for standard orders
let shippingCost = 15;
  • Leave an empty line before the comment, unless it's on the first line of a block, so that it is easier to distinguish between logic and comments.
// DON'T:
// No separation between code and comment

let productPrice = 200;
let tax = productPrice * 0.1;
// Add tax to the final price
let finalPrice = productPrice + tax;

// DO:
// Empty line before the comment improves structure

let productPrice = 200;
let tax = productPrice * 0.1;

// Add tax to the final price
let finalPrice = productPrice + tax;

Code Organization

Modules

  • Group related functionality into separate modules (files).
  • Encapsulate a distinct feature, function, or domain logic in each module.
  • Avoid having large, monolithic files.
  • Examples:
    • auth.js for authentication logic.
    • user.js for user-related operations.
    • database.js for database interactions.

Packages

  • Organize modules into packages (directories) that represent distinct features or domains.
  • Group related modules in each package.
  • Keep file and folder names descriptive of their purpose.
/src
  /auth
    login.js
    signup.js
  /user
    profile.js
    settings.js
  /database
    connection.js
    queries.js

Single Responsibility Principle

  • Write classes and functions that focus on doing one thing and doing it well.
    • Classes should represent a single entity or service.
    • Functions should accomplish one task or logic flow.
  • Avoid multi-purpose classes and functions that handle too many tasks.
// DON'T: Multiple responsibilities in one class

class User {
    login() { /* handles login */ }
    saveProfile() { /* saves user profile */ }
    logout() { /* handles logout */ }
}

// DO: Separate responsibilities into distinct classes

class AuthService {
    login() { /* handles login */ }
    logout() { /* handles logout */ }
}

class UserProfileService {
    saveProfile() { /* saves user profile */ }
}

ES6 Modules

  • Use ES6 import/export syntax to organize your modules, ensuring clean and reusable code.
  • Use Named Exports when a module has multiple exports to clearly define what is being exported.
// auth.js
export function login() { /* login logic */ }
export function logout() { /* logout logic */ }

// main.js
import { login, logout } from './auth.js';
  • Use Default Exports when exporting a single class or function that represents the main functionality of the module.
// User.js
export default class User { /* class logic */ }

// main.js
import User from './User.js';

Functions

  • Use function declarations when you need a named function that should be hoisted,or when the function will be called in multiple places.
// Good for reusable, named functions
function calculateArea(radius) {
    return Math.PI * radius * radius;
}

  • Use arrow functions for callbacks, array manipulations, or functions that do not require a name or a context.
// Good for callbacks or simple operations
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
  • Avoid using traditional function expressions (const fn = function() {}) in favor of arrow functions or function declarations.

Function Decomposition

  • Break down long functions into smaller, reusable functions to improve readability and maintainability.
  • Write functions that handle small, logical units of work.
  • If a function does multiple things, it should be split up.
// DON'T: Long, complex function
function processOrder(order) {
    /* validation logic */
    /* total calculation logic */
    /* payment logic */
    /* email logic */
}

// DO: Decomposed into smaller functions
function processOrder(order) {
    validateOrder(order);
    const total = calculateTotal(order);
    chargePayment(order, total);
    sendConfirmationEmail(order);
}

function validateOrder(order) { /* validation logic */ }
function calculateTotal(order) { /* total calculation logic */ }
function chargePayment(order, total) { /* payment logic */ }
function sendConfirmationEmail(order) { /* email logic */ }

Separate Variable Declarations

  • Declare variables one by one, instead of chaining them in a single statement. This improves clarity and avoids mistakes when assigning values.
// DON'T: Multiple variables in one statement
let name = 'John', age = 30, isActive = true;

// DO: Separate declarations
let name = 'John';
let age = 30;
let isActive = true;

Class Organization

  • When organizing a class, maintain a clear structure for better readability and understanding.
  • Structure classes in the following order:
    1. Static Variables: Declare public static variables first.
    2. Private Static Variables: Next, declare static private variables (using # for privacy).
    3. Public Instance Variables: Declare public instance variables next.
    4. Private Instance Variables: Declare private instance variables following public ones.
    5. Constructor: The constructor should come after the variable declarations.
    6. Static Methods: Declare static methods following the constructor.
    7. Private Static Methods: Private static methods follow public static ones.
    8. Public Methods: Begin with getters and setters, followed by other public instance methods.
    9. Private Methods: Declare private methods last.
class User {
    // 1. Public Static Variables
    static defaultRole = 'user';
    
    // 2. Private Static Variables
    static #adminRole = 'admin';

    // 3. Public Instance Variables
    name;
    age;

    // 4. Private Instance Variables
    #password;

    // 5. Constructor
    constructor(name, age, password) {
        this.name = name;
        this.age = age;
        this.#password = password;
    }

    // 6. Public Static Methods
    static getDefaultRole() {
        return User.defaultRole;
    }

    // 7. Private Static Methods
    static #getAdminRole() {
        return User.#adminRole;
    }

    // 8. Public Methods (Getters/Setters first)
    getName() {
        return this.name;
    }

    setName(newName) {
        this.name = newName;
    }

    // 9. Private Methods
    #encryptPassword() {
        // password encryption logic
    }
}

References