python-anti-patterns

Par wshobson · agents

Utilisez cette compétence lors de la revue de code Python pour identifier les anti-patterns courants à éviter. À utiliser comme liste de contrôle lors des revues de code, avant de finaliser des implémentations, ou lors du débogage de problèmes pouvant provenir de mauvaises pratiques connues.

npx skills add https://github.com/wshobson/agents --skill python-anti-patterns

Liste de contrôle des anti-patterns Python

Une liste de contrôle de référence des erreurs courantes et anti-patterns dans le code Python. Vérifiez-la avant de finaliser les implémentations pour détecter les problèmes rapidement.

Quand utiliser cette skill

  • Examen du code avant fusion
  • Débogage de problèmes mystérieux
  • Enseignement ou apprentissage des bonnes pratiques Python
  • Établissement de normes de codage pour l'équipe
  • Refactorisation de code hérité

Note : Cette skill se concentre sur ce qu'il faut éviter. Pour des conseils sur les patterns positifs et l'architecture, consultez la skill python-design-patterns.

Anti-patterns d'infrastructure

Logique de timeout/retry dispersée

# BAD: Logique de timeout dupliquée partout
def fetch_user(user_id):
    try:
        return requests.get(url, timeout=30)
    except Timeout:
        logger.warning("Timeout fetching user")
        return None

def fetch_orders(user_id):
    try:
        return requests.get(url, timeout=30)
    except Timeout:
        logger.warning("Timeout fetching orders")
        return None

Correction : Centralisez dans des décorateurs ou des wrappers de client.

# GOOD: Logique de retry centralisée
@retry(stop=stop_after_attempt(3), wait=wait_exponential())
def http_get(url: str) -> Response:
    return requests.get(url, timeout=30)

Double retry

# BAD: Retry à plusieurs niveaux
@retry(max_attempts=3)  # Retry applicatif
def call_service():
    return client.request()  # Le client a aussi un retry configuré !

Correction : Utilisez retry à un seul niveau. Connaissez le comportement de retry de votre infrastructure.

Configuration en dur

# BAD: Secrets et config dans le code
DB_HOST = "prod-db.example.com"
API_KEY = "sk-12345"

def connect():
    return psycopg.connect(f"host={DB_HOST}...")

Correction : Utilisez des variables d'environnement avec des paramètres typés.

# GOOD
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    db_host: str = Field(alias="DB_HOST")
    api_key: str = Field(alias="API_KEY")

settings = Settings()

Anti-patterns d'architecture

Types internes exposés

# BAD: Fuite du modèle ORM vers l'API
@app.get("/users/{id}")
def get_user(id: str) -> UserModel:  # Modèle SQLAlchemy
    return db.query(UserModel).get(id)

Correction : Utilisez des DTOs/modèles de réponse.

# GOOD
@app.get("/users/{id}")
def get_user(id: str) -> UserResponse:
    user = db.query(UserModel).get(id)
    return UserResponse.from_orm(user)

Mélange d'I/O et logique métier

# BAD: SQL intégré dans la logique métier
def calculate_discount(user_id: str) -> float:
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
    # Logique métier mélangée avec accès aux données
    if len(orders) > 10:
        return 0.15
    return 0.0

Correction : Pattern repository. Gardez la logique métier pure.

# GOOD
def calculate_discount(user: User, orders: list[Order]) -> float:
    # Logique métier pure, facile à tester
    if len(orders) > 10:
        return 0.15
    return 0.0

Anti-patterns de gestion d'erreurs

Gestion d'exceptions bare

# BAD: Masquage de toutes les exceptions
try:
    process()
except Exception:
    pass  # Échec silencieux - les bugs restent cachés à jamais

Correction : Capturez les exceptions spécifiques. Loggez ou gérez de manière appropriée.

# GOOD
try:
    process()
except ConnectionError as e:
    logger.warning("Connection failed, will retry", error=str(e))
    raise
except ValueError as e:
    logger.error("Invalid input", error=str(e))
    raise BadRequestError(str(e))

Échecs partiels ignorés

# BAD: S'arrête à la première erreur
def process_batch(items):
    results = []
    for item in items:
        result = process(item)  # Lève une exception - batch abandonné
        results.append(result)
    return results

Correction : Capturez à la fois les succès et les échecs.

# GOOD
def process_batch(items) -> BatchResult:
    succeeded = {}
    failed = {}
    for idx, item in enumerate(items):
        try:
            succeeded[idx] = process(item)
        except Exception as e:
            failed[idx] = e
    return BatchResult(succeeded, failed)

Validation d'entrée manquante

# BAD: Aucune validation
def create_user(data: dict):
    return User(**data)  # Crash au cœur du code sur une mauvaise entrée

Correction : Validez tôt aux limites de l'API.

# GOOD
def create_user(data: dict) -> User:
    validated = CreateUserInput.model_validate(data)
    return User.from_input(validated)

Anti-patterns de ressources

Ressources non fermées

# BAD: Fichier jamais fermé
def read_file(path):
    f = open(path)
    return f.read()  # Et si cela lève une exception ?

Correction : Utilisez les context managers.

# GOOD
def read_file(path):
    with open(path) as f:
        return f.read()

Blocking en async

# BAD: Bloque la boucle d'événements entière
async def fetch_data():
    time.sleep(1)  # Bloque tout !
    response = requests.get(url)  # Bloque aussi !

Correction : Utilisez des bibliothèques async-native.

# GOOD
async def fetch_data():
    await asyncio.sleep(1)
    async with httpx.AsyncClient() as client:
        response = await client.get(url)

Anti-patterns de sécurité de type

Type hints manquants

# BAD: Pas de types
def process(data):
    return data["value"] * 2

Correction : Annotez toutes les fonctions publiques.

# GOOD
def process(data: dict[str, int]) -> int:
    return data["value"] * 2

Collections non typées

# BAD: Liste générique sans paramètre de type
def get_users() -> list:
    ...

Correction : Utilisez des paramètres de type.

# GOOD
def get_users() -> list[User]:
    ...

Anti-patterns de test

Test du seul chemin heureux

# BAD: Test uniquement du cas de succès
def test_create_user():
    user = service.create_user(valid_data)
    assert user.id is not None

Correction : Testez les conditions d'erreur et les cas limites.

# GOOD
def test_create_user_success():
    user = service.create_user(valid_data)
    assert user.id is not None

def test_create_user_invalid_email():
    with pytest.raises(ValueError, match="Invalid email"):
        service.create_user(invalid_email_data)

def test_create_user_duplicate_email():
    service.create_user(valid_data)
    with pytest.raises(ConflictError):
        service.create_user(valid_data)

Sur-mocking

# BAD: Mockage de tout
def test_user_service():
    mock_repo = Mock()
    mock_cache = Mock()
    mock_logger = Mock()
    mock_metrics = Mock()
    # Le test ne vérifie pas le comportement réel

Correction : Utilisez les tests d'intégration pour les chemins critiques. Mockez uniquement les services externes.

Liste de contrôle d'examen rapide

Avant de finaliser le code, vérifiez :

  • [ ] Pas de logique de timeout/retry dispersée (centralisée)
  • [ ] Pas de double retry (app + infrastructure)
  • [ ] Pas de configuration ou secrets en dur
  • [ ] Pas de types internes exposés (modèles ORM, protobufs)
  • [ ] Pas de mélange I/O et logique métier
  • [ ] Pas de bare except Exception: pass
  • [ ] Pas d'échecs partiels ignorés dans les batches
  • [ ] Pas de validation d'entrée manquante
  • [ ] Pas de ressources non fermées (utilisation de context managers)
  • [ ] Pas d'appels bloquants dans le code async
  • [ ] Toutes les fonctions publiques ont des type hints
  • [ ] Les collections ont des paramètres de type
  • [ ] Les chemins d'erreur sont testés
  • [ ] Les cas limites sont couverts

Résumé des corrections courantes

Anti-Pattern Correction
Logique de retry dispersée Décorateurs centralisés
Config en dur Variables d'environnement + pydantic-settings
Modèles ORM exposés Schémas DTO/réponse
Mélange I/O + logique Pattern repository
Bare except Capture des exceptions spécifiques
Batch s'arrête à l'erreur Retournez BatchResult avec succès/échecs
Pas de validation Validation aux limites avec Pydantic
Ressources non fermées Context managers
Blocking en async Bibliothèques async-native
Types manquants Annotations de type sur toutes les APIs publiques
Tests du seul chemin heureux Testez les erreurs et cas limites

Skills similaires