Terminologie
Cette article va être un peu fourre tout ça nous allons essayer de regrouper un certain nombre de terminologies que nous pouvons entendre dans le métier de développeur.
Et du coup, cela peut nous aider en tant qu'aide mémoire :)
Dans la façon de concevoir
KISS
Keep It Simple, Stupid!
Cela veut tout simplement dire d'essayer de produire une architecture / un code le plus simple possible.
Nous pouvons ainsi inclure pour le code le fameux principe du "No premature optimization" afin de rendre le code le plus simple et le plus compréhensible. Et ce n'est que si cela est nécessaire que nous allons optimiser le code (et en ce cas, bien documenter le pourquoi du comment)
Nous pouvons rejoindre aussi l'outillage du projet. Par exemple, sur un projet utiliser "Bower" et "TypeScript" (qui sont en soient de bon outils) impliquent que les utilisateurs sachent configurer et utiliser pleinement ces deux outils (et dans le cas de TypeScript, jusqu'à sa version 2.0, on devait connaître aussi l'outils "Typing" afin de rajouter des signatures TypeScript pour différents framworks). Et en fonction de son équipe, cela peut poser problème.
Du coup, si l'usage de Bower n'est pas nécessaire (car nous utilisons des frameworks qui sont accessibles via NPM) et / ou de TypeScript (car nous avons une version récente de NodeJs et / ou que nous utilisons une version récente de nos navigateurs), alors il vaut mieux éviter de les utiliser
Cela veut tout simplement dire d'essayer de produire une architecture / un code le plus simple possible.
Nous pouvons ainsi inclure pour le code le fameux principe du "No premature optimization" afin de rendre le code le plus simple et le plus compréhensible. Et ce n'est que si cela est nécessaire que nous allons optimiser le code (et en ce cas, bien documenter le pourquoi du comment)
Nous pouvons rejoindre aussi l'outillage du projet. Par exemple, sur un projet utiliser "Bower" et "TypeScript" (qui sont en soient de bon outils) impliquent que les utilisateurs sachent configurer et utiliser pleinement ces deux outils (et dans le cas de TypeScript, jusqu'à sa version 2.0, on devait connaître aussi l'outils "Typing" afin de rajouter des signatures TypeScript pour différents framworks). Et en fonction de son équipe, cela peut poser problème.
Du coup, si l'usage de Bower n'est pas nécessaire (car nous utilisons des frameworks qui sont accessibles via NPM) et / ou de TypeScript (car nous avons une version récente de NodeJs et / ou que nous utilisons une version récente de nos navigateurs), alors il vaut mieux éviter de les utiliser
DRY
Don't Repeat Yourself
Le principe ici est de prévenir la duplication de code, de configuration, ...
Bref, essayer de mutualiser le plus possible le code ou tout du moins, d'essayer d'utiliser le plus possible l'existant (et ainsi de ne pas réinventer la roue)
WET
We Enjoy Typing
L'inverse de DRY youy simplement
STUPID
Ici, nous avons une compilation de mauvaise pratiques à éviter:
- Singleton
- Thigh Coupling
- Untestability
- Premature Optimization
- Indescriptive Naming
- Duplication
a) Singleton
Ce design pattern est le plus connu et le plus simple à mettre en oeuvre.
En revanche, d'un point de vue conception logiciel, c'est un principe à éviter le plus possible ou tout du moins à éviter le plus possible son usage
En effet, le fait d'avoir un singleton fait que nous avons un objet qui a un état qui peut être modifié (et donc avoir des soucis dans le cas de code parallèle).
De plus, d'un point vue test, c'est très compliqué car tout simplement, nous ne pouvons pas le réinitialiser et préserve son état. Certains test runner essaie de décharger les objets instanciés le plus possible, mais ils sont plutôt rare.
De plus, l'usage du singleton reflète le symptôme d'un problème récurrent en architecture logiciel: les composants et le services d'une application ne sont pas correctement initialisés et / ou que le cycle de vie de cette application (ainsi que des services / composants) n'est pas bien maîtrisé.
b) Thigh Coupling
Ce que nous voulons dire par ici, c'est d'éviter autant que possible le couplage fort.
En Java, par exemple, ce serait de déclarer des variables via des interfaces et non pas via des implémentations (List versus ArrayList).
Et globalement, ce serait d'autoriser l'injection des dépendances. Autrement dit, lorsque nous instancions un service / composant, nous passons en paramètres les dépendances (via des interfaces) nécessaires, et que le service / composant évite d'importer ces dites dépendances.
Cela permet de plus facilement mocker les dépendances passés lors des tests et aussi de pouvoir remplacer l'implémentation plus facilement sans avoir à chercher toutes les services / composants qui importent cette dépendance.
c) Untestability
Grand drame de notre époque: c'est de concevoir du code qui ne peut être testé.
Je sais que certains tests sont plus compliqués à produire que d'autres. Par exemple, en Angular, faire des tests de services sont 100 fois plus simples que des tests de directives. En revanche, c'est testable !
Dans certains cas, des portions de code ne sont tout simplement pas testable. Ceci est très embêtant car un test a pour but:
- De s'arrurer de la non-régression
- D'expliquer le fonctionnel
- De montrer des exemples d'utilisations
Et dans des langages comme JavaScript, les deux derniers points sont très important.
d) Premature Optimization
Cela rejoint ce que je disais sur le KISS, à savoir essayer de faire un code le plus compréhensible possible.
Car j'ai pu le constater, le nombre de bugs produits en voulant faire de l'optimisation de code et ou du coup en utilisant des sucre syntaxiques comme:
Et j'en ai corrigé du coup qui faisait ça:
if (aCondiction) aMethod();
Et j'en ai corrigé du coup qui faisait ça:
if (aCondiction) aMethod(); aSecondMethod();
En somme, on m'a toujours dit:
- Ne le fais pas !
- Et si tu un expert, ne le fais pas (tout de suite) !
e) Indescriptive Naming
Cela rejoint sur la compréhension du code (donc en quelque sorte, une extension du point précédent): le nommage de nos fonctions, méthodes, variables, classes.
Eviter autant que possible les abréviations et essayer de privilégier le nommage fonctionnel.
Car pour en avoir discuter (et avoir de long débats) autour de moi, un code avec un nommage explicite est une forme en soit de documentation, et donc nous pouvons ne plus mettre de documentation, sauf pour donner plus d'information sur
Eviter autant que possible les abréviations et essayer de privilégier le nommage fonctionnel.
Car pour en avoir discuter (et avoir de long débats) autour de moi, un code avec un nommage explicite est une forme en soit de documentation, et donc nous pouvons ne plus mettre de documentation, sauf pour donner plus d'information sur
- le pourquoi d'une méthode (normalement le nom de la méthode devrait être suffisant, mais bon)
- mais surtout de décrire les paramètres et le retour de la méthode
- pour donner un exemple de valeur
- pour (dans le cas de JavaScript par exemple), dire si un paramètre est optionel, peut avoir plusieurs types, ...
f) Duplication
Tout simplement éviter le WET
SOLID
Ici, nous avons une compilation de bonnes pratiques à appliquer:
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
a) Single Responsibility Principle
Ici, le but est de bien découper son code en faisant du code qui a du sens fonctionnellement parlant.
C'est à dire déjà de séparer le code qui représente des modèles, puis ceux pour les services utilitaires, les services business, les services RESTs, ...
Bref, essayer d'éviter le "God Pattern" avec des fichiers de plusieurs milliers de lignes.
Par exemple, dans le monde Java, cela va se jouer avec des interfaces / classes abstraites pour le côté extension, et sur le scope des variables / méthodes pour empêcher les modifications.
Autrement dit, par défaut, il est conseillé d'utiliser sur toutes les variables Java la propriété "private" afin d'en empêcher la modification (même si dans les faits, avec l'api d'introspection, nous pouvons le faire) et d'utiliser la propriété "final" sur les classes / méthodes afin d'empêcher les surcharges.
En gros, ici, il s'agit d'utiliser au maximum les interfaces afin de pouvoir substiture le type à n'importe quel moment.C'est à dire déjà de séparer le code qui représente des modèles, puis ceux pour les services utilitaires, les services business, les services RESTs, ...
Bref, essayer d'éviter le "God Pattern" avec des fichiers de plusieurs milliers de lignes.
b) Open / Closed Principle
Ici, le principe est de dire que notre programme puisse être étendu MAIS que nous ne puissions pas faire des modifications directes.Par exemple, dans le monde Java, cela va se jouer avec des interfaces / classes abstraites pour le côté extension, et sur le scope des variables / méthodes pour empêcher les modifications.
Autrement dit, par défaut, il est conseillé d'utiliser sur toutes les variables Java la propriété "private" afin d'en empêcher la modification (même si dans les faits, avec l'api d'introspection, nous pouvons le faire) et d'utiliser la propriété "final" sur les classes / méthodes afin d'empêcher les surcharges.
c) Liskov Substitution Principle
En gros, de plutôt se baser sur le contrat que sur l'implémentation.
Par exemple au lieu d'écrire:
private HashMap<String, String> myMap = new HashMap<String, String>();
Faire:
private Map<String, String> myMap = new HashMap<String, String>();
Cela permettant plus simple de changer l'implémentation si nécessaire:
private Map<String, String> myMap = new SortedhMap<String, String>();
Cela se retrouve également dans les paramètres d'entrées / de sortie de nos méthodes (ainsi que sur les Generics) où il faut privilégier au maximum les interfaces
d) Interface Segregation Principle
De même, il faut privilégier la conception de nos applications via les interfaces.Ces dernières permettent
- De limiter les méthodes / variables disponibles (boite blanche)
- De pouvoir proposer plusieurs implémentations et d'en changer
- De pouvoir tester plus facilement en proposant le mocking
e) Dependency Inversion Principle
Clairement se basant sur l'Interface Segregation Principle pour justement privilégier l'usage d'interfaces et non pas des implémentations, car toute modification de votre architecture est risqué si vous vous basez plus sur l'implémentationDans la façon d'écrire
Pascal case
En somme, la majuscule fait la séparation entre les motsEx: MyAwesomeClass
Camel case
équivalent au Pascal Case excepté la première lettreEx: myAwesomeClass
Snake case
En somme, nous utilisons "_" pour faire la séparation entre les motsEx: my_awesome_class
Dash case / Kebab case
En somme, nous utilisons "-" pour faire la séparation entre les mots
Ex: my-awesome-class
Ex: my-awesome-class
Dans la façon de transformer des données
Endomorphisme
Transformation signifiant que la donnée d'entrée et de sortie est du même type.
Ex:
const addNumber = val => val + 1; // input: number, output: number
Ex:
const addNumber = val => val + 1; // input: number, output: number
Isomorphisme
Transformation signifiant que la donnée d'entrée et de sortie ne sont pas du même type, mais sans perte de données. Autrement dit que la donnée d'entrée est utiliser pleinement et sans modification dans la donnée de sortie
Ex:
const objToArray= val => [a]; // input: number, output: array of number
Ex:
const objToArray= val => [a]; // input: number, output: array of number
Homomorphisme
Similaire à l'endomorphisme, à ceci prêt que nous sommes plus au niveau objet. En fait, un objet aura des méthodes. Et l'homomorphisme indique que toutes les méthodes de cette objet retournera le même type que l'objet.
Un exemple d'objet: les Promises ! Où la méthode .then, .catch, ... retourne une nouvelle Promise. C'est ce que nous appelons les Functors.
Ex:
const newArray = [1, 2, 3].map(addNumber); // output: [2, 3, 4]
Un exemple d'objet: les Promises ! Où la méthode .then, .catch, ... retourne une nouvelle Promise. C'est ce que nous appelons les Functors.
Ex:
const newArray = [1, 2, 3].map(addNumber); // output: [2, 3, 4]
Catamorphisme
Transformation signifiant que la donnée d'entrée et de sortie ne sont pas du même type, mais avec perte de données. Autrement dit, nous ne serions pas capable de retrouver la donnée d'entrée en nous basant sur la donnée de sortie
Ex:
const value = [1, 2, 3].reduce((sum, item) => sum + item, 0); // output: 6
Ex:
const value = [1, 2, 3].reduce((sum, item) => sum + item, 0); // output: 6
Commentaires
Enregistrer un commentaire