Skip to content

Clean Code & Refactoring

A practical guide on how we apply SOLID, DRY, and SRP principles to maintain a sustainable and scalable codebase.

Single Responsibility (SRP)

Every class, module, or hook should have only one reason to change. We strictly separate Business Logic, Data Access, and Presentation.

Don't Repeat Yourself (DRY)

Avoid code duplication by abstracting shared logic into Services or Utilities. Example: TokenService and UserUtils.

1. SOLID Principles in Action

We don't just talk about theory. In Data Explorer, SOLID is applied to solve tangible scalability problems.

Open/Closed & Factory Pattern

When adding a new Database Engine, we don't modify core logic. Instead, we create a new Adapter and register it in the Factory.
Database Strategy Factory

# // Adheres to Open/Closed Principle

export class DatabaseFactory {

private static strategies = new Map<string, DatabaseStrategy>();

static register(key: string, strategy: DatabaseStrategy) {

this.strategies.set(key, strategy);

}

static get(key: string): DatabaseStrategy {

return this.strategies.get(key) ?? new DefaultStrategy();

}

}

Interface Segregation (ISP)

Instead of one generic interface, we break it down into IReadable, IWritable, and IMetadataProvider so adapters only implement what they actually support.

2. Decoupling State & Logic (Frontend)

We avoid writing data processing logic directly within React Components. Instead, we use Custom Hooks combined with a centralized ApiService.
ApiService (DRY)

Automatically injects Bearer Tokens and handles 401 errors (Logout) centrally.

Modular Hooks (SRP)

useProfileLogic is broken down into useUserProfile, useUserSettings, and useAccountSecurity.

3. Testing with Vitest

To ensure refactoring doesn't break existing features, we apply Unit Testing to core services and logic hooks.
Testing ApiService

describe('ApiService', () => {

it('should inject authorization header', async () => {

const response = await ApiService.get('/profile');

expect(global.fetch).toHaveBeenCalledWith(

expect.stringContaining('/profile'),

expect.objectContaining({ headers: { Authorization: 'Bearer mock-token' } })

);

});

});

💡 Tip: Use `vi.stubGlobal('fetch', mock)` and `vi.mock('@/core/services/store')` to simulate an isolated test environment.

Refactoring Workflow: From Legacy to Clean

Refactoring is a continuous journey. We apply the "Boy Scout Rule": Always leave the codebase cleaner than you found it.
  • 1
    Step 1: Isolation

    Identify messy logic and wrap it with an Interface.

  • 2
    Step 2: Coverage

    Write Unit Tests to protect current behavior before changing it.

  • 3
    Step 3: Decomposition

    Extract logic into smaller services following SRP.

  • 4
    Step 4: Dependency Injection

    Replace direct instantiation (`new`) with Dependency Injection.

Ready to explore data?

Join the community on GitHub to contribute to the smartest local database IDE.

Launch App