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

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:

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

  • 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.

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, ici, il s'agit d'utiliser au maximum les interfaces afin de pouvoir substiture le type à n'importe quel moment.

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

  1. De limiter les méthodes / variables disponibles (boite blanche)
  2. De pouvoir proposer plusieurs implémentations et d'en changer
  3. 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émentation

Dans la façon d'écrire

Pascal case

En somme, la majuscule fait la séparation entre les mots
Ex: MyAwesomeClass

Camel case

équivalent au Pascal Case excepté la première lettre
Ex: myAwesomeClass

Snake case

En somme, nous utilisons "_" pour faire la séparation entre les mots
Ex: my_awesome_class

Dash case / Kebab case

En somme, nous utilisons "-" pour faire la séparation entre les mots
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

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

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]

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

Commentaires

Posts les plus consultés de ce blog

ISO: liens & outils utiles

NodeJs et SSL: une petite analyse

Créer sa commande slack en quelques minutes