Il y a une place pour tout
Résister à l’envie de vider plus de code dans la classe sur laquelle vous travaillez au lieu d’ouvrir la bonne ou, beurk, d’en créer une nouvelle est DIFFICILE. Pourquoi ne pas simplement mettre cette assiette dans le lave-vaisselle au lieu de l’évier ?
C’est de là que viennent les cours de Dieu. La classe en sait trop. Celui avec la liste d’importation ne se termine jamais. Jack de tous les objets, maître d’aucun.
Je laisserai Eric S. Raymond résumer pourquoi cela n’est pas souhaitable en vertu de la règle de modularité dans L’art de la programmation Unix:
La seule façon d’écrire un logiciel complexe qui ne tombera pas à plat est de maintenir sa complexité globale – de le construire à partir de parties simples reliées par des interfaces bien définies, de sorte que la plupart des problèmes soient locaux et que vous puissiez espérer améliorer une partie sans casser le tout.
Nous voulons revenir au principe de responsabilité unique de SOLID dans chaque classe en faisant bien une chose. La bonne nouvelle est que vous pouvez commencer à nettoyer cela petit à petit à tout moment.
Bien que certains aspects de cet anti-modèle puissent se glisser n’importe où dans un projet, certains mots-clés comme « commun » et « utilitaire » attirent mon attention comme étant les dépotoirs.
Commençons par un exemple inspiré du code des projets sur lesquels j’ai travaillé. Voici une petite version de ce qui peut facilement atteindre des milliers de lignes sans rapport s’il n’est pas arrêté :
class CommonUtils:
def send_alert(self, message):
try:
return client.chat_postMessage(
channel="C01ABCD1234",
text=f"BE CAREFUL! {message}"
)
except Exception as e:
print(f"Error: {e}")def get_service_name_from_url(self, url):
return url.split('.')[1]
def read_file(self, path, filename):
with open(path + "/" + filename) as f:
return f.read()
Les meilleures intentions sont là. Envelopper l’alerte via le client de chat est logique, nous avons donc un endroit pour apporter des modifications si nous devons ou voulons passer à un autre service.
Mais quelle est la probabilité que cela passe inaperçu enfoui au milieu de cette classe qui défie toute description et que la prochaine personne utilise directement le client ou pire ajoute une autre presque mais pas vraiment la même fonction à cette classe 67 lignes plus bas ? Avec à peine plus d’efforts, nous déplacerons cette fonction vers la sienne message_service.py
class MessageServiceException:
passclass MessageService:
def alert(self, message):
try:
client.chat_postMessage(
channel="C01ABCD1234",
text=f"BE CAREFUL! {message}"
)
except Exception as e:
raise(MessageServiceException(e))
Ajoutez un bon test unitaire et il y a beaucoup plus de chances de voir la réutilisation et l’expansion.
Ensuite, cette petite fonction d’analyse pratique semble convenir à notre classe commune, n’est-ce pas ? Notre image réelle, cependant, est une URL avec un format particulier transmis autour de l’application sous forme de chaîne simple, et au moment où nous avons besoin de quelque chose, nous invoquons cette fonction.
Maintenant, où ai-je mis cette petite chose ? C’est dans la pile de clés Allen simples que j’ai avec tous ces petits meubles. C’est un excellent candidat pour un objet de domaine :
def getServiceNameFromUrl(self, url):
return url.split(‘.’)[1]
devient:
class ServiceUrl:
def __init__(self, urlstring):
parsedUrl = urllib.parse(str(urlstring).strip())
self.host = parsedUrl.netloc
host_parts = self.host.split(‘.’)
self.app_name = host[0]
self.service_name = host[1]
self.region = host[2]
self.domain = host[3:].join(‘.’)
self.path = parseUrl.pathdef __str__(self):
return f"https://{self.host}/{self.path}"
Son utilisation est aussi simple que :
ServiceUrl("https://app.service.region.example.com/root").service_name
J’approfondis ce sujet dans Making Strings Smart in Python.
Le dernier refactor de la liste est un wrapper pour la lecture de fichiers :
def read_file(self, path, filename):
with open(path + “/” + filename) as f:
return f.read()
C’est de loin le plus simple des trois. Supprimez-le simplement ! Il y a quelque chose d’irrésistible dans l’emballage des fonctionnalités de base, mais c’est dommageable. La syntaxe Python d’origine de la fonction qu’elle enveloppe est tout aussi lisible sinon plus que l’encapsuleur.
Il y a toujours une idée qu’il sera utile d’avoir cette fonction pour apporter des modifications plus tard, mais si vous utilisiez vraiment cette fonction partout dans l’application dont vous aviez besoin pour lire un fichier, vous n’oseriez jamais la changer. Le potentiel de casser quelque chose est tout simplement trop grand. Vous verrez ce type de code dans chaque projet et je vous encourage à le supprimer sans remords.
Chaque fois que vous voyez une classe dont vous ne pouvez pas décrire le but en une phrase ou deviner ce qu’elle fait à partir du nom, prenez ces stratégies que nous avons examinées dans l’article et continuez à pirater des morceaux jusqu’à ce que ce soit un souvenir qui s’estompe. Git rm, git commit, git push.
Cela vous choquera de voir à quel point cela ne s’est jamais produit.