Functions and utilities should operate at appropriate abstraction levels without being aware of higher-level concepts or implementation details. This prevents tight coupling and improves reusability and testability.
Functions and utilities should operate at appropriate abstraction levels without being aware of higher-level concepts or implementation details. This prevents tight coupling and improves reusability and testability.
When designing APIs, ensure that:
Example:
Instead of making a utility aware of a specific Client
type:
// ❌ Bad - utility knows about high-level Client concept
export function createCompositeProxy<T extends object>(client: Client, target: T, layers: CompositeProxyLayer[]): T {
// ...
getPrototypeOf: () => Object.getPrototypeOf(client._originalClient),
}
Use an abstract parameter that provides only what’s needed:
// ✅ Good - utility works with abstract prototype provider
export function createCompositeProxy<T extends object>(prototypeProvider: {}, target: T, layers: CompositeProxyLayer[]): T {
// ...
getPrototypeOf: () => Object.getPrototypeOf(prototypeProvider),
}
Similarly, abstract scattered conditionals into proper interfaces:
// ❌ Bad - conditionals scattered throughout class
if (TARGET_BUILD_TYPE === 'rn') {
// React Native specific logic
}
// ✅ Good - abstracted into library interface
const library = await libraryLoader.load()
await library.startTransaction(options)
This approach makes code more modular, testable, and maintainable by ensuring each component operates at its appropriate level of abstraction.
Enter the URL of a public GitHub repository