Engineering/Coding Standards/TypeScript/TypeScript Class Design/

TypeScript Class Design Standards · TS-01

Types

Use TypeScript’s static typing feature to write clean code, catching type-related errors at compile-time rather than at runtime.

Use type annotations for function parameters and return types · TS-01.1 · MUST

Ensure type safety by always using type annotations for function parameters and return types.

Parameter and return type annotation example (✅)

function AccumulateCosts(costs: number[]): number {
  return costs.reduce((partialSum, nextValue) => partialSum + nextValue, 0);
}

Use type annotations variables of complex objects · TS-01.2 · MUST

Ensure type safety by always using type annotations on variables, when the type of such variables is complex and hard to discern.

Explicit typing example (✅)

let receipt: IReceipt = { 
  customerName: input.customerName,
  vatAmount: input.vatAmount,
  grossCost: input.netCost + input.vatAmount,
  serviceFee: (input.netCost * (100 / config.serviceFeePercentage)) 
};

Use const over let for variables that do not change · TS-01.3 · MUST

Declare immutability of variables that will not be reassigned after initialisation. Additionally, preventing accidental reassignment.

const declaration (✅)

const MAX_MESSAGES = 100;

let declaration (❌)

let MAX_MESSAGES = 100;

Use unknown over any · TS-01.4 · SHOULD

Enforce safe type checking before performing potentially unsafe operations by using unknown instead of any.

unknown example (✅)

function handleAnonymousRequest(request: unknown): string {
  // Code won't compile due to errors.
  const requestText = request.toUpperCase();

  // Good practice, checking the 
  if (typeof input === 'string') {
    return input.toUpperCase();
  }
}

any example (❌)

function handleAnonymousRequest(request: any): string {
  // Bad practice, compile-time safe but can cause runtime errors.
  return request.toUpperCase();
}

Use readonly for properties that won't change after initialisation · TS-01.5 · SHOULD

Utilise readonly when a class property won’t change after it has been instantiated as an object.

readonly example (✅)

class User {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }
}

Use undefined over null · TS-01.6 · SHOULD

Typescript, by default, represents missing values as undefined and advises that null is avoided in the official docs.

Use optional parameters · TS-01.7 · SHOULD

When writing several overloads that differ only in trailing parameters, prefer the use of optional parameters.

Optional parameter example (✅)

function getTotalCost(firstCost: number, secondCost: number, thirdCost?: number);

Overloaded parameter example (❌)

function getTotalCost(firstCost: number, secondCost: number);
function getTotalCost(firstCost: number, secondCost: number, thirdCost: number);

Avoid redundant type annotations when TypeScript can infer types · TS-01.8 · COULD

Explicitly typing everything in a codebase should be decided by the project team. Unecessary verbosity may lead to inconcise code and incurs overhead on development time.

Object-Oriented Programming

Leverage TypeScript’s OOP features to model applications around encapsulation, abstraction, inheritance and polymorphism.

Use interfaces to define object structures · TS-01.9 · MUST

Use interfaces to define the shape of objects, enforcing adherence to structures which model business requirements.

interface IReceipt {
  customerName: string;
  vatAmount: number;
  grossCost: number;
  serviceFee: number | null;
  groupBooking: boolean;
}

Use classes to define objects · TS-01.10 · MUST

Use classes to define objects with properties and methods.

class Payment {
  bill: IBill;

  constructor(bill: IBill) {
    this.bill = bill;
  }

  generateReceipt(): IReceipt {
    // Class specific logic
  }
}