# Analyse de duplication, mutualisation et optimisation du code **Date** : 2024-12-19 **Auteur** : Équipe 4NK ## Résumé exécutif Cette analyse identifie les duplications de code, les opportunités de mutualisation/centralisation et les axes d'organisation et d'optimisation dans le codebase. L'objectif est de réduire la duplication, améliorer la maintenabilité et optimiser l'architecture sans dégrader les performances. ## 1. Duplications identifiées ### 1.1 Initialisation IndexedDB (Critique - Forte duplication) **Localisation** : Multiple fichiers avec pattern identique **Fichiers concernés** : - `lib/notificationService.ts` (lignes 39-89) - `lib/publishLog.ts` (lignes 24-72) - `lib/storage/indexedDB.ts` (lignes 25-72) - `lib/objectCache.ts` (lignes 33-86) - `lib/localeStorage.ts` (lignes 14-43) - `lib/settingsCache.ts` (lignes 24-54) - `public/writeWorker.js` (lignes 404-479) **Pattern dupliqué** : ```typescript private async init(): Promise { if (this.db) return if (this.initPromise) return this.initPromise this.initPromise = this.openDatabase() try { await this.initPromise } catch (error) { this.initPromise = null throw error } } private openDatabase(): Promise { return new Promise((resolve, reject) => { if (typeof window === 'undefined' || !window.indexedDB) { reject(new Error('IndexedDB is not available')) return } const request = window.indexedDB.open(DB_NAME, DB_VERSION) request.onerror = () => reject(...) request.onsuccess = () => { this.db = request.result; resolve() } request.onupgradeneeded = (event) => { /* schema creation */ } }) } ``` **Impact** : - ~200 lignes de code dupliquées - Maintenance difficile (changements à appliquer en 7+ endroits) - Risque d'incohérences entre implémentations **Solution proposée** : Créer un utilitaire générique `lib/indexedDBHelper.ts` avec factory pattern --- ### 1.2 Pattern de subscription avec relay rotation (Critique - Forte duplication) **Localisation** : `lib/userContentSync.ts` **Fonctions concernées** : - `fetchAndCachePublications` (lignes 22-146) - `fetchAndCacheSeries` (lignes 151-281) - `fetchAndCachePurchases` (lignes 286-383) - `fetchAndCacheSponsoring` (lignes 388-485) - `fetchAndCacheReviewTips` (lignes 490-587) - `fetchAndCachePaymentNotes` (lignes 599-721) **Pattern dupliqué** (~50 lignes par fonction) : ```typescript // 1. Récupération lastSyncDate const { getLastSyncDate } = await import('./syncStorage') const lastSyncDate = await getLastSyncDate() // 2. Construction des filters const filters = [{ ...buildTagFilter(...), since: lastSyncDate, limit: 1000 }] // 3. Tentative avec relay rotation const { createSubscription } = require('@/types/nostr-tools-extended') let sub = null let usedRelayUrl = '' try { const result = await tryWithRelayRotation( pool as unknown as import('nostr-tools').SimplePool, async (relayUrl, poolWithSub) => { usedRelayUrl = relayUrl // Notification syncProgressManager const { syncProgressManager } = await import('./syncProgressManager') const currentProgress = syncProgressManager.getProgress() if (currentProgress) { syncProgressManager.setProgress({ ...currentProgress, currentStep: 0, currentRelay: relayUrl, }) } return createSubscription(poolWithSub, [relayUrl], filters) }, 5000 ) sub = result } catch { // Fallback to primary relay usedRelayUrl = getPrimaryRelaySync() sub = createSubscription(pool, [usedRelayUrl], filters) } // 4. Gestion des événements avec Promise + timeout const events: Event[] = [] return new Promise((resolve) => { let finished = false const done = async () => { /* ... */ } sub.on('event', (event) => { events.push(event) }) sub.on('eose', () => void done()) setTimeout(() => void done(), 10000).unref?.() }) ``` **Impact** : - ~300 lignes de code dupliquées - Logique de gestion d'événements répétée 6 fois - Risque d'incohérences dans la gestion des erreurs et timeouts **Solution proposée** : Créer une fonction générique `createSyncSubscription` dans `lib/syncSubscriptionHelper.ts` --- ### 1.3 Pattern de traitement d'événements avec groupement par hash (Moyenne duplication) **Localisation** : `lib/userContentSync.ts` **Fonctions concernées** : - `fetchAndCachePublications` (lignes 88-126) - `fetchAndCacheSeries` (lignes 218-256) **Pattern dupliqué** : ```typescript // Group events by hash ID and cache the latest version of each const eventsByHashId = new Map() for (const event of events) { const tags = extractTagsFromEvent(event) if (tags.id) { const parsed = parseObjectId(tags.id) const hash = parsed.hash ?? tags.id if (!eventsByHashId.has(hash)) { eventsByHashId.set(hash, []) } eventsByHashId.get(hash)!.push(event) } } // Cache each publication/series for (const [_hash, hashEvents] of eventsByHashId.entries()) { const latestEvent = getLatestVersion(hashEvents) if (latestEvent) { const extracted = await extractPublicationFromEvent(latestEvent) // ou extractSeriesFromEvent if (extracted) { const publicationParsed = parseObjectId(extracted.id) const extractedHash = publicationParsed.hash ?? extracted.id const extractedIndex = publicationParsed.index ?? 0 const tags = extractTagsFromEvent(latestEvent) const { writeService } = await import('./writeService') await writeService.writeObject( 'publication', // ou 'series' extractedHash, latestEvent, extracted, tags.version ?? 0, tags.hidden ?? false, extractedIndex, false ) } } } ``` **Impact** : - ~40 lignes dupliquées - Logique de groupement et cache répétée **Solution proposée** : Créer une fonction générique `groupAndCacheEventsByHash` dans `lib/eventCacheHelper.ts` --- ### 1.4 Pattern de queries avec objectCache (Faible duplication mais répétitif) **Localisation** : Fichiers `*Queries.ts` **Fichiers concernés** : - `lib/purchaseQueries.ts` - `lib/seriesQueries.ts` - `lib/articleQueries.ts` - (et probablement d'autres fichiers queries) **Pattern répétitif** : ```typescript export async function getXxxById(id: string, _timeoutMs: number = 5000): Promise { const parsed = parseObjectId(id) const hash = parsed.hash ?? id // Read only from IndexedDB cache const cached = await objectCache.get('xxx', hash) if (cached) { return cached as Xxx } // Also try by ID if hash lookup failed const cachedById = await objectCache.getById('xxx', id) if (cachedById) { return cachedById as Xxx } // Not found in cache - return null (no network request) return null } ``` **Impact** : - Pattern répété dans plusieurs fichiers queries - Logique de fallback identique **Solution proposée** : Créer une fonction helper `getCachedObjectById` dans `lib/queryHelpers.ts` --- ### 1.5 Pattern de writeObject avec extraction (Moyenne duplication) **Localisation** : Multiple fichiers **Fichiers concernés** : - `lib/userContentSync.ts` (lignes 107-123, 237-253, 351-359, 453-461, 555-563) - `lib/platformSync.ts` (lignes 304-305, 310-311, 316-317) **Pattern répétitif** : ```typescript const extracted = await extractXxxFromEvent(event) if (extracted) { const { writeService } = await import('./writeService') await writeService.writeObject( 'xxx', extracted.hash, event, extracted, tags.version ?? 0, tags.hidden ?? false, extracted.index ?? 0, false ) } ``` **Impact** : - Appels répétés avec mêmes paramètres par défaut - Logique d'extraction + écriture répétée **Solution proposée** : Créer une fonction helper `cacheEventAsObject` dans `lib/eventCacheHelper.ts` --- ### 1.6 Pattern de gestion de transactions IndexedDB (Moyenne duplication) **Localisation** : Multiple fichiers **Pattern répétitif** : ```typescript const transaction = db.transaction([STORE_NAME], 'readonly' | 'readwrite') const store = transaction.objectStore(STORE_NAME) const index = store.index('xxx') return new Promise((resolve, reject) => { const request = index.get(key) // ou openCursor, getAll, etc. request.onsuccess = () => resolve(request.result) request.onerror = () => reject(request.error) }) ``` **Impact** : - Wrapper Promise répété dans de nombreux endroits - Gestion d'erreurs similaire partout **Solution proposée** : Créer des helpers dans `lib/indexedDBHelper.ts` : `getFromStore`, `getAllFromStore`, `putToStore`, `deleteFromStore`, `openCursor` --- ### 1.7 Pattern de gestion de progress dans SyncProgressBar (Duplication interne) **Localisation** : `components/SyncProgressBar.tsx` **Pattern dupliqué** (lignes 104-126 et 177-199) : ```typescript const { syncProgressManager } = await import('@/lib/syncProgressManager') const checkProgress = (): void => { const currentProgress = syncProgressManager.getProgress() if (currentProgress) { setSyncProgress(currentProgress) if (currentProgress.completed) { setIsSyncing(false) void loadSyncStatus() } } } // Check progress periodically const progressInterval = setInterval(() => { checkProgress() const currentProgress = syncProgressManager.getProgress() if (currentProgress?.completed) { clearInterval(progressInterval) } }, 500) // Cleanup after 60 seconds max setTimeout(() => { clearInterval(progressInterval) setIsSyncing(false) }, 60000) ``` **Impact** : - Code dupliqué dans deux fonctions (auto-sync et resynchronize) - Logique de polling répétée **Solution proposée** : Extraire dans un hook custom `useSyncProgress` ou une fonction helper --- ## 2. Opportunités de mutualisation/centralisation ### 2.1 Service d'initialisation IndexedDB unifié **Objectif** : Centraliser toute la logique d'initialisation IndexedDB **Structure proposée** : ```typescript // lib/indexedDBHelper.ts export interface IndexedDBConfig { dbName: string version: number storeName: string keyPath: string indexes?: Array<{ name: string; keyPath: string; unique?: boolean }> onUpgrade?: (db: IDBDatabase, event: IDBVersionChangeEvent) => void } export class IndexedDBHelper { private static instances = new Map() private db: IDBDatabase | null = null private initPromise: Promise | null = null static getInstance(config: IndexedDBConfig): IndexedDBHelper { // Singleton par dbName } async init(): Promise { /* ... */ } async getStore(mode: 'readonly' | 'readwrite'): Promise { /* ... */ } // Helpers pour opérations courantes } ``` **Bénéfices** : - Réduction de ~200 lignes de code dupliqué - Maintenance centralisée - Cohérence garantie entre services --- ### 2.2 Helper de subscription avec relay rotation **Objectif** : Centraliser le pattern de subscription avec rotation de relais **Structure proposée** : ```typescript // lib/syncSubscriptionHelper.ts export interface SyncSubscriptionConfig { pool: SimplePoolWithSub filters: Filter[] onEvent: (event: Event) => void | Promise onComplete?: (events: Event[]) => void | Promise timeout?: number updateProgress?: (relayUrl: string) => void } export async function createSyncSubscription( config: SyncSubscriptionConfig ): Promise<{ subscription: Subscription; relayUrl: string; events: Event[] }> { // Centralise toute la logique de rotation, gestion d'événements, timeout } ``` **Bénéfices** : - Réduction de ~300 lignes de code dupliqué - Gestion d'erreurs unifiée - Facilite les tests --- ### 2.3 Helper de groupement et cache d'événements **Objectif** : Centraliser la logique de groupement par hash et cache **Structure proposée** : ```typescript // lib/eventCacheHelper.ts export interface EventCacheConfig { objectType: ObjectType extractor: (event: Event) => Promise getHash: (extracted: ExtractedObject) => string getIndex: (extracted: ExtractedObject) => number } export async function groupAndCacheEventsByHash( events: Event[], config: EventCacheConfig ): Promise { // Groupement par hash, sélection de la dernière version, cache } ``` **Bénéfices** : - Réduction de ~40 lignes de code dupliqué - Logique de versioning centralisée --- ### 2.4 Helper de queries unifié **Objectif** : Simplifier les queries avec fallback hash/ID **Structure proposée** : ```typescript // lib/queryHelpers.ts export async function getCachedObjectById( objectType: ObjectType, id: string ): Promise { // Logique de fallback hash/ID centralisée } ``` **Bénéfices** : - Réduction de code répétitif dans les queries - Cohérence des fallbacks --- ### 2.5 Helper de cache d'événements **Objectif** : Simplifier l'écriture d'objets après extraction **Structure proposée** : ```typescript // lib/eventCacheHelper.ts export async function cacheEventAsObject( event: Event, objectType: ObjectType, extractor: (event: Event) => Promise ): Promise { // Extraction + écriture avec paramètres par défaut } ``` **Bénéfices** : - Réduction de code répétitif - Paramètres par défaut cohérents --- ## 3. Axes d'organisation et d'optimisation ### 3.1 Organisation des helpers **Structure proposée** : ``` lib/ helpers/ indexedDBHelper.ts # Initialisation et opérations IndexedDB syncSubscriptionHelper.ts # Subscriptions avec relay rotation eventCacheHelper.ts # Groupement et cache d'événements queryHelpers.ts # Helpers pour queries transactionHelpers.ts # Wrappers pour transactions IndexedDB ``` **Bénéfices** : - Organisation claire par responsabilité - Facilite la découverte et la réutilisation - Séparation des préoccupations --- ### 3.2 Optimisation des imports dynamiques **Problème identifié** : Imports dynamiques répétés dans les boucles **Exemples** : - `const { writeService } = await import('./writeService')` dans des boucles - `const { syncProgressManager } = await import('./syncProgressManager')` dans des callbacks **Solution** : Importer en début de fonction ou utiliser des imports statiques quand possible **Impact** : Réduction des latences et amélioration des performances --- ### 3.3 Optimisation de la gestion des événements **Problème identifié** : Accumulation d'événements en mémoire avant traitement **Solution** : Traitement en streaming avec backpressure **Structure proposée** : ```typescript export async function createStreamingSyncSubscription( config: SyncSubscriptionConfig & { processor: (event: Event) => Promise batchSize?: number } ): Promise<{ results: T[] }> { // Traitement par batch au lieu d'accumulation complète } ``` **Bénéfices** : - Réduction de l'utilisation mémoire - Traitement plus rapide pour de gros volumes --- ### 3.4 Centralisation de la gestion d'erreurs IndexedDB **Problème identifié** : Gestion d'erreurs dispersée et parfois incohérente **Solution** : Créer un wrapper d'erreur IndexedDB avec logging structuré **Structure proposée** : ```typescript // lib/indexedDBHelper.ts export class IndexedDBError extends Error { constructor( message: string, public readonly operation: string, public readonly storeName?: string, public readonly cause?: unknown ) { super(message) // Logging structuré automatique } } ``` **Bénéfices** : - Traçabilité améliorée - Gestion d'erreurs cohérente --- ### 3.5 Optimisation des transactions IndexedDB **Problème identifié** : Transactions multiples pour des opérations liées **Solution** : Regrouper les opérations dans une seule transaction quand possible **Exemple** : Dans `writeWorker.js`, `handleWriteMultiTable` pourrait optimiser les transactions par type --- ### 3.6 Typage strict pour les helpers **Problème identifié** : Utilisation de `unknown` et `any` dans certains helpers **Solution** : Génériques TypeScript stricts pour tous les helpers **Bénéfices** : - Sécurité de type améliorée - Meilleure autocomplétion - Détection d'erreurs à la compilation --- ### 3.7 Documentation et exemples **Problème identifié** : Manque de documentation sur les patterns à utiliser **Solution** : Créer `docs/patterns/` avec : - `indexedDB-patterns.md` : Patterns d'utilisation IndexedDB - `subscription-patterns.md` : Patterns de subscription - `caching-patterns.md` : Patterns de cache **Bénéfices** : - Onboarding facilité - Cohérence des implémentations futures --- ## 4. Priorisation des actions ### Priorité 1 (Critique - Impact élevé) 1. **Service d'initialisation IndexedDB unifié** (~200 lignes économisées) 2. **Helper de subscription avec relay rotation** (~300 lignes économisées) ### Priorité 2 (Important - Impact moyen) 3. **Helper de groupement et cache d'événements** (~40 lignes économisées) 4. **Helper de cache d'événements** (réduction de code répétitif) 5. **Helper de queries unifié** (simplification des queries) ### Priorité 3 (Amélioration - Impact faible mais bénéfique) 6. **Optimisation des imports dynamiques** 7. **Centralisation de la gestion d'erreurs** 8. **Documentation des patterns** --- ## 5. Risques et précautions ### Risques identifiés 1. **Régression fonctionnelle** : Refactoring de code critique (IndexedDB, subscriptions) - **Mitigation** : Tests unitaires avant refactoring, migration progressive 2. **Performance** : Abstraction peut introduire overhead - **Mitigation** : Benchmarks avant/après, optimisation si nécessaire 3. **Compatibilité** : Changements d'API peuvent casser le code existant - **Mitigation** : Déprecation progressive, migration guides ### Précautions - Valider chaque refactoring avec des tests - Maintenir la rétrocompatibilité quand possible - Documenter les breaking changes - Mesurer l'impact sur les performances --- ## 6. Métriques de succès ### Réduction de code - **Objectif** : Réduction de ~600 lignes de code dupliqué - **Mesure** : Comparaison avant/après avec `cloc` ou similaire ### Maintenabilité - **Objectif** : Réduction du temps de modification de patterns communs - **Mesure** : Temps moyen pour appliquer un changement (avant/après) ### Qualité - **Objectif** : Réduction des bugs liés à l'incohérence - **Mesure** : Nombre de bugs liés à la duplication (avant/après) --- ## Conclusion Cette analyse identifie des opportunités significatives de réduction de duplication et d'amélioration de l'organisation du code. Les priorités 1 et 2 devraient être traitées en premier pour maximiser l'impact sur la maintenabilité et réduire les risques d'incohérences. Les refactorings proposés respectent l'architecture existante et les principes de séparation des responsabilités. Ils doivent être réalisés progressivement avec validation à chaque étape.