23 KiB
ORIGIN_ERROR_PREVENTION_GUIDE.md
📋 RÉSUMÉ EXÉCUTIF
Ce document définit le système complet de prévention d'erreurs pour le projet Veza. Il s'intègre parfaitement à la méthodologie ORIGIN_ existante et doit être appliqué AVANT de commencer toute nouvelle tâche d'implémentation. Ce guide garantit qu'aucune erreur récurrente ne sera introduite dans le codebase.
Dernière mise à jour : 2026-03-04
Statut : ✅ Document de référence officiel
Version : 2.0.0
🎯 OBJECTIFS
Objectif Principal
Établir un système de prévention d'erreurs qui garantit qu'aucune erreur récurrente ne sera introduite dans les futures implémentations.
Objectifs Secondaires
- Réduire le temps de correction d'erreurs (< 5% du temps de développement)
- Maintenir la qualité du code (0 erreurs P0/P1)
- Faciliter l'onboarding (checklists claires)
- Standardiser les patterns de code (templates validés)
🔒 RÈGLES IMMUABLES
- Pre-Flight Check OBLIGATOIRE avant toute nouvelle tâche
- Templates OBLIGATOIRES pour créer de nouveaux fichiers
- Quality Gates BLOQUANTS en CI/CD
- Aucune exception sans approbation Lead Engineer
- Documentation OBLIGATOIRE de tout nouveau pattern d'erreur
📖 TABLE DES MATIÈRES
- Pre-Flight Checklists
- Implementation Patterns
- Validation Gates
- Templates de Code
- Workflow Intégré
- Références
1. PRE-FLIGHT CHECKLISTS
1.1 Checklist Globale (Avant TOUTE Tâche)
OBLIGATOIRE : Exécuter cette checklist avant de commencer une nouvelle tâche.
# Exécuter le script de pre-flight check
./scripts/pre-flight-check.sh
Checklist manuelle :
- Aucune erreur P0/P1 existante (vérifier avec
./scripts/discover-errors.sh) - Tests existants passent (
go test ./.../npm test) - Linter ne produit aucune erreur (
golangci-lint run/npm run lint) - Code est à jour avec
main(git pull origin main) - Branche créée pour la tâche (
git checkout -b feature/TXXXX-description)
1.2 Checklist Backend Go
Avant de créer/modifier du code Go :
-
Vérifier qu'aucun import cycle ne sera créé
# Visualiser le graphe de dépendances cd veza-backend-api go mod graph | grep -i "cycle" -
Valider la cohérence des types (string vs *string)
- Consulter
ORIGIN_ERROR_PATTERNS.mdPAT-002 - Décider une stratégie cohérente avant de modifier un modèle
- Consulter
-
Vérifier que tous les packages importés existent
go build ./... # Doit réussir sans erreur -
Tests unitaires du composant parent passent
go test ./internal/services/... -v -
go vetne produit aucun warninggo vet ./... -
golangci-lintne produit aucune erreurgolangci-lint run -
Vérifier la configuration JWT (issuer/audience) au démarrage
// Dans main.go ou init — valider dès le boot if config.JWTIssuer == "" || config.JWTAudience == "" { log.Fatal("JWT_ISSUER and JWT_AUDIENCE must be set") } -
Propager le contexte de la requête (
c.Request.Context()) — jamaiscontext.Background()dans un handler# Linter rule (golangci-lint contextcheck) golangci-lint run --enable contextcheck -
Goroutines avec mécanisme d'arrêt (
context.Context+sync.WaitGroup)- Vérifier avec
goleakdans les tests :go.uber.org/goleak
- Vérifier avec
-
Pagination bornée — vérifier que
MaxPageSizeest appliquégrep -rn "parsePagination\|MaxPageSize" internal/ -
Réponses d'erreur standardisées — aucun
gin.H{"error"dans les handlersgrep -rn 'gin.H{"error"' internal/handlers/ # Doit retourner 0 résultat
Patterns à éviter :
- ❌ Services qui importent handlers
- ❌ Handlers qui importent services directement
- ❌ Types partagés dans les packages qui les utilisent
- ❌ Mélange de
stringet*stringpour champs optionnels - ❌
context.Background()dans les handlers HTTP - ❌ Goroutines sans mécanisme d'arrêt (
go func()nu) - ❌ Pagination sans
MaxPageSize - ❌
gin.H{"error": ...}au lieu deRespondWithAppError - ❌ JWT parsing sans validation issuer/audience
Patterns sûrs :
- ✅ Interfaces dans
internal/types/ouinternal/interfaces/ - ✅ Services dépendent uniquement d'interfaces
- ✅ Handlers dépendent uniquement d'interfaces
- ✅ Types cohérents (toujours
stringOU toujours*string) - ✅
c.Request.Context()propagé dans toute la chaîne handler → service → repo - ✅ Goroutines avec
ctx.Done()+sync.WaitGroup - ✅
parsePagination()centralisé avecMaxPageSize = 100 - ✅
RespondWithAppError()pour toutes les erreurs - ✅ JWT issuer/audience validés au parsing et vérifiés au boot
1.3 Checklist Frontend React/TypeScript
Avant de créer/modifier du code TypeScript/React :
- Vérifier que
tsconfig.jsonest correctcd apps/web npx tsc --noEmit --strict - Linter ne produit aucune erreur sur fichiers modifiés
npm run lint - Tests existants passent avant modification
npm test -- --run - Types TypeScript sont stricts (pas de
any)- Vérifier avec
tsc --noEmit --strict - Utiliser
unknownsi le type est vraiment inconnu
- Vérifier avec
- JSX syntax validée (Prettier)
npm run format - Pas de
console.logen production- Utiliser un logger configuré
- ESLint devrait bloquer automatiquement
Patterns à éviter :
- ❌ Regex non terminées dans les tests
- ❌ Tags JSX non fermés
- ❌ Types
anyexplicites - ❌ Variables non utilisées
- ❌
console.logen production
Patterns sûrs :
- ✅ Types explicites pour toutes les fonctions
- ✅ Self-closing tags JSX (
<Component />) - ✅ Mocks configurés pour tous les tests
- ✅ Logger au lieu de
console.log
1.4 Checklist Services Rust
Avant de créer/modifier du code Rust :
cargo checkpassecd veza-chat-server # ou veza-stream-server cargo checkcargo clippyne produit aucun warningcargo clippy -- -D warnings- SQLx queries validées avec schema
cargo sqlx prepare --check - Tests unitaires passent
cargo test
Patterns à éviter :
- ❌
unwrap()en production (utiliser?ou gestion d'erreur) - ❌ Types
i32pour IDs (utiliserUuid) - ❌ Queries SQL non validées
Patterns sûrs :
- ✅ Gestion d'erreur avec
Result<T, E> - ✅ Types
Uuidpour tous les IDs - ✅ SQLx queries validées avec
sqlx::query!
2. IMPLEMENTATION PATTERNS
2.1 Backend Service Pattern
Pattern sûr pour créer un nouveau service :
// ✅ PATTERN SÛR - Évite import cycles
// internal/services/user_service.go
package services
import (
"context"
"veza-backend-api/internal/types" // Interfaces dans package neutre
"veza-backend-api/internal/models"
)
// UserService implémente l'interface définie dans types
type UserService struct {
repo types.UserRepository // Dépend de l'interface, pas de l'implémentation
logger types.Logger
}
// NewUserService crée une nouvelle instance
func NewUserService(repo types.UserRepository, logger types.Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}
// CreateUser crée un nouvel utilisateur
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*models.User, error) {
// Validation
if err := s.validateCreateRequest(req); err != nil {
return nil, err
}
// Business logic
user := &models.User{
Email: req.Email,
Username: req.Username,
}
// Persistence
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
}
// validateCreateRequest valide la requête
func (s *UserService) validateCreateRequest(req *CreateUserRequest) error {
if req.Email == "" {
return types.ErrValidation("email is required")
}
// ... autres validations
return nil
}
Interfaces dans package séparé :
// internal/types/interfaces.go
package types
import (
"context"
"veza-backend-api/internal/models"
)
// UserRepository définit les opérations de persistence
type UserRepository interface {
Create(ctx context.Context, user *models.User) error
FindByID(ctx context.Context, id uuid.UUID) (*models.User, error)
FindByEmail(ctx context.Context, email string) (*models.User, error)
}
// Logger définit les opérations de logging
type Logger interface {
Info(msg string, fields ...interface{})
Error(msg string, fields ...interface{})
Debug(msg string, fields ...interface{})
}
2.2 Backend Handler Pattern
Pattern sûr pour créer un nouveau handler :
// ✅ PATTERN SÛR - Évite import cycles
// internal/handlers/user_handlers.go
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"veza-backend-api/internal/types" // Interfaces seulement
"veza-backend-api/internal/models"
)
// UserHandlers gère les requêtes HTTP pour les utilisateurs
type UserHandlers struct {
userService types.UserService // Interface, pas l'implémentation
}
// NewUserHandlers crée une nouvelle instance
func NewUserHandlers(userService types.UserService) *UserHandlers {
return &UserHandlers{
userService: userService,
}
}
// CreateUser gère POST /api/v1/users
func (h *UserHandlers) CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
user, err := h.userService.CreateUser(c.Request.Context(), &req)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusCreated, user)
}
// handleError gère les erreurs de manière cohérente
func handleError(c *gin.Context, err error) {
// Logique de gestion d'erreur centralisée
// ...
}
2.3 Frontend Component Pattern
Pattern sûr pour créer un nouveau composant React :
// ✅ PATTERN SÛR - Évite import hell
// src/components/user/UserProfile.tsx
import type { User } from '@/types'; // Types séparés
import { useUserStore } from '@/stores/user'; // State management
import { userService } from '@/services/user'; // API calls
import { useUser } from '@/hooks/useUser'; // Custom hook
interface UserProfileProps {
userId: string;
className?: string;
}
// Component NE fait PAS de logic business
export const UserProfile: React.FC<UserProfileProps> = ({
userId,
className
}) => {
// Custom hook gère la logique
const { data: user, isLoading, error } = useUser(userId);
// États de chargement et d'erreur
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <div>User not found</div>;
// Rendu simple
return (
<div className={className}>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
};
Custom Hook Pattern :
// ✅ PATTERN SÛR - Logique réutilisable
// src/hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
import { userService } from '@/services/user';
export function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => userService.getUser(userId),
enabled: !!userId,
});
}
2.4 Frontend Service Pattern
Pattern sûr pour créer un nouveau service API :
// ✅ PATTERN SÛR - Types explicites
// src/services/user.ts
import type { User, CreateUserRequest } from '@/types';
import { apiClient } from './apiClient';
export const userService = {
async getUser(userId: string): Promise<User> {
const response = await apiClient.get<User>(`/api/v1/users/${userId}`);
return response.data;
},
async createUser(request: CreateUserRequest): Promise<User> {
const response = await apiClient.post<User>('/api/v1/users', request);
return response.data;
},
async updateUser(userId: string, request: Partial<User>): Promise<User> {
const response = await apiClient.put<User>(`/api/v1/users/${userId}`, request);
return response.data;
},
};
2.5 Test Pattern (Frontend)
Pattern sûr pour écrire des tests :
// ✅ PATTERN SÛR - Mocks configurés
// src/components/user/UserProfile.test.tsx
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { useUser } from '@/hooks/useUser';
// Mock le hook
vi.mock('@/hooks/useUser');
describe('UserProfile', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should render user profile', () => {
// Arrange
const mockUser = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
};
vi.mocked(useUser).mockReturnValue({
data: mockUser,
isLoading: false,
error: null,
} as any);
// Act
render(<UserProfile userId="123" />);
// Assert
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
it('should show loading state', () => {
// Arrange
vi.mocked(useUser).mockReturnValue({
data: null,
isLoading: true,
error: null,
} as any);
// Act
render(<UserProfile userId="123" />);
// Assert
expect(screen.getByRole('status')).toBeInTheDocument(); // Spinner
});
});
2.6 Audit Prevention Patterns (2026-03-04)
Les patterns suivants ont été identifiés lors de l'audit du 2026-03-04. Chaque pattern a un mécanisme de prévention automatisé.
JWT Configuration Validation at Startup
// ✅ PATTERN SÛR - Valider la configuration JWT au démarrage
// internal/auth/config.go
func ValidateJWTConfig(cfg *config.Config) error {
if cfg.JWTSecret == "" {
return fmt.Errorf("JWT_SECRET is required")
}
if cfg.JWTIssuer == "" {
return fmt.Errorf("JWT_ISSUER is required")
}
if cfg.JWTAudience == "" {
return fmt.Errorf("JWT_AUDIENCE is required")
}
if cfg.JWTExpiration <= 0 {
return fmt.Errorf("JWT_EXPIRATION must be positive")
}
return nil
}
// main.go — appeler au boot, fail-fast si invalide
func main() {
cfg := config.Load()
if err := auth.ValidateJWTConfig(cfg); err != nil {
log.Fatalf("JWT configuration error: %v", err)
}
}
Context Propagation Enforcement
# ✅ PATTERN SÛR - golangci-lint config (.golangci.yml)
linters:
enable:
- contextcheck
linters-settings:
contextcheck:
# Détecte context.Background() et context.TODO() dans les fonctions
# qui reçoivent déjà un context en paramètre
// ✅ PATTERN SÛR - Toujours propager le contexte
func (h *Handler) GetResource(c *gin.Context) {
ctx := c.Request.Context()
result, err := h.service.Get(ctx, id) // ctx propagé
}
func (s *Service) Get(ctx context.Context, id string) (*Resource, error) {
return s.repo.FindByID(ctx, id) // ctx propagé
}
Goroutine Lifecycle Management
// ✅ PATTERN SÛR - Goroutine avec lifecycle complet
type BackgroundWorker struct {
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
func NewBackgroundWorker(parentCtx context.Context) *BackgroundWorker {
ctx, cancel := context.WithCancel(parentCtx)
return &BackgroundWorker{ctx: ctx, cancel: cancel}
}
func (w *BackgroundWorker) Start(task func(ctx context.Context)) {
w.wg.Add(1)
go func() {
defer w.wg.Done()
task(w.ctx)
}()
}
func (w *BackgroundWorker) Stop() {
w.cancel()
w.wg.Wait()
}
Pagination Limit Enforcement
// ✅ PATTERN SÛR - Pagination centralisée avec limites
// internal/api/pagination.go
const (
DefaultPageSize = 20
MaxPageSize = 100
)
type PaginationParams struct {
Limit int
Offset int
}
func ParsePagination(c *gin.Context) PaginationParams {
limit, _ := strconv.Atoi(c.DefaultQuery("limit", strconv.Itoa(DefaultPageSize)))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
if limit <= 0 {
limit = DefaultPageSize
}
if limit > MaxPageSize {
limit = MaxPageSize
}
if offset < 0 {
offset = 0
}
return PaginationParams{Limit: limit, Offset: offset}
}
Error Response Standardization
// ✅ PATTERN SÛR - Réponse d'erreur standardisée unique
// internal/api/errors.go
// RespondWithAppError gère TOUTES les réponses d'erreur
func RespondWithAppError(c *gin.Context, err error) {
var appErr *apperrors.AppError
if errors.As(err, &appErr) {
c.JSON(appErr.HTTPStatus(), appErr.ToResponse())
return
}
c.JSON(http.StatusInternalServerError, apperrors.InternalError().ToResponse())
}
// INTERDIT : c.JSON(400, gin.H{"error": "..."})
// OBLIGATOIRE : RespondWithAppError(c, apperrors.NewValidationError("...", err))
3. VALIDATION GATES
3.1 Pre-Commit Gates (Husky)
Configuration automatique : Les hooks Husky sont configurés dans .husky/pre-commit.
Gates activés :
- ✅ Formatage automatique (Prettier, gofmt)
- ✅ Linter (ESLint, golangci-lint)
- ✅ Tests unitaires rapides (
go test -short,npm test -- --run) - ✅ Type checking (TypeScript)
Si un gate échoue :
- ❌ Le commit est bloqué
- ✅ Corriger les erreurs
- ✅ Réessayer le commit
3.2 Pre-Merge Gates (GitHub Actions)
Configuration : .github/workflows/error-prevention.yml
Gates activés :
-
Architecture Validation
- Vérification des import cycles (Go)
- Vérification de la structure des packages
-
Type Safety
- TypeScript strict mode
- Go type checking
-
Test Coverage
- Coverage ≥ 80% pour nouveau code
- Tous les tests passent
-
Linter
- Zero linter errors
- Zero linter warnings (ou < 5)
-
Build
- Backend compile sans erreur
- Frontend build réussit
Si un gate échoue :
- ❌ La PR ne peut pas être mergée
- ✅ Corriger les erreurs
- ✅ Push les corrections
- ✅ Les gates se relancent automatiquement
3.3 Pre-Deployment Gates
Gates activés :
- ✅ Tous les tests passent (unit, integration, E2E)
- ✅ Coverage ≥ 80% (global)
- ✅ Performance tests passent
- ✅ Security scan pass
- ✅ Smoke tests passent en staging
4. TEMPLATES DE CODE
4.1 Utilisation des Templates
Avant de créer un nouveau fichier :
- Consulter la liste des templates disponibles dans
/dev-environment/templates/ - Copier le template approprié
- Remplacer les placeholders (
{{PLACEHOLDER}}) - Adapter selon les besoins spécifiques
Exemple :
# Créer un nouveau service Go
cp dev-environment/templates/backend-service.template.go \
veza-backend-api/internal/services/my_service.go
# Éditer et remplacer les placeholders
# {{SERVICE_NAME}} → MyService
# {{PACKAGE_NAME}} → myservice
4.2 Templates Disponibles
Backend Go :
backend-service.template.go- Service avec interfacebackend-handler.template.go- Handler HTTPbackend-repository.template.go- Repository pattern
Frontend React/TypeScript :
frontend-component.template.tsx- Composant Reactfrontend-hook.template.ts- Custom hookfrontend-service.template.ts- Service API
Rust :
rust-service.template.rs- Service Rust
Voir : /dev-environment/templates/ pour les templates complets.
5. WORKFLOW INTÉGRÉ
5.1 Workflow Complet
graph TD
A[Nouvelle Tâche TXXXX] --> B[Pre-Flight Check]
B -->|FAIL| C[Corriger Erreurs Existantes]
C --> B
B -->|PASS| D[Choisir Template]
D --> E[Implémenter avec Pattern Sûr]
E --> F[Tests Unitaires TDD]
F --> G{Coverage ≥ 80%?}
G -->|Non| F
G -->|Oui| H[Lint Check]
H --> I{Zero Errors?}
I -->|Non| E
I -->|Oui| J[Pre-Commit Hook]
J --> K{Hook Pass?}
K -->|Non| E
K -->|Oui| L[Commit]
L --> M[Push & Create PR]
M --> N[CI/CD Gates]
N --> O{All Gates Pass?}
O -->|Non| E
O -->|Oui| P[Code Review]
P --> Q{Approved?}
Q -->|Non| E
Q -->|Oui| R[Merge]
5.2 Checklist par Étape
Étape 1: Pre-Flight Check
- Exécuter
./scripts/pre-flight-check.sh - Vérifier qu'aucune erreur P0/P1 existe
- Tests existants passent
- Linter clean
Étape 2: Implémentation
- Utiliser template approprié
- Suivre pattern sûr (voir section 2)
- Éviter les anti-patterns (voir
ORIGIN_ERROR_PATTERNS.md) - Tests en TDD (Red-Green-Refactor)
Étape 3: Validation Locale
- Tests unitaires passent
- Coverage ≥ 80%
- Linter zero errors
- Type check passe
- Build réussit
Étape 4: Commit
- Pre-commit hook passe
- Message de commit suit format:
TXXXX: type: description - Commit atomique (une fonctionnalité par commit)
Étape 5: PR & Review
- CI/CD gates passent
- Code review approuvé (2 reviewers)
- Documentation mise à jour si nécessaire
6. RÉFÉRENCES
Documents ORIGIN
- ORIGIN_ERROR_PATTERNS.md - Catalogue des patterns d'erreurs
- ORIGIN_CODE_STANDARDS.md - Standards de code
- ORIGIN_MASTER_ARCHITECTURE.md - Architecture du projet
- ORIGIN_TESTING_STRATEGY.md - Stratégie de tests
- ORIGIN_IMPLEMENTATION_TASKS.md - Tâches d'implémentation
Scripts Utilitaires
./scripts/pre-flight-check.sh- Validation pré-tâche./scripts/discover-errors.sh- Découverte d'erreurs./scripts/generate-error-summary.sh- Rapport d'erreurs
Outils
- Go :
go vet,golangci-lint,go test - TypeScript :
tsc,eslint,prettier - Rust :
cargo check,cargo clippy,cargo test
✅ CHECKLIST DE VALIDATION
Avant de Commencer une Tâche
- Pre-flight check exécuté et passé
- Template choisi et copié
- Pattern sûr identifié
- Checklist spécifique (Backend/Frontend/Rust) complétée
Pendant l'Implémentation
- Pattern sûr suivi
- Anti-patterns évités
- Tests écrits en TDD
- Linter activé en temps réel
Avant le Commit
- Tests passent
- Coverage ≥ 80%
- Linter zero errors
- Type check passe
- Build réussit
Avant le Merge
- CI/CD gates passent
- Code review approuvé
- Documentation mise à jour
🔄 MAINTENANCE
Mise à Jour du Guide
- Fréquence : Mensuelle ou après découverte d'un nouveau pattern
- Responsable : Lead Engineers
- Processus :
- Identifier nouveau pattern d'erreur
- Documenter dans
ORIGIN_ERROR_PATTERNS.md - Mettre à jour ce guide si nécessaire
- Communiquer à l'équipe
Amélioration Continue
- Analyser les erreurs qui passent malgré les gates
- Améliorer les templates si nécessaire
- Ajuster les checklists selon les retours
Dernière mise à jour : 2026-03-04
Version : 2.0.0
Statut : ✅ APPROUVÉ ET VERROUILLÉ
"Prevention is better than cure."