veza/veza-docs/ORIGIN/ORIGIN_CODE_STANDARDS.md
2026-03-05 19:22:31 +01:00

49 KiB

ORIGIN_CODE_STANDARDS.md

📋 RÉSUMÉ EXÉCUTIF

Ce document définit les standards de code complets et définitifs pour la plateforme Veza. Il couvre les conventions de code, architectures, patterns, et anti-patterns pour Go (backend), Rust (services temps réel), TypeScript/React (frontend), CSS/Tailwind, Git, et documentation. Ces standards garantissent la maintenabilité, lisibilité, performance, et cohérence du code pendant 24 mois.

🎯 OBJECTIFS

Objectif Principal

Établir des standards de code stricts et immuables qui garantissent la qualité, maintenabilité, et cohérence du codebase pendant 24 mois avec une équipe de 10+ développeurs.

Objectifs Secondaires

  • Réduire la dette technique (< 5% du temps de développement)
  • Faciliter l'onboarding (< 1 semaine pour nouveaux développeurs)
  • Garantir la lisibilité (code self-documenting)
  • Optimiser les performances (hot paths identifiés)
  • Standardiser le style (linters, formatters automatiques)

📖 TABLE DES MATIÈRES

  1. General Principles
  2. Standards éthiques du code
  3. Go Standards (Backend)
  4. Rust Standards (Services)
  5. TypeScript Standards (Frontend)
  6. React Standards
  7. CSS/Tailwind Standards
  8. Git Standards
  9. Documentation Standards
  10. Code Review Process
  11. Refactoring Guidelines
  12. Anti-Patterns Library

🔒 RÈGLES IMMUABLES

  1. Formatters OBLIGATOIRES: gofmt (Go), rustfmt (Rust), Prettier (TS/React)
  2. Linters OBLIGATOIRES: golangci-lint (Go), clippy (Rust), ESLint (TS)
  3. Tests OBLIGATOIRES pour toute nouvelle feature (coverage ≥ 80%)
  4. Code review OBLIGATOIRE (2 approbations minimum)
  5. Naming conventions STRICTES (camelCase/PascalCase/snake_case selon langage)
  6. Documentation OBLIGATOIRE pour fonctions publiques
  7. Error handling COMPLET (pas de panic/unwrap en production)
  8. Magic numbers INTERDITS (utiliser constantes nommées)
  9. Code mort INTERDIT (suppression immédiate)
  10. Complexity limit: Fonctions max 50 lignes, cyclomatic complexity < 10

1. GENERAL PRINCIPLES

1.1 SOLID Principles

Single Responsibility Principle (SRP):

// ❌ Bad: UserService does too much
type UserService struct{}
func (s *UserService) CreateUser() {}
func (s *UserService) SendEmail() {}
func (s *UserService) ProcessPayment() {}

// ✅ Good: Separate services
type UserService struct{}
func (s *UserService) CreateUser() {}

type EmailService struct{}
func (s *EmailService) SendEmail() {}

type PaymentService struct{}
func (s *PaymentService) ProcessPayment() {}

Open/Closed Principle (OCP):

// ✅ Good: Open for extension, closed for modification
type NotificationSender interface {
    Send(message string) error
}

type EmailNotification struct{}
func (e *EmailNotification) Send(message string) error { /* ... */ }

type SMSNotification struct{}
func (s *SMSNotification) Send(message string) error { /* ... */ }

// Add new notification types without modifying existing code
type PushNotification struct{}
func (p *PushNotification) Send(message string) error { /* ... */ }

Liskov Substitution Principle (LSP):

// ✅ Good: Subtypes can replace base types
type Storage interface {
    Save(data []byte) error
    Load() ([]byte, error)
}

type S3Storage struct{}
func (s *S3Storage) Save(data []byte) error { /* ... */ }
func (s *S3Storage) Load() ([]byte, error) { /* ... */ }

type LocalStorage struct{}
func (l *LocalStorage) Save(data []byte) error { /* ... */ }
func (l *LocalStorage) Load() ([]byte, error) { /* ... */ }

// Can swap implementations
var storage Storage = &S3Storage{}  // or &LocalStorage{}

Interface Segregation Principle (ISP):

// ❌ Bad: Fat interface
type Worker interface {
    Work()
    Eat()
    Sleep()
    Code()
}

// ✅ Good: Segregated interfaces
type Workable interface {
    Work()
}

type Eatable interface {
    Eat()
}

type Sleepable interface {
    Sleep()
}

type Codeable interface {
    Code()
}

Dependency Inversion Principle (DIP):

// ❌ Bad: High-level module depends on low-level module
type UserService struct {
    repo *PostgresUserRepository  // Concrete dependency
}

// ✅ Good: Both depend on abstraction
type UserRepository interface {
    Create(user *User) error
    FindByID(id uuid.UUID) (*User, error)
}

type UserService struct {
    repo UserRepository  // Abstract dependency
}

// Implementations
type PostgresUserRepository struct{}
type MongoUserRepository struct{}

1.2 DRY (Don't Repeat Yourself)

Extract Common Logic:

// ❌ Bad: Duplication
func CreateUser(req CreateUserRequest) error {
    if req.Email == "" {
        return errors.New("email required")
    }
    if !isValidEmail(req.Email) {
        return errors.New("invalid email")
    }
    // ... create user
}

func UpdateUser(req UpdateUserRequest) error {
    if req.Email == "" {
        return errors.New("email required")
    }
    if !isValidEmail(req.Email) {
        return errors.New("invalid email")
    }
    // ... update user
}

// ✅ Good: Extract validation
func validateEmail(email string) error {
    if email == "" {
        return errors.New("email required")
    }
    if !isValidEmail(email) {
        return errors.New("invalid email")
    }
    return nil
}

func CreateUser(req CreateUserRequest) error {
    if err := validateEmail(req.Email); err != nil {
        return err
    }
    // ... create user
}

1.3 KISS (Keep It Simple, Stupid)

Favor Simplicity:

// ❌ Bad: Overengineered
func IsEven(n int) bool {
    return n & 1 == 0
}

// ✅ Good: Simple and readable
func IsEven(n int) bool {
    return n % 2 == 0
}

1.4 YAGNI (You Aren't Gonna Need It)

Don't Add Features Until Needed:

// ❌ Bad: Premature generalization
type Cache interface {
    Get(key string) (interface{}, error)
    Set(key string, value interface{}) error
    Delete(key string) error
    GetMulti(keys []string) ([]interface{}, error)  // Not needed yet
    SetMulti(map[string]interface{}) error          // Not needed yet
    Flush() error                                     // Not needed yet
    GetStats() CacheStats                            // Not needed yet
}

// ✅ Good: Start simple, add when needed
type Cache interface {
    Get(key string) (interface{}, error)
    Set(key string, value interface{}) error
    Delete(key string) error
}

2. STANDARDS ÉTHIQUES DU CODE

2.1 Tracking et consentement

Aucun tracking côté client sans consentement explicite de l'utilisateur.

Toute collecte de données comportementales (analytics, événements, métriques d'usage) doit être conditionnée à un opt-in explicite. Le consentement ne doit jamais être présumé.

// ❌ Interdit : tracking silencieux
useEffect(() => {
  analytics.track('page_view', { page: location.pathname });
}, [location]);

// ✅ Obligatoire : vérifier le consentement
useEffect(() => {
  if (userConsent.analytics) {
    analytics.track('page_view', { page: location.pathname });
  }
}, [location, userConsent.analytics]);

2.2 Algorithmes d'exposition de contenu

Tout algorithme influençant la visibilité ou le classement du contenu (feed, recherche, suggestions) doit être documenté et justifiable. La documentation doit inclure :

  • Les critères de classement et leur pondération
  • L'objectif produit visé (ex : diversité, fraîcheur, pertinence)
  • Les biais potentiels identifiés et les mesures de mitigation

Aucun algorithme opaque n'est autorisé. Les créateurs doivent pouvoir comprendre pourquoi leur contenu est ou n'est pas mis en avant.

2.3 Collecte de métriques

Chaque métrique collectée doit avoir une justification produit documentée. La collecte préventive ("au cas où on en aurait besoin") est interdite.

// ❌ Interdit : collecte préventive sans justification
type TrackEvent struct {
    TrackID       uuid.UUID
    UserID        uuid.UUID
    ScrollDepth   float64   // Pourquoi ?
    MouseMovement []Point   // Pourquoi ?
    TimeOnPage    int       // Pourquoi ?
    DeviceGyro    []float64 // Pourquoi ?
}

// ✅ Obligatoire : chaque champ justifié
type TrackPlayEvent struct {
    TrackID      uuid.UUID // Identifier le contenu joué
    ListenTimeMs int64     // Calculer les royalties (seuil 30s)
    Completed    bool      // Taux de complétion pour le créateur
}

2.4 Scoring, ranking et diversité

Toute fonction de scoring ou de ranking doit inclure des tests de non-régression sur la diversité. Ces tests vérifient que les résultats ne concentrent pas la visibilité sur un sous-ensemble disproportionné de créateurs.

func TestSearchRanking_Diversity(t *testing.T) {
    results := searchService.Search(ctx, "electronic music", SearchParams{Limit: 50})

    creatorIDs := make(map[uuid.UUID]bool)
    for _, r := range results {
        creatorIDs[r.CreatorID] = true
    }

    // Les 50 premiers résultats doivent provenir d'au moins 10 créateurs distincts
    assert.GreaterOrEqual(t, len(creatorIDs), 10,
        "Les résultats de recherche doivent refléter une diversité minimale de créateurs")
}

3. GO STANDARDS (BACKEND)

3.1 Project Structure (Clean Architecture)

veza-backend-api/
├── cmd/
│   └── api/
│       └── main.go                 # Entry point
├── internal/
│   ├── api/                        # HTTP handlers (Delivery layer)
│   │   └── handlers/
│   │       ├── user_handlers.go
│   │       ├── track_handlers.go
│   │       └── auth_handlers.go
│   ├── core/                       # Business logic (Use Cases)
│   │   ├── services/
│   │   │   ├── user_service.go
│   │   │   └── track_service.go
│   │   └── domain/                 # Entities
│   │       ├── user.go
│   │       └── track.go
│   ├── repository/                 # Data access (Repository pattern)
│   │   ├── interfaces.go
│   │   ├── user_repository.go
│   │   └── track_repository.go
│   ├── infrastructure/             # External dependencies
│   │   ├── database/
│   │   │   └── postgres.go
│   │   ├── cache/
│   │   │   └── redis.go
│   │   └── storage/
│   │       └── s3.go
│   ├── middleware/                 # HTTP middleware
│   │   ├── auth.go
│   │   ├── cors.go
│   │   └── logging.go
│   └── config/                     # Configuration
│       └── config.go
├── pkg/                            # Public packages (reusable)
│   ├── logger/
│   └── validator/
├── migrations/                     # Database migrations
├── tests/
│   ├── unit/
│   └── integration/
├── go.mod
└── go.sum

3.2 Naming Conventions

Variables: camelCase

var userName string
var userCount int

Constants: PascalCase or SCREAMING_SNAKE_CASE (for exported)

const MaxRetries = 3
const DEFAULT_TIMEOUT = 30 * time.Second

Functions: PascalCase (exported), camelCase (unexported)

// Exported
func CreateUser(req *CreateUserRequest) (*User, error) {}

// Unexported
func validateEmail(email string) error {}

Types: PascalCase

type UserService struct {}
type CreateUserRequest struct {}

Interfaces: -er suffix (if single method)

type Reader interface {
    Read(p []byte) (n int, err error)
}

type UserRepository interface {  // Multi-method, no -er suffix
    Create(user *User) error
    FindByID(id uuid.UUID) (*User, error)
}

3.3 Error Handling

Always Check Errors:

// ❌ Bad: Ignoring error
user, _ := repo.FindByID(id)

// ✅ Good: Check and handle
user, err := repo.FindByID(id)
if err != nil {
    return nil, fmt.Errorf("failed to find user: %w", err)
}

Wrap Errors with Context:

import "fmt"

func CreateUser(req *CreateUserRequest) error {
    if err := validateEmail(req.Email); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }
    
    if err := repo.Create(user); err != nil {
        return fmt.Errorf("failed to create user in database: %w", err)
    }
    
    return nil
}

Custom Error Types (when needed):

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}

// Usage
if req.Email == "" {
    return &ValidationError{Field: "email", Message: "required"}
}

Don't Panic in Production:

// ❌ Bad: Panic for recoverable errors
if err != nil {
    panic(err)
}

// ✅ Good: Return error
if err != nil {
    return fmt.Errorf("operation failed: %w", err)
}

// ⚠️ OK: Panic only for programmer errors (init, config)
func init() {
    if os.Getenv("DATABASE_URL") == "" {
        panic("DATABASE_URL environment variable not set")
    }
}

3.4 Function Design

Keep Functions Small (< 50 lines):

// ❌ Bad: Too long
func CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": "invalid request"})
        return
    }
    
    if req.Email == "" {
        c.JSON(400, gin.H{"error": "email required"})
        return
    }
    
    if !isValidEmail(req.Email) {
        c.JSON(400, gin.H{"error": "invalid email"})
        return
    }
    
    existingUser, _ := repo.FindByEmail(req.Email)
    if existingUser != nil {
        c.JSON(409, gin.H{"error": "email already exists"})
        return
    }
    
    hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 12)
    if err != nil {
        c.JSON(500, gin.H{"error": "internal error"})
        return
    }
    
    user := &User{
        ID:           uuid.New(),
        Email:        req.Email,
        PasswordHash: string(hash),
        CreatedAt:    time.Now(),
    }
    
    if err := repo.Create(user); err != nil {
        c.JSON(500, gin.H{"error": "failed to create user"})
        return
    }
    
    c.JSON(201, user)
}

// ✅ Good: Extract to service layer
func CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, ErrorResponse{Code: 2000, Message: "Invalid request"})
        return
    }
    
    user, err := userService.CreateUser(c.Request.Context(), &req)
    if err != nil {
        handleError(c, err)
        return
    }
    
    c.JSON(201, user)
}

// Business logic in service
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
    if err := s.validateCreateRequest(req); err != nil {
        return nil, err
    }
    
    if err := s.checkEmailExists(req.Email); err != nil {
        return nil, err
    }
    
    user := s.buildUser(req)
    
    if err := s.repo.Create(ctx, user); err != nil {
        return nil, fmt.Errorf("failed to create user: %w", err)
    }
    
    return user, nil
}

Single Return Type (prefer):

// ❌ Bad: Multiple return patterns
func FindUser(id uuid.UUID) (*User, error) {
    user, err := repo.FindByID(id)
    if err == sql.ErrNoRows {
        return nil, nil  // nil user, nil error
    }
    if err != nil {
        return nil, err  // nil user, error
    }
    return user, nil     // user, nil error
}

// ✅ Good: Consistent return pattern
func FindUser(id uuid.UUID) (*User, error) {
    user, err := repo.FindByID(id)
    if err == sql.ErrNoRows {
        return nil, ErrUserNotFound
    }
    if err != nil {
        return nil, fmt.Errorf("failed to find user: %w", err)
    }
    return user, nil
}

3.5 Concurrency

Use Context for Cancellation:

func ProcessTask(ctx context.Context, taskID uuid.UUID) error {
    select {
    case <-ctx.Done():
        return ctx.Err()  // Cancelled or timed out
    case <-time.After(1 * time.Second):
        // Continue processing
    }
    
    // ... do work
    
    return nil
}

// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := ProcessTask(ctx, taskID); err != nil {
    log.Error("Task failed", zap.Error(err))
}

Goroutine Patterns:

// ✅ Good: Worker pool
func ProcessTracks(tracks []*Track) error {
    const numWorkers = 10
    jobs := make(chan *Track, len(tracks))
    results := make(chan error, len(tracks))
    
    // Start workers
    for w := 0; w < numWorkers; w++ {
        go func() {
            for track := range jobs {
                results <- processTrack(track)
            }
        }()
    }
    
    // Send jobs
    for _, track := range tracks {
        jobs <- track
    }
    close(jobs)
    
    // Collect results
    for i := 0; i < len(tracks); i++ {
        if err := <-results; err != nil {
            return err
        }
    }
    
    return nil
}

Avoid Goroutine Leaks:

// ❌ Bad: Goroutine leak (no way to stop)
func StartWorker() {
    go func() {
        for {
            doWork()
            time.Sleep(1 * time.Second)
        }
    }()
}

// ✅ Good: Cancellable goroutine
func StartWorker(ctx context.Context) {
    go func() {
        ticker := time.NewTicker(1 * time.Second)
        defer ticker.Stop()
        
        for {
            select {
            case <-ctx.Done():
                return  // Exit goroutine
            case <-ticker.C:
                doWork()
            }
        }
    }()
}

3.6 Testing

Table-Driven Tests:

func TestIsValidEmail(t *testing.T) {
    tests := []struct {
        name  string
        email string
        want  bool
    }{
        {"valid email", "user@example.com", true},
        {"missing @", "userexample.com", false},
        {"missing domain", "user@", false},
        {"empty", "", false},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := IsValidEmail(tt.email)
            if got != tt.want {
                t.Errorf("IsValidEmail(%q) = %v, want %v", tt.email, got, tt.want)
            }
        })
    }
}

Use testify/assert:

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestCreateUser(t *testing.T) {
    user, err := CreateUser(&CreateUserRequest{
        Email: "test@example.com",
    })
    
    assert.NoError(t, err)
    assert.NotNil(t, user)
    assert.Equal(t, "test@example.com", user.Email)
}

Mock Dependencies:

// Interface
type UserRepository interface {
    Create(user *User) error
}

// Mock implementation (using testify/mock)
type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) Create(user *User) error {
    args := m.Called(user)
    return args.Error(0)
}

// Test with mock
func TestUserService_CreateUser(t *testing.T) {
    mockRepo := new(MockUserRepository)
    mockRepo.On("Create", mock.Anything).Return(nil)
    
    service := NewUserService(mockRepo)
    user, err := service.CreateUser(&CreateUserRequest{
        Email: "test@example.com",
    })
    
    assert.NoError(t, err)
    assert.NotNil(t, user)
    mockRepo.AssertExpectations(t)
}

3.7 API Standards

Pagination obligatoire

Toute route retournant une liste doit accepter les paramètres limit et offset et imposer une limite maximale de 100 éléments.

const (
    DefaultPageSize = 20
    MaxPageSize     = 100
)

func sanitizePagination(limit, offset int) (int, int) {
    if limit <= 0 || limit > MaxPageSize {
        limit = DefaultPageSize
    }
    if offset < 0 {
        offset = 0
    }
    return limit, offset
}

// ❌ Interdit : liste sans pagination
func (h *TrackHandler) ListTracks(c *gin.Context) {
    tracks, _ := h.service.ListAll(c.Request.Context())
    c.JSON(200, tracks)
}

// ✅ Obligatoire : pagination bornée
func (h *TrackHandler) ListTracks(c *gin.Context) {
    limit, offset := sanitizePagination(
        c.GetInt("limit"),
        c.GetInt("offset"),
    )
    tracks, total, err := h.service.List(c.Request.Context(), limit, offset)
    if err != nil {
        RespondWithAppError(c, err)
        return
    }
    c.JSON(200, PaginatedResponse{Data: tracks, Total: total, Limit: limit, Offset: offset})
}

Propagation du contexte

Interdit d'utiliser context.Background() dans les handlers ou les services. Le contexte de la requête HTTP doit être propagé à toutes les couches.

// ❌ Interdit : context.Background() dans un handler/service
func (s *TrackService) GetTrack(id uuid.UUID) (*Track, error) {
    return s.repo.FindByID(context.Background(), id)
}

// ✅ Obligatoire : propager le contexte de la requête
func (s *TrackService) GetTrack(ctx context.Context, id uuid.UUID) (*Track, error) {
    return s.repo.FindByID(ctx, id)
}

Gestion des erreurs standardisée

Tous les handlers doivent utiliser RespondWithAppError pour les réponses d'erreur. Les réponses gin.H{"error": ...} ad hoc sont interdites.

// ❌ Interdit : réponses d'erreur ad hoc
func (h *TrackHandler) GetTrack(c *gin.Context) {
    track, err := h.service.GetTrack(c.Request.Context(), id)
    if err != nil {
        c.JSON(500, gin.H{"error": "internal error"})
        return
    }
    c.JSON(200, track)
}

// ✅ Obligatoire : RespondWithAppError
func (h *TrackHandler) GetTrack(c *gin.Context) {
    track, err := h.service.GetTrack(c.Request.Context(), id)
    if err != nil {
        RespondWithAppError(c, err)
        return
    }
    c.JSON(200, track)
}

4. RUST STANDARDS (SERVICES)

4.1 Project Structure

veza-chat-server/
├── src/
│   ├── main.rs                     # Entry point
│   ├── lib.rs                      # Library root
│   ├── handlers/                   # WebSocket handlers
│   │   ├── mod.rs
│   │   ├── message.rs
│   │   └── presence.rs
│   ├── services/                   # Business logic
│   │   ├── mod.rs
│   │   ├── message_service.rs
│   │   └── auth_service.rs
│   ├── repository/                 # Data access
│   │   ├── mod.rs
│   │   └── message_repository.rs
│   ├── models/                     # Data models
│   │   ├── mod.rs
│   │   ├── message.rs
│   │   └── room.rs
│   ├── utils/                      # Utilities
│   │   ├── mod.rs
│   │   └── jwt.rs
│   └── config.rs                   # Configuration
├── migrations/                     # SQLx migrations
├── tests/
│   ├── integration/
│   └── unit/
├── Cargo.toml
└── Cargo.lock

4.2 Naming Conventions

Variables/Functions: snake_case

let user_name = "John";
fn create_user() {}

Types/Traits: PascalCase

struct UserService;
trait Repository {}

Constants: SCREAMING_SNAKE_CASE

const MAX_CONNECTIONS: usize = 10000;

Lifetimes: short, lowercase

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {}

4.3 Error Handling

Use Result Type:

use anyhow::{Result, Context};

fn create_user(email: &str) -> Result<User> {
    if email.is_empty() {
        return Err(anyhow!("Email is required"));
    }
    
    let user = User::new(email)
        .context("Failed to create user")?;
    
    Ok(user)
}

Custom Error Types (with thiserror):

use thiserror::Error;

#[derive(Error, Debug)]
pub enum UserError {
    #[error("User not found: {0}")]
    NotFound(Uuid),
    
    #[error("Email already exists: {0}")]
    EmailExists(String),
    
    #[error("Validation failed: {0}")]
    Validation(String),
    
    #[error(transparent)]
    Database(#[from] sqlx::Error),
}

// Usage
fn find_user(id: Uuid) -> Result<User, UserError> {
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
        .fetch_one(&pool)
        .await
        .map_err(|_| UserError::NotFound(id))?;
    
    Ok(user)
}

Avoid unwrap/expect in Production:

// ❌ Bad: unwrap can panic
let user = find_user(id).unwrap();

// ✅ Good: Handle error
let user = find_user(id)
    .context("Failed to find user")?;

// ⚠️ OK: expect for programmer errors (init)
let config = Config::from_env()
    .expect("CONFIG environment variables missing");

4.4 Ownership & Borrowing

Prefer Borrowing:

// ❌ Bad: Takes ownership (can't use afterwards)
fn process_user(user: User) {
    println!("{}", user.name);
}

let user = User::new("John");
process_user(user);
// user is moved, can't use here

// ✅ Good: Borrows (can still use)
fn process_user(user: &User) {
    println!("{}", user.name);
}

let user = User::new("John");
process_user(&user);
// user still usable here

Clone When Necessary:

// ✅ Clone for thread safety
let user = user.clone();
tokio::spawn(async move {
    process_user(user).await;
});

4.5 Async/Await

Use async/await for IO:

use tokio;

async fn fetch_user(id: Uuid) -> Result<User> {
    let user = sqlx::query_as!(
        User,
        "SELECT * FROM users WHERE id = $1",
        id
    )
    .fetch_one(&pool)
    .await?;
    
    Ok(user)
}

#[tokio::main]
async fn main() -> Result<()> {
    let user = fetch_user(uuid).await?;
    println!("{:?}", user);
    Ok(())
}

Concurrent Operations:

use tokio::try_join;

async fn fetch_user_data(user_id: Uuid) -> Result<(User, Vec<Track>)> {
    let user_future = fetch_user(user_id);
    let tracks_future = fetch_user_tracks(user_id);
    
    // Run concurrently
    let (user, tracks) = try_join!(user_future, tracks_future)?;
    
    Ok((user, tracks))
}

4.6 Testing

Unit Tests:

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_user_creation() {
        let user = User::new("test@example.com");
        assert_eq!(user.email, "test@example.com");
    }
    
    #[tokio::test]
    async fn test_fetch_user() {
        let pool = setup_test_db().await;
        let user = fetch_user(test_uuid, &pool).await.unwrap();
        assert_eq!(user.email, "test@example.com");
    }
}

Property-Based Testing (proptest):

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_email_validation(email in "[a-z]+@[a-z]+\\.[a-z]+") {
        assert!(is_valid_email(&email));
    }
}

5. TYPESCRIPT STANDARDS (FRONTEND)

5.1 TypeScript Configuration

tsconfig.json (strict mode):

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

5.2 Type Definitions

Prefer Interfaces over Types (for objects):

// ✅ Good: Interface (can be extended)
interface User {
  id: string;
  email: string;
  name: string;
}

interface AdminUser extends User {
  permissions: string[];
}

// ⚠️ Type (use for unions, intersections)
type UserRole = 'user' | 'creator' | 'admin';
type Optional<T> = T | null | undefined;

Avoid any:

// ❌ Bad: any (loses type safety)
function processData(data: any) {
  return data.value;
}

// ✅ Good: Generic
function processData<T>(data: T): T {
  return data;
}

// ✅ Good: unknown (safe any)
function processJSON(json: string): unknown {
  return JSON.parse(json);
}

const data = processJSON('{"value": 42}');
// Must type-check before using
if (typeof data === 'object' && data !== null && 'value' in data) {
  console.log(data.value);
}

Discriminated Unions:

type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

function handleResponse<T>(response: ApiResponse<T>) {
  if (response.status === 'success') {
    // TypeScript knows response.data exists
    console.log(response.data);
  } else {
    // TypeScript knows response.error exists
    console.error(response.error);
  }
}

5.3 Function Types

Type Function Parameters and Returns:

// ✅ Good: Typed
function calculateTotal(
  price: number,
  quantity: number,
  discount?: number
): number {
  const subtotal = price * quantity;
  return discount ? subtotal * (1 - discount) : subtotal;
}

// ✅ Good: Arrow function
const calculateTotal = (
  price: number,
  quantity: number,
  discount = 0
): number => {
  const subtotal = price * quantity;
  return subtotal * (1 - discount);
};

Async Functions:

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new Error('Failed to fetch user');
  }
  return response.json();
}

5.4 Null Safety

Use Optional Chaining:

// ❌ Bad: Verbose null checks
const city = user && user.profile && user.profile.location && user.profile.location.city;

// ✅ Good: Optional chaining
const city = user?.profile?.location?.city;

Use Nullish Coalescing:

// ❌ Bad: || can give unexpected results (0, '', false treated as falsy)
const port = process.env.PORT || 3000;  // Problem if PORT=0

// ✅ Good: ?? only for null/undefined
const port = process.env.PORT ?? 3000;

5.5 Enums vs Union Types

Prefer String Unions (more type-safe):

// ✅ Good: String union (can't assign invalid values)
type UserRole = 'user' | 'creator' | 'premium' | 'moderator' | 'admin';

const role: UserRole = 'user';  // ✅
const invalidRole: UserRole = 'guest';  // ❌ Compile error

// ⚠️ Enum (runtime overhead, can assign numbers)
enum UserRole {
  User = 'user',
  Creator = 'creator',
  Admin = 'admin',
}

6. REACT STANDARDS

6.1 Component Structure

Functional Components (prefer over class):

// ✅ Good: Functional component with TypeScript
interface TrackCardProps {
  track: Track;
  onPlay: (trackId: string) => void;
  className?: string;
}

export const TrackCard: React.FC<TrackCardProps> = ({ 
  track, 
  onPlay, 
  className 
}) => {
  const [isLiked, setIsLiked] = useState(false);
  
  const handleLike = useCallback(() => {
    setIsLiked(!isLiked);
    // API call...
  }, [isLiked]);
  
  return (
    <div className={cn('track-card', className)}>
      <h3>{track.title}</h3>
      <button onClick={() => onPlay(track.id)}>Play</button>
      <button onClick={handleLike}>
        {isLiked ? '❤️' : '🤍'}
      </button>
    </div>
  );
};

6.2 Hooks

Use Hooks Correctly:

// ✅ Good: Hooks at top level
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUser(userId).then(setUser).finally(() => setLoading(false));
  }, [userId]);
  
  if (loading) return <Spinner />;
  if (!user) return <div>User not found</div>;
  
  return <div>{user.name}</div>;
}

// ❌ Bad: Conditional hooks
function UserProfile({ userId }: { userId: string }) {
  if (!userId) return null;
  
  const [user, setUser] = useState<User | null>(null);  // ❌ Conditional hook
  // ...
}

Custom Hooks:

// ✅ Good: Extract reusable logic
function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    setLoading(true);
    fetchUser(userId)
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);
  
  return { user, loading, error };
}

// Usage
function UserProfile({ userId }: { userId: string }) {
  const { user, loading, error } = useUser(userId);
  
  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <div>User not found</div>;
  
  return <div>{user.name}</div>;
}

useMemo and useCallback:

// ✅ Good: Memoize expensive computations
function TrackList({ tracks }: { tracks: Track[] }) {
  const sortedTracks = useMemo(() => {
    return [...tracks].sort((a, b) => b.playCount - a.playCount);
  }, [tracks]);
  
  const handlePlay = useCallback((trackId: string) => {
    playTrack(trackId);
  }, []);
  
  return (
    <div>
      {sortedTracks.map(track => (
        <TrackCard key={track.id} track={track} onPlay={handlePlay} />
      ))}
    </div>
  );
}

6.3 State Management

Local State (useState):

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Global State (Zustand):

import { create } from 'zustand';

interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
  logout: () => void;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// Usage
function Header() {
  const user = useUserStore((state) => state.user);
  const logout = useUserStore((state) => state.logout);
  
  return (
    <header>
      {user ? (
        <>
          <span>{user.name}</span>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <a href="/login">Login</a>
      )}
    </header>
  );
}

Server State (React Query):

import { useQuery, useMutation } from '@tanstack/react-query';

function useTrack(trackId: string) {
  return useQuery({
    queryKey: ['track', trackId],
    queryFn: () => fetchTrack(trackId),
  });
}

function useLikeTrack() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: (trackId: string) => likeTrack(trackId),
    onSuccess: (data, trackId) => {
      // Invalidate cache
      queryClient.invalidateQueries({ queryKey: ['track', trackId] });
    },
  });
}

// Usage
function TrackPage({ trackId }: { trackId: string }) {
  const { data: track, isLoading, error } = useTrack(trackId);
  const likeMutation = useLikeTrack();
  
  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!track) return <div>Track not found</div>;
  
  return (
    <div>
      <h1>{track.title}</h1>
      <button 
        onClick={() => likeMutation.mutate(track.id)}
        disabled={likeMutation.isPending}
      >
        Like
      </button>
    </div>
  );
}

6.4 Component Composition

Avoid Prop Drilling:

// ❌ Bad: Prop drilling
function App() {
  const [user, setUser] = useState<User | null>(null);
  
  return <Layout user={user} setUser={setUser} />;
}

function Layout({ user, setUser }: { user: User | null; setUser: (user: User) => void }) {
  return <Header user={user} setUser={setUser} />;
}

function Header({ user, setUser }: { user: User | null; setUser: (user: User) => void }) {
  return <UserMenu user={user} setUser={setUser} />;
}

// ✅ Good: Context or global state
function App() {
  return <Layout />;
}

function Header() {
  const user = useUserStore((state) => state.user);
  return <UserMenu />;
}

Compound Components:

// ✅ Good: Compound pattern
interface CardProps {
  children: React.ReactNode;
}

export const Card = ({ children }: CardProps) => {
  return <div className="card">{children}</div>;
};

Card.Header = ({ children }: CardProps) => {
  return <div className="card-header">{children}</div>;
};

Card.Body = ({ children }: CardProps) => {
  return <div className="card-body">{children}</div>;
};

Card.Footer = ({ children }: CardProps) => {
  return <div className="card-footer">{children}</div>;
};

// Usage
<Card>
  <Card.Header>Title</Card.Header>
  <Card.Body>Content</Card.Body>
  <Card.Footer>Actions</Card.Footer>
</Card>

7. CSS/TAILWIND STANDARDS

7.1 Tailwind Utilities

Prefer Tailwind Utilities:

// ✅ Good: Tailwind utilities
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
  <h2 className="text-xl font-bold text-gray-900">Title</h2>
  <button className="px-4 py-2 text-white bg-blue-600 rounded hover:bg-blue-700">
    Action
  </button>
</div>

// ❌ Bad: Inline styles
<div style={{ 
  display: 'flex', 
  alignItems: 'center', 
  justifyContent: 'space-between',
  padding: '16px',
  backgroundColor: 'white',
  borderRadius: '8px',
  boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}>
  {/* ... */}
</div>

Use cn() Helper (for conditional classes):

import { cn } from '@/lib/utils';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  children: React.ReactNode;
}

export const Button = ({ 
  variant = 'primary', 
  size = 'md', 
  disabled = false,
  children 
}: ButtonProps) => {
  return (
    <button
      className={cn(
        'rounded font-semibold transition-colors',
        {
          'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
          'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
        },
        {
          'px-2 py-1 text-sm': size === 'sm',
          'px-4 py-2 text-base': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
        },
        {
          'opacity-50 cursor-not-allowed': disabled,
        }
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
};

7.2 Custom Components (Extract Reusable)

@apply for Component Classes:

/* components.css */
@layer components {
  .btn {
    @apply px-4 py-2 rounded font-semibold transition-colors;
  }
  
  .btn-primary {
    @apply bg-blue-600 text-white hover:bg-blue-700;
  }
  
  .btn-secondary {
    @apply bg-gray-200 text-gray-900 hover:bg-gray-300;
  }
  
  .card {
    @apply p-4 bg-white rounded-lg shadow-md;
  }
}

8. GIT STANDARDS

8.1 Commit Messages

Format: Conventional Commits

<type>(<scope>): <subject>

<body>

<footer>

Types:

feat:     New feature
fix:      Bug fix
docs:     Documentation changes
style:    Code style (formatting, no logic change)
refactor: Code refactoring (no feature/bug change)
perf:     Performance improvements
test:     Add/update tests
chore:    Build process, dependencies, tooling

Examples:

# Good commit messages
git commit -m "feat(auth): add two-factor authentication"
git commit -m "fix(api): resolve rate limiting bug on login endpoint"
git commit -m "refactor(user): extract validation logic to separate service"
git commit -m "docs(readme): update installation instructions"

# Bad commit messages
git commit -m "fix bug"
git commit -m "WIP"
git commit -m "update"

8.2 Branch Naming

Format: <type>/<ticket>-<description>

feature/VEZ-123-user-authentication
bugfix/VEZ-456-fix-login-error
hotfix/VEZ-789-critical-security-patch
refactor/VEZ-012-extract-user-service
docs/VEZ-345-api-documentation

8.3 Pull Request Template

## Description
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed

## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warnings

9. DOCUMENTATION STANDARDS

9.1 Code Comments

Go (godoc):

// CreateUser creates a new user in the system.
// It validates the request, checks for duplicate emails,
// hashes the password, and stores the user in the database.
//
// Returns the created user and nil error on success.
// Returns nil and error if validation fails or database error occurs.
func CreateUser(req *CreateUserRequest) (*User, error) {
    // Validation
    if err := validateCreateRequest(req); err != nil {
        return nil, fmt.Errorf("validation failed: %w", err)
    }
    
    // ... implementation
}

Rust (rustdoc):

/// Creates a new user in the system.
///
/// # Arguments
///
/// * `email` - The user's email address
/// * `password` - The user's plain-text password (will be hashed)
///
/// # Returns
///
/// Returns `Ok(User)` on success, or `Err(UserError)` if:
/// - Email is invalid
/// - Email already exists
/// - Database error occurs
///
/// # Examples
///
/// ```
/// let user = create_user("test@example.com", "SecurePass123").await?;
/// assert_eq!(user.email, "test@example.com");
/// ```
pub async fn create_user(email: &str, password: &str) -> Result<User, UserError> {
    // Implementation...
}

TypeScript (JSDoc):

/**
 * Fetches a user by ID from the API.
 *
 * @param userId - The UUID of the user to fetch
 * @returns A Promise that resolves to the User object
 * @throws {Error} If the user is not found or API request fails
 *
 * @example
 * ```typescript
 * const user = await fetchUser('550e8400-e29b-41d4-a716-446655440000');
 * console.log(user.name);
 * ```
 */
export async function fetchUser(userId: string): Promise<User> {
  const response = await fetch(`/api/users/${userId}`);
  if (!response.ok) {
    throw new Error('Failed to fetch user');
  }
  return response.json();
}

9.2 README Files

Every Module/Package Needs README.md:

# User Service

User management service for Veza platform.

## Features

- User registration with email verification
- Password authentication with bcrypt
- JWT token generation
- Two-factor authentication (TOTP)

## Usage

```go
import "veza/internal/services"

userService := services.NewUserService(db, redis)
user, err := userService.CreateUser(&CreateUserRequest{
    Email:    "test@example.com",
    Password: "SecurePass123",
})

Testing

go test ./... -v

Dependencies

  • GORM (database ORM)
  • bcrypt (password hashing)
  • jwt-go (JWT tokens)

### 9.3 Architecture Decision Records (ADR)

Toute décision architecturale significative **doit** être documentée sous forme d'ADR dans le dossier `docs/adr/`.

**Format obligatoire** :

```markdown
# ADR-NNN: Titre de la décision

## Statut
Accepté | Proposé | Déprécié | Remplacé par ADR-XXX

## Contexte
Quel est le problème ou la situation qui motive cette décision ?

## Décision
Quelle est la décision prise ?

## Raison
Pourquoi cette décision a-t-elle été choisie ?

## Alternatives rejetées
Quelles autres options ont été envisagées et pourquoi ont-elles été écartées ?

## Conséquences
Quels sont les effets positifs et négatifs de cette décision ?

## Impact éthique
(Obligatoire si pertinent) Quel est l'impact sur la vie privée des utilisateurs,
l'équité de traitement des créateurs, ou la transparence de la plateforme ?

Règles :

  • Chaque ADR est immuable une fois accepté (on crée un nouvel ADR pour le remplacer)
  • La section Impact éthique est obligatoire dès que la décision touche aux données utilisateurs, aux algorithmes de classement, ou à la collecte de métriques
  • Les ADR sont numérotés séquentiellement (ADR-001, ADR-002, ...)
  • Un ADR doit être créé avant l'implémentation, pas après

10. CODE REVIEW PROCESS

10.1 Review Checklist

Reviewer Must Check:

  • Code follows style guidelines (linters pass)
  • Tests are included and pass
  • Documentation is updated
  • No security vulnerabilities (SQL injection, XSS, etc.)
  • Error handling is complete
  • Performance is acceptable (no N+1 queries, etc.)
  • Breaking changes are documented

10.2 Review Comments

Constructive Feedback:

✅ Good:
"Consider extracting this logic into a separate function for better testability:
func validateEmail(email string) error { ... }"

❌ Bad:
"This code is terrible."

Approval Process:

  • 2 approvals required (1 from senior engineer)
  • All comments resolved
  • CI/CD passes

11. REFACTORING GUIDELINES

11.1 When to Refactor

Triggers:

  • Duplicated code (DRY violation)
  • Functions > 50 lines
  • Cyclomatic complexity > 10
  • Test coverage < 80%
  • Code smells (magic numbers, deep nesting)

Boy Scout Rule: Leave code cleaner than you found it

11.2 Refactoring Techniques

Extract Method:

// Before
func ProcessOrder(order *Order) error {
    // Validate order (20 lines)
    // ...
    
    // Calculate total (15 lines)
    // ...
    
    // Process payment (25 lines)
    // ...
}

// After
func ProcessOrder(order *Order) error {
    if err := validateOrder(order); err != nil {
        return err
    }
    
    total := calculateTotal(order)
    
    if err := processPayment(order, total); err != nil {
        return err
    }
    
    return nil
}

12. ANTI-PATTERNS LIBRARY

12.1 God Object

Problem: One class/module does everything

Solution: Split into smaller, focused modules (SRP)

12.2 Spaghetti Code

Problem: Tangled, unstructured code

Solution: Use clear architecture (Clean Architecture, layered)

12.3 Magic Numbers

Problem: Unexplained literal values

// ❌ Bad
if user.Age > 18 && user.Age < 65 {
    // ...
}

// ✅ Good
const (
    MinimumAdultAge = 18
    RetirementAge   = 65
)

if user.Age >= MinimumAdultAge && user.Age < RetirementAge {
    // ...
}

12.4 Premature Optimization

Problem: Optimizing before identifying bottlenecks

Solution: Profile first, then optimize

13. ERROR PREVENTION

13.1 Pre-Flight Checks (OBLIGATOIRE)

Avant de commencer TOUTE nouvelle tâche :

  • Exécuter ./scripts/pre-flight-check.sh
  • Vérifier qu'aucune erreur P0/P1 existe
  • Tests existants passent
  • Linter clean
  • Code à jour avec main

Référence complète : docs/ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md

13.2 Templates de Code (OBLIGATOIRE)

Utiliser les templates validés pour créer de nouveaux fichiers :

  • Backend Go : dev-environment/templates/backend-*.template.go
  • Frontend React : dev-environment/templates/frontend-*.template.tsx
  • Rust : dev-environment/templates/rust-*.template.rs

NE JAMAIS créer un fichier sans utiliser un template (sauf exception approuvée).

13.3 Patterns Sûrs

Backend Go :

  • Interfaces dans internal/types/ ou internal/interfaces/
  • Services dépendent uniquement d'interfaces
  • Handlers dépendent uniquement d'interfaces
  • Types cohérents (toujours string OU toujours *string)

Frontend TypeScript/React :

  • Types explicites pour toutes les fonctions
  • Self-closing tags JSX (<Component />)
  • Mocks configurés pour tous les tests
  • Logger au lieu de console.log

Référence : docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md pour tous les patterns.

13.4 Quality Gates

Pre-Commit (Husky) :

  • Formatage automatique
  • Linter (zero errors)
  • Tests unitaires rapides
  • Type checking

Pre-Merge (GitHub Actions) :

  • Architecture validation (import cycles)
  • Type safety
  • Test coverage ≥ 80%
  • Linter validation
  • Build validation

Si un gate échoue : Corriger l'erreur, NE PAS contourner.

13.5 Documentation des Erreurs

Si une nouvelle erreur est découverte :

  1. Documenter dans docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md
  2. Ajouter la solution standard
  3. Mettre à jour les checklists de prévention
  4. Communiquer à l'équipe

CHECKLIST DE VALIDATION

Code Quality

  • Formatters run (gofmt, rustfmt, Prettier)
  • Linters pass (golangci-lint, clippy, ESLint)
  • Tests written (coverage ≥ 80%)
  • Documentation updated (README, comments)

Error Prevention

  • Pre-flight check exécuté et passé
  • Template utilisé pour nouveau fichier
  • Patterns sûrs suivis
  • Aucun anti-pattern introduit

Architecture

  • Clean Architecture followed
  • SOLID principles respected
  • Dependencies injected
  • Separation of concerns
  • No import cycles

Performance

  • No N+1 queries
  • Expensive operations optimized
  • Caching considered
  • Bundle size acceptable (frontend)

Security

  • Input validated
  • Errors handled
  • Secrets not committed
  • SQL injection prevented

📊 MÉTRIQUES DE SUCCÈS

Code Quality Metrics

  • Cyclomatic Complexity: < 10 per function
  • Function Length: < 50 lines
  • Test Coverage: ≥ 80%
  • Code Duplication: < 3%
  • Linter Warnings: 0

🔄 HISTORIQUE DES VERSIONS

Version Date Changements
2.0.0 2026-03-04 Révision éthique — ajout standards éthiques du code, ADR, standards API (pagination, contexte, erreurs)
1.0.0 2025-11-02 Version initiale - Standards complets

⚠️ AVERTISSEMENT

CES STANDARDS SONT IMMUABLES

Les standards de code définis ici sont OBLIGATOIRES. Toute dérogation nécessite:

  1. RFC Code Standards Exception avec justification
  2. Approbation Lead Engineer
  3. Documentation de l'exception

Le non-respect des standards bloque la merge de la PR.


Document créé par: Engineering Team
Date de création: 2025-11-02
Dernière révision: 2026-03-04 (v2.0.0 — révision éthique)
Prochaine révision: Annuelle
Propriétaire: Lead Engineers (Go, Rust, Frontend)

Statut: APPROUVÉ ET VERROUILLÉ