Écrire du code pour votre futur vous
Photo par Debby Hudson sur Unsplash
Nous écrivons tous du mauvais code. Soyons honnêtes. Même si nous faisons de notre mieux pour écrire le code le plus efficace et le mieux conçu, il nous arrive tous d’écrire cette méthode sale dont nous ne sommes pas si fiers et que nous redoutons de devoir déboguer le mois prochain, ou l’année prochaine.
Je ne vais pas parler de ce code, car il est si facile à repérer et il n’y a pas besoin d’écrire à ce sujet. Ce qui m’intéresse, c’est le code qui semble correct quand vous avez fini de taper, mais qui vous reviendra à la tronche un jour. Il finit par être difficile à maintenir, à étendre ou même à comprendre. Après des années de codage, j’ai établi un ensemble de petits warnings qui m’alertent chaque fois que je me dirige vers une impasse.
Signification
Le code est un langage. Encore plus lorsque vous utilisez un langage de programmation tel que Ruby, qui est conçu pour être très expressif. Mais pour moi, le code commence à sentir mauvais quand il devient peu clair ce qu’il fait vraiment.
Noms
Le nommage est essentiel. L’une des choses qui change le plus dans mon processus de création est probablement le nom des classes, méthodes et variables que je conçois.
Les humains nomment les choses qu’ils comprennent. Et plus la connaissance d’un élément est élevée, plus le nommage est précis. Renommer mes classes dans le processus de développement est généralement un bon signe que je me fais une meilleure idée de ce qu’elles font ou du concept qu’elles représentent.
Longueur des méthodes
L’un des modèles de conception les plus fréquents qu’on nous enseigne en tant que développeurs OOP en herbe est de garder les méthodes plus courtes que quelques lignes. Un avertissement, cependant, est que le code finit par être dispersé partout, et vous pouvez parfois perdre le contact avec le cycle de vie de vos données. Cela est particulièrement vrai pour les classes qui sont utilisées pour construire une structure de données.
Par exemple, prenez cette classe qui construit un hash data à envoyer à FCM :
module MobilePush
class FcmNotification < BaseNotification
def launch
# Bootstrap stuff...
push_service.send_message(project, data)
end
private
# Lots of methods...
def data
{
notification: json_notification,
recipient: recipient_name,
room: room_name
}
end
# Lots of other methods...
def json_notification
@notification.to_json
end
def recipient_name
@recipient.name
end
# Maybe a few other methods...
def room_name
@room.name
end
# The rest of the methods...
end
end
Note : Cet exemple est un peu trop simplifié pour des raisons de brièveté. Mais vous comprenez l’idée.
J’ai écrit cela en pensant “Hé, c’est intelligent ! Tout est bien trié. Ça ressemble vraiment à du code propre”.
Puis j’ai pris quelques jours de congé avec ma famille. Pendant mon absence, l’un de mes collègues a dû gérer mon nouveau code et ajouter une nouvelle fonctionnalité. Mais il l’a trouvé inutilement abstrait, difficile à maintenir et à étendre. Et il avait raison. Je l’ai donc refactorisé en :
module MobilePush
class FcmNotification < BaseNotification
def launch
# Bootstrap stuff...
push_service.send_message(project, data)
end
private
# Lots of methods...
def data
{
notification: @notification.to_json,
recipient: @recipient.name,
room: @room.name
}
end
# The rest of the methods...
end
end
Donc OK, j’ai dû remplacer room_name par @room.name à deux ou trois endroits dans la classe, car il était également utilisé ailleurs. Mais j’ai aussi rendu mon code un peu moins abstrait. Maintenant, mon collègue sait exactement de quoi est fait data, d’un seul coup d’œil.
Histoires
Une chose à retenir lors de l’écriture de code, c’est qu’il n’est pas seulement destiné à être efficace et concis. Le code est destiné à être lu par d’autres. Vos collègues, votre chef d’équipe, et encore plus crucialement, par la personne qui sera en charge de votre codebase autrefois nouveau, maintenant legacy.
One-liners
J’adore les one-liners. Ils sont amusants, vont droit au but, et ils mettent en valeur les compétences techniques et l’esprit de leurs créateurs. Ils sont à la programmation ce que les punchlines, ou les titres de journaux sont à l’écriture. Mais qui veut lire un livre entier écrit de cette façon ?
Même si j’utilise parfois des one-liners dans mon code, je ne le fais que lorsque je me sens confiant que mon successeur - ou mon futur moi - sera capable de lui donner un sens facilement.
Soyez explicite
Je crois que la plupart du code devrait être explicite et guider le lecteur avec des repères qui l’aideront à suivre.
Par exemple, au lieu de ce type d’expression :
Device.where('last_seen > ?', 1.hour.ago).where(logging: true).where(notifications: true).last.send_message(topic, payload)
Je préférerais soit utiliser des méthodes de classe, soit des scopes dans le modèle, pour finir avec :
device = Device.active_last(1.hour).logging.notifying.last
device.send_message(topic, payload)
Je ne suis pas stupide. Je peux comprendre le premier exemple. Mais lors de l’analyse de centaines de lignes de code pour trouver un bug, je peux regarder ce code et dire instantanément ce qu’il fait. Pas besoin de m’assurer que j’ai bien compris le < et le >. Cela a été fait une fois, le jour où il a été tapé, et je peux m’y fier en toute confiance car chacune de ces méthodes est testée et fait ce qu’elle dit.
Soyez méthodique
J’aime séparer les intentions dans ce que je fais avec ma classe. Quand vous racontez une histoire, vous introduisez d’abord votre personnage avec une description claire. Ensuite seulement vous lui donnez un rôle actif dans votre histoire. C’est ce que fait l’exemple de code ci-dessus. J’aurais aussi pu facilement écrire :
Device.active_last(1.hour).logging.notifying.last.send_message(topic, payload)
Mais cela mélange les intentions. Cette ligne interroge à la fois le modèle et déclenche une action, donc vous commencez à lire la ligne en pensant : “Oh, il définit le Device” et la terminez en pensant “Ah. Non, il envoie un message à ce device”. Votre lecteur ne devrait jamais jamais être induit en erreur, même dans des assertions courtes et simples comme celles-ci.
J’essaie de séparer les requêtes des actions. Ce sont deux types de tâches et nécessitent deux étapes distinctes dans mon code.
Considérez ces trois phrases :
L’homme dans le bar qui est vieux et sale parle des résultats de football d’hier. - désordonné
Il y a un homme qui parle dans le vieux bar sale des résultats de football d’hier. - plus clair
Dans le vieux bar sale, il y a un homme. Il parle des résultats de football d’hier. - cristallin
Je veux que mon code soit cristallin. Je veux que mon relecteur de code, mon successeur ou mon futur moi très-fatigué-un-vendredi-après-midi comprenne parfaitement ce que je fais avec mon code.
Tests
Écrire des tests m’aide à identifier les dépendances. Les tests d’intégration nécessitent souvent pas mal de bootstrapping : un utilisateur connecté, quelques entités liées… c’est bien.
Mais j’écris parfois des tests qui dépendent trop d’autres modèles. Si vous ne pouvez pas tester votre modèle Comment sans un modèle Post, alors quelque chose peut être trop étroitement couplé dans le code, et la relation entre ces entités doit être examinée.
Les assertions me guident également vers la clarté du code. J’ai constaté que lorsque j’ai du mal à définir une attente dans mon test, cela signifie généralement que cette méthode particulière ne fait pas quelque chose d’assez clair. Elle doit soit être divisée en méthodes plus petites, soit complètement repensée.
Un mot sur les commentaires
Un autre signe que le code doit être refactorisé est les commentaires. Les commentaires indiquent principalement que ce que fait le programme n’est pas clair. Donc quand je vois des commentaires dans le code, la première chose que je fais est de les supprimer et de comprendre ce qui se passe sans les lire. Et chaque fois que cela devient désordonné, c’est un bon signe qu’ils peuvent tous être remplacés par un simple TODO: refactor.
Les commentaires sont aussi souvent un signe de dépendance externe. Typiquement, vous obtiendrez ce genre de chose :
# Handle temperature > 21
# Deal with legacy customer IDs
Le premier peut dépendre de l’appareil que vous gérez, et cette dépendance pourrait être adressée par l’équipe matérielle.
Le second pourrait être supprimé en recourant à une classe héritée, comme LegacyCustomer, qui pourrait gérer ce genre de cas d’utilisateurs spéciaux et garder la classe Customer propre avec une interface prévisible et simple.
Les seuls commentaires que je garde sont les commentaires en haut au-dessus des déclarations de classe, la documentation Yard, quelques TODOs de refactoring et des méta stupides et amusantes sur le code.
Conclusion
Je regarde parfois du code désordonné et me demande “qui a écrit cette merde ?”
Voyons voir… Git blame…
Moi.
Je n’aime pas ce sentiment. Je veux être fier de mon code. Je veux que les autres développeurs, y compris mon futur moi, lui donnent un sens facilement et instantanément, et me remercient d’avoir fait l’effort de créer du code qui a du sens, est facile à déboguer et à maintenir.
Suivre cet ensemble de directives m’aidera, je l’espère, dans cette voie.
MoskitoHero