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
- General Principles
- Standards éthiques du code
- Go Standards (Backend)
- Rust Standards (Services)
- TypeScript Standards (Frontend)
- React Standards
- CSS/Tailwind Standards
- Git Standards
- Documentation Standards
- Code Review Process
- Refactoring Guidelines
- Anti-Patterns Library
🔒 RÈGLES IMMUABLES
- Formatters OBLIGATOIRES: gofmt (Go), rustfmt (Rust), Prettier (TS/React)
- Linters OBLIGATOIRES: golangci-lint (Go), clippy (Rust), ESLint (TS)
- Tests OBLIGATOIRES pour toute nouvelle feature (coverage ≥ 80%)
- Code review OBLIGATOIRE (2 approbations minimum)
- Naming conventions STRICTES (camelCase/PascalCase/snake_case selon langage)
- Documentation OBLIGATOIRE pour fonctions publiques
- Error handling COMPLET (pas de panic/unwrap en production)
- Magic numbers INTERDITS (utiliser constantes nommées)
- Code mort INTERDIT (suppression immédiate)
- 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/ouinternal/interfaces/ - ✅ Services dépendent uniquement d'interfaces
- ✅ Handlers dépendent uniquement d'interfaces
- ✅ Types cohérents (toujours
stringOU 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 :
- Documenter dans
docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md - Ajouter la solution standard
- Mettre à jour les checklists de prévention
- 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:
- RFC Code Standards Exception avec justification
- Approbation Lead Engineer
- 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É