Carbonteq
Best Practices

SOLID Principles

Single Responsibility Principle

Don't

Pack multiple functionalities into one class. This creates:

  • Poor conceptual cohesion
  • Multiple reasons for the class to change
  • Difficult to understand dependencies
  • Hard to modify without affecting other parts
DoClick to view

Separate concerns into focused classes. This provides:

  • Clear single responsibility for each class
  • Easier maintenance and testing
  • Better code organization and reusability
  • Reduced coupling between components
class UserSettings {
constructor(private readonly user: User) {}
changeSettings(settings: UserSettings) {
if (this.verifyCredentials()) {
// Update settings logic
}
}
verifyCredentials() {
// Authentication logic
}
}

Open/Closed Principle

Don't

Modify existing code to add new functionality. This approach:

  • Violates the open/closed principle
  • Requires changing tested code
  • Increases risk of introducing bugs
  • Creates tight coupling between components
DoClick to view

Use abstraction to enable extension without modification. This provides:

  • Open for extension through inheritance
  • Closed for modification of existing code
  • Consistent interface for all implementations
  • Easy addition of new functionality
class HttpRequester {
constructor(private readonly adapter: Adapter) {}
async fetch<T>(url: string): Promise<T> {
if (this.adapter instanceof AjaxAdapter) {
return await makeAjaxCall<T>(url);
} else if (this.adapter instanceof NodeAdapter) {
return await makeHttpCall<T>(url);
}
// Need to modify this class for each new adapter type
}
}

Liskov Substitution Principle

Don't

Create subclasses that violate parent class contracts. This causes:

  • Unexpected behavior when substituting objects
  • Breaking the "is-a" relationship assumption
  • Runtime errors and incorrect results
  • Violation of polymorphism principles
DoClick to view

Design inheritance that maintains behavioral contracts. This ensures:

  • Proper substitutability of parent and child objects
  • Consistent behavior across the inheritance hierarchy
  • Reliable polymorphism without surprises
  • Maintainable code with clear contracts
class Rectangle {
constructor(protected width = 0, protected height = 0) {}
setWidth(width: number): this {
this.width = width;
return this;
}
setHeight(height: number): this {
this.height = height;
return this;
}
getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width: number): this {
this.width = this.height = width; // Violates LSP
return this;
}
setHeight(height: number): this {
this.width = this.height = height; // Violates LSP
return this;
}
}
// BAD: Square returns 25, Rectangle returns 20
const shapes = [new Rectangle(), new Square()];
shapes.forEach(shape => {
console.log(shape.setWidth(4).setHeight(5).getArea());
});

Interface Segregation Principle

Don't

Create large interfaces with many unrelated methods. This forces:

  • Clients to implement methods they don't use
  • Unnecessary dependencies on unused functionality
  • Violation of single responsibility at interface level
  • Difficult maintenance and testing
DoClick to view

Create focused interfaces with related methods. This provides:

  • Clients depend only on methods they use
  • Flexible composition of functionality
  • Easy implementation without unused methods
  • Better maintainability and testability
interface SmartPrinter {
print();
fax();
scan();
}
class EconomicPrinter implements SmartPrinter {
print() { /* Print logic */ }
fax() {
throw new Error("Fax not supported."); // Forced to implement
}
scan() {
throw new Error("Scan not supported."); // Forced to implement
}
}

Dependency Inversion Principle

Don't

Depend directly on concrete implementations. This creates:

  • Tight coupling between modules
  • Difficulty in testing with mocks
  • Hard to change implementations
  • Reduced flexibility and reusability
DoClick to view

Depend on abstractions and inject dependencies. This enables:

  • Loose coupling between modules
  • Easy testing with dependency injection
  • Flexible implementations without code changes
  • Better maintainability and extensibility
class XmlFormatter {
parse<T>(content: string): T {
// XML parsing logic
}
}
class ReportReader {
private readonly formatter = new XmlFormatter(); // Tightly coupled
async read(path: string): Promise<ReportData> {
const text = await readFile(path, "UTF8");
return this.formatter.parse<ReportData>(text);
// Hard to test or change formatter
}
}