Les associations mentales tuent la productivité

Lorsque vous regardez un code, et que celui-ci n’est pas explicite ou utilise des noms non significatifs, vous devez retenir la signification de certain élément dans le but de comprendre la suite. Ou lorsque vous devez constamment vous remettre en contexte pour comprendre.

Par exemple lorsque vous lisez $object = new User(); vous devez vous créer une association mentale (objet signifie User) et la retenir pour être en mesure de comprendre la suite du code.

Si vous avez eu a la place $user = new User();, vous n’avez pas à vous référer à une autre ligne a un autre endroit dans le fichier ou retourner dans votre mémoire pour vous souvenir de l’association mentale. La variable est explicite et dit exactement ce qu’il y a l’intérieur. Résultat la compréhension du code s’en retrouve amélioré et facilité.

Une instance de la class User est déjà un concept abstrait puisqu’il n’est pas possible de savoir si c’est le User Roger ou Alphone.

Si vous avez une seule association, vous n’aurez probablement pas de problème à lire et comprendre. Imaginez, si vous devez faire référence à plusieurs associations, vous risquez d’avoir beaucoup de difficulté à lire et comprendre le code.

Les associations mentales avec les méthodes et fonctions

Lorsque vous définissez une fonction ou méthode qui comporte un nombre d’arguments très élevé plus de 2 ou 3, il devient difficile de se souvenir de la signification de ceux-ci sans avoir à se référer à sa définition lors de son utilisation.

CreateUser.php

1
2
3
4
5
$user = $userFactory->create(
   'Yanik', 'Lupien', 'Programmeur PHP', 
   35, '2010-10-12', '08:33', false, 3
);
$userRepo->persist($user);

Regardez, cet appel de méthode, et essayer de deviner la signification du 7e ou 8e argument.

Êtes-vous en mesure de savoir ?

Probablement pas.

Voici quelque chemin difficile possible pour y arriver.

  • lire la définition de la méthode en allant consulter le fichier où se trouve la définition de la classe.
  • Si vous avez de la chance que le programmeur ait correctement documenté la méthode pour que l’IDE soit en mesure de vous donner une aide contextuelle. Par exemple avec Eclipse vous pouvez avoir un accès rapide à la définition en gardant CRTL enfoncé et par un clique sur le nom de la méthode « create ».

Maintenant que vous avez trouvé le bon fichier et la bonne méthode. Vous avez maintenant la définition devant vous.

UserFactory.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class UserFactory {
...
/**
 * Create a new user base on this exhaustive list of arguments.
 * 
 * @param string $firstName 
 * @param string $lastName
 * @param string $description 
 * @param string $age 
 * @param string $createdDate 
 * @param string $createdTime 
 * @param boolean $active User account active or not
 * @param integer $countryId Country id where the user live
 * @param integer $stateId (Optional) State id where the user live
 * @param array $options (Optional) Some options. 
 * @return User
 */
function create(
    $firstName, $lastName, $description, $age, 
    $createdDate, $createdTime, 
    $active, $countryId, $stateId = null, $options = array()
) { 
    $user = new User();
    $user->setFirstName($firstName);
    $user->setLastName($lastName);
    $user->setDescription($description);
    $user->setAge($age);
    $user->setCreatedDate($createdDate);
    $user->setCreatedTime($createdTime);
    $user->setActive($active);
    $user->setCountryId($countryId);
    $user->setStateId($stateId);
    $user->setOptions($options);
    return $user;
};
...
}

Vous regardez la définition. Pour vous simplifier la vie, vous essayez de lire à partir du dernier argument. Non ça ne fonctionne pas très bien, car ceux si sont optionnel donc vous mettez donc à compter les arguments à partir du début. Finalement, êtes en mesure de savoir que l’argument #5 est en fait le statues actif ou non et que le 6e est le ID du pays.

Les problématiques

  • A la lecture du code qui utilise cette méthode il est très difficile de savoir au premier coup d’oeil quel sont les différents arguments de la function.
  • Il y a aussi une duplication de la documentation de chaque propriété de la classe User. Toutes les propriétés se retrouvent redéfinies et documentées une 2e fois dans la méthode create. Nécessite une double maintenance.
  • Et finalement vous avez besoin de vous souvenir par association mentale que l’argument 3 est la description, le 4 est l’age, le 5e est date, le 6e est heure et le 7e est le statues actif ou non.

Solutions

Réutilisez votre code, ici c’est la définition de la classe User qui peut être réutilisable. Plutôt que d’avoir une liste exhaustive d’arguments, utilisez l’objet lui-même.

CreateUser.php

1
2
3
4
5
6
7
8
9
10
11
$user = $userFactory->create();
$user->setId($this->getNewId());
$user->setFirstName('Yanik');
$user->setLastName('Lupien');
$user->setDescription('Programmeur PHP');
$user->setAge(99);
$user->setCreationDate('2010-10-12');
$user->setCreationTime('08:33');
$user->setActive(false);
$user->setCountryId(3);
$userRepo->persist($user);

Remarquez dans l’exemple précédent, pour comprendre chaque argument, vous n’avez plus à aller consulter la classe UserRepository. Vous n’avez plus non plus à compter ou vous souvenir de l’ordre. L’association est explicite. Le code se retrouve éclairci et beaucoup facile à comprendre.

UserFactory.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserFactory {
...
/**
 * Create a new user.
 * 
 * @return User
 */
function create() { 
    $user = new User();
    $user->setId($this->getNewId());
    return $user;
};
...
}

De plus, la classe UserFactory ne se retrouve plus avec un double de la définition de User.
Vous réduisez le couplage entre les 2 classes UserRepository et le User ce qui rend la maintenance beaucoup plus facile.

Il reste une dernière amélioration, et elle au niveau du setCountry. Je pourrais aussi parler au sujet setOptions qui utilise un array mais je crois que je vais faire un article complet sur le sujet.

CreateUser.php

8
9
10
...
$user->setCountryId(3);
...

Pouvez-vous me dire quelle est la signification de 3 ?

Probablement pas.

Ce qui m’apporte au sujet suivant.

Association mentale numérique

CreateUser.php

8
9
10
...
$user->setCountryId(3);
...

Le programmeur qui a bâti ce script sait probablement la signification de ce nombre. Par contre à nos yeux celui-ci ne signifie pas grand-chose. C’est peu’être… Canada, É.-U., France, Chine…

Une fois de plus nous devons chercher la source et consulter la signification de ce nombre.

Puisque le nombre de pays n’est pas si élevé et que celui-ci est généralement fixe, il nous est donc possible de définir des constantes pour rendre le code un peu plus explicite.

Country.php

1
2
3
4
5
6
7
class Country {
    const USA = 1;
    const CANADA = 2;
    const CHINA = 3;
    const JAPAN = 3;
    ...
}

Ce qui donne une lecture beaucoup plus explicite que la précédente.

8
9
10
...
$user->setCountryId(Country::CHINA);
...

Une fois de plus nous avons amélioré l’expérience de lecture pour nos collègues programmeurs.

Name space & Association mentale

Avec l’arrivé des names space dans PHP 5, plusieurs se sont lance dans leur utilisation. Encore une fois si nous le lecteur somme obliger de faire des associations mental pour comprendre le code, il est très probable que leur utilisation soit erronée.

Voici un exemple où un programmeur avait à la base une bonne intention, celle d’uniformité la structure de chaque entité de l’entreprise. Puisque chaque entité User, Site, Customer, … avait plusieurs classes telles que la classe Entite, l’interface Repository, la classe d’Exception, la classe Collection, … celui-ci s’est dit je vais créer un espace de nom propre a chaque entité.

Voici la structure des namespace et ses fichiers

namespace MyCompany\User;
Libs\MyCompany\User\Collection.php
Libs\MyCompany\User\Entity.php
Libs\MyCompany\User\Exception.php
Libs\MyCompany\User\Repository.php
...
namespace MyCompany\Site;
Libs\MyCompany\Site\Collection.php
Libs\MyCompany\Site\Entity.php
Libs\MyCompany\Site\Exception.php
Libs\MyCompany\Site\Repository.php
...
namespace MyCompany\Customer;
Libs\MyCompany\Customer\Collection.php
Libs\MyCompany\Customer\Entity.php
Libs\MyCompany\Customer\Exception.php
Libs\MyCompany\Customer\Repository.php

À première vue l’idée semble bonne. Tous les fichiers sont uniformisés. C’est bien organise et en ordre. Il n’y a pas de redondance dans le les noms. C’est propre et la structure de fichier est claire.

Maintenant voici la définition de la classe MyCompany\User\Entity et MyCompany\User\Repository

Lib\MyCompany\User\Entity.php

8
9
10
11
namespace MyCompany\User;
class Entity {
 
}

Lib\MyCompany\User\Repository.php

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace MyCompany\User;
interface Repository {
    /**
     * Find entity by unique id
     * @return Entity
     */
    function findById($id);
 
    /**
     * Find a entity by name
     * @return Collection
     */
    function findById($name);
 
    /**
     * Save an entity to repository
     * @param Entity $entity Entity to save in repository
     * @return void
     */
    function save(Entity $entity);
}

À première vue les classes semblent bien organise.

Voici les problématiques

  • Pour comprendre le fichier il est toujours nécessaire de faire référence a une association mentale et où de se remettre en contexte.
  • A la lecture du code de ces classes, il est facile de penser que ce sont des définitions abstraites puisque les noms Entite, Repository, … font référence à des concepts très abstraits que la spécialisation qui est décrit par ces fichiers. Il faut se référer à l’espace de nom pour comprendre la signification du context de celui çi.
  • les méthodes sont aussi documentées relativement à l’espace de nom. Par exemple, la méthode findByName plutôt que de retourner un UserCollection retourne une Collection encore une fois la notion de Collection fait référence à une notion très abstraite. Alors que dans les fait cette méthode retourne une UserCollection, une instance très spécialisée.
  • comme les noms de classes et interfaces, les noms de fichiers porte le même nom que ceux-ci et font donc aussi référence à un concept abstrait non spécialisé. Si vous ouvrez 4 fichiers Entity.php dans un IDE comme Eclipse, il devient très difficile de s’orienter et de savoir dans quelle spécialisation nous sommes.

Solutions

Dans le cas actuel, renommer les fichiers, les classe, les variables et les commentaires à leur spécialisation aiderait énormément la compréhension. Dans le cas actuel, il est vrai qu’il y aurait une redondance du nom dans la structure. L’avantage gagné au niveau de la lecture et compréhension du code compense largement cette petite redondance. Il y a toujours possibilité de changer la structure 😉

8
9
10
11
12
namespace MyCompany\Customer;
Libs\MyCompany\Customer\CustomerCollection.php
Libs\MyCompany\Customer\CustomerEntity.php
Libs\MyCompany\Customer\CustomerException.php
Libs\MyCompany\Customer\CustomerRepository.php

Lib\MyCompany\User\UserRepository.php

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace MyCompany\User;
interface UserRepository {
    /**
     * Find UserEntity by unique id
     * @return UserEntity
     */
    function findById($id);
 
    /**
     * Find a entity by name
     * @return UserCollection
     */
    function findById($name);
 
    /**
     * Save an UserEntity to current repository
     * @param UserEntity $user User to save in repository
     * @return void
     */
    function save(UserEntity $user);
}

Conclusion

Un collègue programmeur m’a envoyé une « Quote » intéressante, la voici :
« There is a cost associated to mental mapping… if you’re adding required mental mapping in your code, you are stealing from you employer ».

En tant que développeur professionnel nous nous devons d’écrire un code clair où il est facile de se retrouver et où il est facile de comprendre l’objectif de celui-ci. Un code clair est explicite, prévisible et surtout facile à lire et comprendre sans besoin d’utiliser d’associations mentales.

Références

books.google.ca / Clean Code: A Handbook of Agile Software Craftsmanship

Tags: , , , ,

Leave a Comment