mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
338 lines
7.1 KiB
Markdown
338 lines
7.1 KiB
Markdown
# Anti-Patterns
|
|
|
|
Common mistakes to avoid when designing or evaluating TypeScript library APIs.
|
|
|
|
## API Design Anti-Patterns
|
|
|
|
### Function Overloads
|
|
|
|
```ts
|
|
// Poor: TypeScript errors become "none of the 5 overloads match"
|
|
function create(config: Config): Store;
|
|
function create(initialState: State): Store;
|
|
function create(initialState: State, options: Options): Store;
|
|
|
|
// Good: Single config object
|
|
function create(config: { initialState?: State; options?: Options }): Store;
|
|
```
|
|
|
|
**Why it fails:** Autocomplete can't determine intended overload. Each optional parameter multiplies overload count.
|
|
|
|
### Boolean Traps
|
|
|
|
```ts
|
|
// Poor: What do these booleans mean?
|
|
createPlayer(true, false, true);
|
|
|
|
// Good: Named options
|
|
createPlayer({ autoplay: true, muted: false, loop: true });
|
|
```
|
|
|
|
### Multiple Competing APIs
|
|
|
|
```ts
|
|
// Poor: Confusing - which to use?
|
|
store.set({ volume: 0.5 });
|
|
store.setState({ volume: 0.5 });
|
|
store.update({ volume: 0.5 });
|
|
store.volume = 0.5;
|
|
|
|
// Good: One obvious way
|
|
store.setState({ volume: 0.5 });
|
|
```
|
|
|
|
### Runtime Plugin Registration
|
|
|
|
```ts
|
|
// Poor: Loses type safety, implicit ordering
|
|
store.use(middleware1);
|
|
store.use(middleware2);
|
|
|
|
// Good: Composition at creation
|
|
create(middleware1(middleware2(fn)));
|
|
```
|
|
|
|
### Implicit Magic Dependencies
|
|
|
|
```ts
|
|
// Poor: Where does MediaContext come from?
|
|
function useVolume() {
|
|
const { volume } = useContext(MediaContext); // Not obvious
|
|
return volume;
|
|
}
|
|
|
|
// Good: Explicit dependency
|
|
function useVolume(store: MediaStore) {
|
|
return useSnapshot(store.state).volume;
|
|
}
|
|
```
|
|
|
|
### Over-Abstraction
|
|
|
|
```ts
|
|
// Poor: AbstractFactoryManagerProvider
|
|
const factory = new PlayerFactoryManager();
|
|
const provider = factory.createProvider();
|
|
const player = provider.getInstance();
|
|
|
|
// Good: Direct API
|
|
const player = createPlayer();
|
|
```
|
|
|
|
---
|
|
|
|
## TypeScript Anti-Patterns
|
|
|
|
### Forcing Explicit Generics
|
|
|
|
```ts
|
|
// Poor: User must annotate
|
|
const store = createStore<MyState, MyActions>({ ... });
|
|
|
|
// Good: Infer from usage
|
|
const store = createStore({ ... });
|
|
type State = typeof store.state;
|
|
```
|
|
|
|
### `unknown` in Public API
|
|
|
|
```ts
|
|
// Poor: Forces casting everywhere
|
|
onError: (error: unknown) => void
|
|
|
|
// Good: Typed errors
|
|
onError: (error: MediaError) => void
|
|
```
|
|
|
|
### Deep Generic Nesting
|
|
|
|
```ts
|
|
// Poor: Inference fails, unreadable
|
|
type Store<T extends Record<K, V>, K extends string, V extends Serializable<V>>
|
|
|
|
// Good: Simpler constraints
|
|
type Store<T extends Record<string, unknown>>
|
|
```
|
|
|
|
### Shotgun Parsing
|
|
|
|
```ts
|
|
// Poor: Validation scattered everywhere
|
|
function processUser(data: unknown) {
|
|
if (!data.name) throw new Error('Missing name');
|
|
// ... 100 lines later ...
|
|
if (!data.email) throw new Error('Missing email');
|
|
}
|
|
|
|
// Good: Parse at boundaries, trust types internally
|
|
const user = userSchema.parse(request.body);
|
|
processUser(user); // Type guarantees fields exist
|
|
```
|
|
|
|
---
|
|
|
|
## State Management Anti-Patterns
|
|
|
|
### Mandatory Providers
|
|
|
|
```tsx
|
|
// Poor: Boilerplate for every usage
|
|
<StoreProvider store={store}>
|
|
<App />
|
|
</StoreProvider>
|
|
|
|
// Good: Works without wrapper
|
|
const useStore = create((set) => ({ ... }));
|
|
// Optional provider for overrides/testing
|
|
```
|
|
|
|
### No Selector Support
|
|
|
|
```ts
|
|
// Poor: Subscribes to everything, excessive re-renders
|
|
const state = useStore();
|
|
|
|
// Good: Fine-grained subscriptions
|
|
const volume = useStore((s) => s.volume);
|
|
```
|
|
|
|
### Sync-Only State
|
|
|
|
```ts
|
|
// Poor: Async requires workarounds
|
|
store.setState({ loading: true });
|
|
const data = await fetch(...);
|
|
store.setState({ loading: false, data });
|
|
|
|
// Good: Built-in async support
|
|
const dataAtom = atom(async () => fetch(...));
|
|
```
|
|
|
|
### Per-Module Middleware
|
|
|
|
```ts
|
|
// Poor: Unexpected interactions
|
|
const volumeFeature = createFeature({
|
|
middleware: [logger], // Don't do this
|
|
});
|
|
|
|
// Good: Middleware at store level
|
|
create(
|
|
logger((...a) => ({
|
|
...volumeFeature(...a),
|
|
...playbackFeature(...a),
|
|
}))
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling Anti-Patterns
|
|
|
|
### Generic Errors
|
|
|
|
```ts
|
|
// Poor: No context, not actionable
|
|
throw new Error('Invalid state');
|
|
|
|
// Good: Typed, contextual, actionable
|
|
throw new MediaError('INVALID_STATE', {
|
|
current: state,
|
|
expected: ['idle', 'ready'],
|
|
hint: 'Call reset() before attempting this operation',
|
|
});
|
|
```
|
|
|
|
### Swallowing Errors
|
|
|
|
```ts
|
|
// Poor: Silent failure
|
|
try {
|
|
await load();
|
|
} catch {
|
|
// nothing
|
|
}
|
|
|
|
// Good: Explicit handling
|
|
try {
|
|
await load();
|
|
} catch (error) {
|
|
onError?.(error);
|
|
setState({ error });
|
|
}
|
|
```
|
|
|
|
### Inconsistent Async Errors
|
|
|
|
```ts
|
|
// Poor: Different patterns
|
|
methodA().catch(handler); // Promise rejection
|
|
methodB((err) => { ... }); // Callback
|
|
methodC(); // throws? returns error? who knows
|
|
|
|
// Good: Consistent pattern
|
|
const result = await methodA(); // Returns Result<T, E>
|
|
if (!result.ok) handle(result.error);
|
|
```
|
|
|
|
---
|
|
|
|
## Packaging Anti-Patterns
|
|
|
|
### Deep Subpaths
|
|
|
|
```ts
|
|
// Poor: Unwieldy imports
|
|
|
|
// Good: Shallow subpaths
|
|
import { useStore } from '@lib/react';
|
|
import { useStore } from '@lib/react/hooks/store/useStore';
|
|
```
|
|
|
|
### Barrel Export Everything
|
|
|
|
```ts
|
|
// Poor: Tree-shaking issues, TS perf hit
|
|
export * from './components';
|
|
export * from './hooks';
|
|
export * from './utils';
|
|
// ... 50 more
|
|
|
|
// Good: Explicit exports
|
|
export { Button } from './Button';
|
|
export { useStore } from './useStore';
|
|
```
|
|
|
|
### Bundled Framework Dependencies
|
|
|
|
```json
|
|
// Poor: Bundles React, version conflicts
|
|
"dependencies": {
|
|
"react": "^18.0.0"
|
|
}
|
|
|
|
// Good: Peer dependency
|
|
"peerDependencies": {
|
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Documentation Anti-Patterns
|
|
|
|
### Examples Without Types
|
|
|
|
```ts
|
|
// Poor: JavaScript only
|
|
const store = createStore({
|
|
count: 0,
|
|
increment() { ... }
|
|
});
|
|
|
|
// Good: TypeScript with inference visible
|
|
const store = createStore({
|
|
count: 0,
|
|
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
});
|
|
// type State = { count: number; increment: () => void }
|
|
```
|
|
|
|
### Missing Error Scenarios
|
|
|
|
```ts
|
|
// Poor: Only happy path
|
|
const data = await query.fetch();
|
|
|
|
// Good: Shows error handling
|
|
const data = await query.fetch();
|
|
if (query.error) {
|
|
if (query.error instanceof NetworkError) { ... }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
| Anti-Pattern | Why It Fails |
|
|
| --------------------------- | --------------------------------------- |
|
|
| Function overloads | Poor errors, autocomplete confusion |
|
|
| Runtime plugin registration | Loses type safety, implicit ordering |
|
|
| Positional parameters (3+) | Order confusion, breaking changes |
|
|
| Implicit contracts | Silent breakage when requirements unmet |
|
|
| Per-module middleware | Unexpected interactions |
|
|
| Shotgun parsing | Validation scattered, not at boundaries |
|
|
| Boolean traps | `fn(true, false)` — what do these mean? |
|
|
| Multiple competing APIs | Confusing — which method to use? |
|
|
| Deep generic nesting | Inference fails, unreadable errors |
|
|
| Barrel exports | Tree-shaking issues, TS performance |
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [Principles](principles.md) — what to do instead
|
|
- [TypeScript](typescript.md) — type inference patterns
|
|
- [Component Anti-Patterns](../../component/references/anti-patterns.md) — UI component mistakes
|