Introduction
Welcome to Talawa-Admin
This section outlines the coding standards and best practices to follow when refactoring or writing TypeScript code for Talawa projects. Following these guidelines ensures readability, maintainability, and scalability across the codebase. Refer to CODE_STYLE.md for coding conventions to follow when contributing.
Before You Begin
It's important to consider these factors before you start.
Understand the Code Before Changing
-
Thoroughly understand the existing code's purpose and functionality before attempting to refactor it.
-
Identify areas for improvement and define clear refactoring goals.
Refactor in Small, Incremental Steps
-
Avoid large, sweeping changes. Break down refactoring into small, manageable tasks and PRs.
-
Commit changes often: to track progress and easily revert if issues arise.
Prioritize Testing
-
Write comprehensive unit tests before refactoring to ensure the code's behavior remains consistent after changes.
-
Run tests frequently during and after refactoring to catch regressions early.
Separate Refactoring from Feature Development
Avoid mixing refactoring with new feature development in the same commit or branch to maintain clarity and ease debugging.
Best Practices
Follow these guidelines for writing reusable TypeScript code.
Embrace Modularity
Modular design is a cornerstone of maintainable software architecture. By breaking down your codebase into smaller, well-defined parts, you make it easier to understand, test, and scale.
To explore how modularity translates into building consistent and maintainable front-end elements, refer to the Reusable Components guide
Focus on Readability and Maintainability
-
Maintain Consistent Naming Conventions and Code Style: Adhere to established naming conventions for variables, functions, and types, and use consistent code formatting. This enhances readability and makes it easier to navigate and understand the codebase.
-
Break down complex functions: Use smaller, more focused units.
-
Eliminate code duplication: Extract common logic into reusable functions or components.
-
Use appropriate data types: Leverage TypeScript's type system for enhanced type safety and clarity.
Document Your Code:
Use TSDoc comments to document functions, classes, and interfaces, explaining their purpose, parameters, and return values. This improves code readability and makes it easier for others (and your future self) to understand and use your reusable components.
Leverage TypeScript's Features
There are many useful practices that you should consider when writing TypeScript code for our repositories. These include the following:
-
Utilize Generics: By using generics, you create a scalable and maintainable foundation for your application, allowing for easy expansion and modification as your business needs evolve. Employ generics to create functions, classes, and interfaces that can work with various data types without sacrificing type safety. This allows you to write a single piece of logic that adapts to different inputs.
-
Here is an itemized list of advantages:
- Code Reusability: The same interface can be implemented for different types, reducing duplication.
- Type Safety: TypeScript ensures that the correct types are used in each implementation.
- Flexibility: You can easily create new repositories for new entities without writing redundant code.
- Consistency: All repositories follow the same structure, making the codebase more predictable and easier to maintain.
-
Generics allow you to write a single piece of logic that adapts to different inputs.
function identity<T>(value: T): T {
return value;
}-
For instance, a generic Repository interface can handle different entities like products, orders, or customers.
interface Repository<T> {
getById(id: string): Promise<T>;
getAll(): Promise<T[]>;
create(item: T): Promise<void>;
update(id: string, item: Partial<T>): Promise<void>;
delete(id: string): Promise<void>;
}
class ProductRepository implements Repository<Product> {
// Implementation here
}
class OrderRepository implements Repository<Order> {
// Implementation here
}
-
-
-
Define Clear Interfaces and Types: Use interfaces to define the shape of objects and types for complex data structures. This provides clear contracts for how data should be structured, promoting consistency and easier integration.
interface User {
id: string;
name: string;
email: string;
}
- Use Utility Types: Take advantage of TypeScript's built-in utility types like Partial, Readonly, Pick, and Omit to create new types based on existing ones, simplifying complex type manipulations and promoting code reuse in type definitions.
// Makes all properties of User optional
type OptionalUser = Partial<User>;
- Implement Type Guards: Use type guards to perform runtime type checking, ensuring your code handles different types correctly and safely within conditional blocks.
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
-
Consider using enums: When sets of related constants are used
-
Use access modifiers: Consider (public, private, protected) modifier for better encapsulation in classes.
-
Avoid
anyMinimize the use of theanytype as it defeats the purpose of TypeScript's type safety. Strive to provide explicit types or leverage type inference where possible.