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 |