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
}
}