“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” – Martin Fowler
Pourquoi prendre la peine et le temps d’écrire du « code clean » ?
Un code
propre facilite la lecture, l’évolution et la maintenance de celui-ci. Cela
passe par la mise en place de règles commune afin d’écrire du code que tout le
monde comprend, accélérant ainsi le développement et réduisant les risques d’erreur.
Sur le
long terme cela ne peut être que bénéfique pour vous, vos équipes et pour vos
projets.
Avant de commencer,
si vous vous débutez avec React, je vous conseille de suivre le guide rédigé
par Airbnb sur React/JSX (https://github.com/airbnb/javascript/tree/master/react).
Ce guide vous apprendra la majorité des conventions et normes actuellement
répandues sur le développement en React.
Nous
pouvons maintenant attaquer !
Vous trouverez dans cet article, 11 bonnes pratiques de développement. Certaines de ses règles sont basiques et évidentes si vous avez quelques années d’expérience ; cependant j’aime à penser que « Common Sense is Not Common Practice ». De plus, quelqu’unes de ces bonnes pratiques ne sont pas spécifiques à React et peuvent être appliquées dans tous types de projets.
1 – Utilisation d’un Linter :
Un Linter est
un outil qui analyse statiquement du code et vérifie que celui-ci respecte un
certain nombre de règles.
L’intérêt
est multiple :
- Vous êtes assuré de la constance du code, qu’il s’agisse de bonnes pratiques ou de considérations plus esthétiques : autant de points plus ou moins triviaux dont vous n’aurez plus à vous soucier directement.
- Vous êtes toujours à jour sans avoir à faire d’effort, les mises à jour du Linter prenant en considération les évolutions des bonnes pratiques de développement.
- En cas d’erreur de syntaxe dans votre code, l’analyse statique de ce dernier échouera, et l’erreur en question vous sera remontée : c’est un garde-fou supplémentaire
Cet outil
est très simple à mettre en place et assure une bonne qualité de votre
code. Un outil indispensable !
EsLint (https://eslint.org/), TsLint (https://palantir.github.io/tslint/)
2 – Un code propre devrait se comprendre
sans commentaire :
Il est
parfois nécessaire d’expliquer du code complexe par un commentaire, afin de
faciliter la vie du collègue qui passera après nous.
Cependant
l’excès de commentaire peut avoir l’effet totalement inverse. Premièrement,
cela va diminuer la visibilité du code, rendant la lecture de celui-ci plus
compliqué et plus longue.
Mais
surtout cela peut introduire de l’incompréhension et entrainer une perte de
temps importante. En effet, si les commentaires n’ont pas évolué pour refléter
les modifications du code, lors de correction de bug ou l’implémentation d’évolution,
ils deviennent contre-productifs, apportant des explications qui ne sont plus
vraies.
Pour
éviter ce problème, il est nécessaire de limiter au maximum le nombre de
commentaires. Et il est préférable de mettre en place d’autres règles comme une
convention de nommage.
3 – Convention de nommage:
Il est important d’établir avec son équipe une convention de nommage et de la respecter. Rédiger ensemble des règles sur le nommage des fonctions, des noms des variables permet une homogénéisation du code ; ce qui permet à toute l’équipe de gagner en rapidité de lecture et faciliter la compréhension du code dans sa globalité.
Voici
quelques règles communes (cette liste n’est pas exhaustive):
- Les variables du type Boolean et les fonctions retournant un Boolean doivent commencer par ‘is’,’has’ ou ‘should’ exemple :
|
const isComplete = current >= goal; const hasChildren = data.length>0; |
- Utilisez
l’extension .jsx pour les composants React. (.tsx dans un contexte TypeScript)
- Utilisez
PascalCase pour les noms de fichiers (par exemple, LoginComponent.jsx).
- Utilisez
le nom de fichier comme nom de composant.
- Les
événements React sont nommés à l’aide de camelCase et commencent par ‘on’ (par
exemple, onSubmitButtonClick).
- Les
gestionnaires d’événements doivent commencer par ‘handle’ (par exemple,
handleSubmitButtonClicked).
- Les
fonctions doivent être nommées pour ce qu’elles font, pas comment elles le
font. Parce que la façon dont vous réalisez le traitement peut évoluer au cours
du temps, et vous ne devriez pas avoir besoin de modifier toutes les références
de ces fonctions dans votre code à cause de cela.
4- Un code propre est ‘DRY’
DRY est un
acronyme qui signifie «Don’t Repeat
Yourself». Si vous faites la même chose à plusieurs endroits, consolidez
votre code et supprimez les duplications.
La
factorisation de votre code est très importante, car elle améliore grandement la
maintenabilité de celui-ci. En effet la duplication peut poser de sérieux
problèmes pour le projet, car celles-ci rendent les futures évolutions plus
risquées et peuvent causer des bugs très difficiles à identifier. Le plus grand
danger étant que le code dupliqué évolue de façon inconsistante.
Attention :
Sachez qu’il est possible d’aller trop loin dans la factorisation de votre
code. Rendant, celui-ci inutilement complexe afin de prendre en compte tous les
cas possibles, ce qui va alors à l’encontre de l’intention initiale.
5 – Diviser pour mieux
régner :
Utilisez
des petites fonctions, chacune ayant un rôle très précis. C’est ce qu’on
appelle le principe de responsabilité unique. Assurez-vous que chaque fonction
fait son travail et le fait bien.
La
division de vos fonctions les plus importantes en plusieurs fonctions plus
petites permettra leurs réutilisations et deviendra également beaucoup plus
facile à tester.
6 – Utiliser le
package classnames :
classnames est un excellent package pour générer des noms de classes de composants. En pratique, il existe de nombreux cas où différents styles doivent être appliqués à même composant. Pour éviter l’abondance de conditions dans votre code, qui alourdisse la lisibilité, vous pouvez préparer les noms de classe à l’aide de ce package.
|
// dirty return <button className={‘btn ’ + (isPressed? 'btn-pressed': (!isPressed && isHovered) ? 'btn-over':’’)}>{ label }</button>; |
|
// clean const btnClass = classnames('btn', { 'btn-pressed': isPressed, 'btn-over': !isPressed && isHovered }); return <button className={btnClass}>{label}</button>; |
7 – La déstructuration
ES6 a
introduit le concept de déstructuration. La déstructuration vous permet de
« séparer » les propriétés d’un objet ou des éléments d’un tableau.
Cela permet par exemple d’éviter la répétition très courante de this.props ou this.state avec React
|
//dirty return <GreatingsUser title={this.props.title} options={this.props.attributes} isUserLoginIn={this.state.isLoggedIn}/>; |
|
//clean const {title, attributes} = this.props; const {isLoggedIn} = this.state; return <GreatingsUser title={title} options={attributes} isUserLoginIn={isLoggedIn}/>; |
La déstructuration d’un tableau est un moyen beaucoup plus simple d’accéder à un élément du tableau et permet d’éviter d’accéder à un élément du tableau par sa position ou sa clé.
|
//dirty const splitLocale = locale.split('-'); const language = splitLocale[0]; const country = splitLocale[1]; |
|
//clean const [language, country] = locale.split('-'); |
8- Séparer la logique du rendu visuel
Ceci est
un principe très important dans React, il existe plusieurs noms pour parler de
ce pattern :
– Stateful vs Stateless
– Conteneur vs Composant de présentation
– Composants Smart vs Dumb
La
différence est que certains composants possèdent un ‘état’ (state) et les autres
sont sans ‘état’. Les composants stateful suivent l’évolution des données et
peuvent effectuer des traitements en fonctions de ces données (Smart), tandis
que les composants stateless affichent simplement ce qui leur est donné en
paramètre, ou restituent toujours la même chose (Dumb).
Le fait
de mélanger votre logique de chargement de données avec votre logique de rendu
(ou de présentation) va augmenter la taille et la complexité de vos composants.
Dans l’exemple ci-dessous, les données utilisateurs sont chargées puis affichées dans un seul composant.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
//dirty class User extends Component { state = { loading: true }; componentDidMount() { fetchUser(this.props.id).then(user => { this.setState({ loading: false, user }); }); } render() { const { loading, user } = this.state; return loading ? ( <div>Loading...</div> ) : ( <div> <div>First name: {user.firstName}</div> <div>First name: {user.lastName}</div> ... </div> ); } } |
La bonne
pratique est d’écrire un composant parent (Stateful) dont la seule
responsabilité est la gestion des données qui appellera ensuite des composants
enfants (Stateless) qui afficheront ces données ; ici RenderUser et
Loading.
De plus cette séparation permet la mise en place de la règle vue précédemment dans la section « Diviser pour mieux régner ». Permettant ainsi l’utilisation de composant plus petit avec moins de responsabilités ; qui seront alors plus simple à lire, à tester, à maintenir et à réutiliser.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
//clean import RenderUser from './RenderUser'; import Loading from './Loading'; class User extends Component { state = { loading: true }; componentDidMount() { fetchUser(this.props.id).then(user => { this.setState({ loading: false, user }); }); } render() { const { loading, user } = this.state; return loading ? <Loading /> : <RenderUser user={user} />; } } |
9 – L’utilisation
de conditions dans le JSX
false, null, undefined et true sont des enfants valides pour React. Cependant ils ne s’affichent simplement pas. Ce qui permet de s’en servir de condition pour restituer différents composants directement dans la fonction render de React.
|
render() { return ( <div> { !user && isFetching && <LoadingComponent /> } { user && !isFetching && <GreatingsUser username={user.name}/> } </div> ); } |
10 – Les conditions ternaires
Les conditions ternaires imbriquées ne sont généralement pas une bonne idée. Plus les conditions sont complexes plus la lecture est difficile. Il est déconseillé de sacrifier la lisibilité pour un simple gain de syntaxe et de place.
|
//dirty <div> {user ? user.isLoggedIn ? <GreatingsUser/> : user.hasEmail? <GreatingsEmail/> : <GreatingsGuest /> : null } </div> |
|
//clean <div> { ((user) => { if (user.isLoggedIn) { return <GreatingsUser />; } else if (user.hasEmail) { return <GreatingsEmail />; } else { return <GreatingsGuest />; } }) } </div> |
Fractionner vos composants autant que possible est toujours une bonne chose, cela permet la mise en place d’évolution ou correction simplement et rapidement.
11 – Use propTypes and defaultProps
Les
propTypes et defaultProps sont des propriétés statiques, déclarées aussi haut
que possible dans le code du composant. Elles devraient être immédiatement visibles par les autres
développeurs qui lisent le fichier, car elles servent de documentation pour
votre code.
Tous vos composants doivent avoir des propTypes dès
qu’il possède des props
propTypes permettent de déclarer les différents types des paramètres props fournit à votre composant. Si un des types n’est pas respecté, un avertissement dans la console de votre navigateur sera ajouté. C’est semblable un contrat d’api. Cela permet de prévenir d’erreur dans votre code et de spécifier les types attendus de vos variables.
|
import PropTypes from 'prop-types'; class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } Greeting.propTypes = { name: PropTypes.string }; |
Ici,
on déclare que le type attendu de la propriété name est une chaine de
caractère.
Toutes les propriétés doivent avoir un defaultProps
défini.
Comme son nom le laisse deviner defaultProps permet de déclarer la valeur par défaut d’une props. Cela permet d’éviter d’initialiser à la main dans votre code les props de votre composant et d’éviter les conditions pour s’assurer que toutes les props sont bien définies. Ainsi à l’initialisation de votre composant, les defaultProps définis seront fusionné avec vos props, évitant que vos propriétés aient pour valeur UNDEFINED.
|
class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } // Specifies the default values for props: Greeting.defaultProps = { name: 'Stranger' }; |
Ici, on définit pour la propriété name la valeur par défaut ‘Stranger’.