Guide de gestion des erreurs JavaScript

Les erreurs sont bonnes. L'auteur du document, dont nous publions la traduction aujourd'hui, se dit convaincu que cette idée est connue de tous. À première vue, les erreurs semblent effrayantes. Ils peuvent être accompagnés d'une sorte de perte. Une erreur commise en public nuit à l'autorité de celui qui l'a commise. Mais en faisant des erreurs, nous en tirons des leçons, ce qui signifie que la prochaine fois que nous nous retrouverons dans une situation où nous nous serions auparavant mal comportés, nous ferons tout ce qui est nécessaire.



Ci-dessus, nous avons parlé des erreurs que les gens font dans la vie quotidienne. Les erreurs de programmation sont autre chose. Les messages d'erreur nous aident à améliorer le code, ils nous permettent d'informer les utilisateurs de nos projets que quelque chose s'est mal passé, et éventuellement de dire aux utilisateurs comment se comporter afin que les erreurs ne se produisent plus.

Ce matériel de gestion des erreurs JavaScript est divisé en trois parties. Tout d'abord, nous allons donner un aperçu général du système de gestion des erreurs en JavaScript et parler des objets d'erreur. Après cela, nous recherchons la réponse à la question de savoir quoi faire avec les erreurs qui se produisent dans le code du serveur (en particulier, lors de l'utilisation du bundle Node.js + Express.js). Ensuite, nous discutons de la gestion des erreurs dans React.js. Les cadres qui seront examinés ici sont sélectionnés en raison de leur énorme popularité. Cependant, les principes de traitement des erreurs discutés ici sont universels, donc même si vous n'utilisez pas Express et React, vous pouvez facilement appliquer ce que vous avez appris aux outils avec lesquels vous travaillez.

Le code du projet de démonstration utilisé dans ce matériel se trouve dans ce référentiel.

1. Erreurs dans JavaScript et façons universelles de les utiliser


Si quelque chose s'est mal passé dans votre code, vous pouvez utiliser la construction suivante.

throw new Error('something went wrong') 

Pendant l'exécution de cette commande, une instance de l'objet Error sera créée et une exception sera générée (ou, comme on dit, "levée") avec cet objet. L'instruction throw peut lever des exceptions contenant des expressions arbitraires. Dans ce cas, l'exécution du script s'arrêtera si aucune mesure n'a été prise pour gérer l'erreur.

Les programmeurs JS débutants n'utilisent généralement pas l'instruction throw . Ils rencontrent généralement des exceptions levées par le langage d'exécution ou des bibliothèques tierces. Lorsque cela se produit, quelque chose comme une ReferenceError: fs is not defined pénètre dans la console ReferenceError: fs is not defined et l'exécution du programme s'arrête.

▍Erreur d'objet


Les instances de l'objet Error ont plusieurs propriétés que nous pouvons utiliser. La première propriété qui nous intéresse est le message . C'est là que la ligne obtient qui peut être passée au constructeur d'erreur comme argument. Par exemple, ce qui suit montre comment instancier l'objet Error et afficher dans la console la chaîne transmise par le constructeur en accédant à sa propriété de message .

 const myError = new Error('please improve your code') console.log(myError.message) // please improve your code 

La deuxième propriété de l'objet, très importante, est la trace de la pile d'erreurs. Il s'agit d'une propriété de stack . Pour ce faire, vous pouvez afficher la pile des appels (historique des erreurs), qui montre la séquence des opérations qui ont conduit au dysfonctionnement du programme. En particulier, cela nous permet de comprendre quel fichier contient le mauvais code et de voir quelle séquence d'appels de fonction a conduit à l'erreur. Voici un exemple de ce que vous pouvez voir en accédant à la propriété stack .

 Error: please improve your code at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3) 

Ici, en haut, il y a un message d'erreur, suivi d'une indication de la section de code dont l'exécution a provoqué l'erreur, puis décrit l'endroit d'où cette section ayant échoué a été appelée. Cela continue au «plus loin» par rapport au fragment de code d'erreur.

▍ Génération et gestion des erreurs


La création d'une instance de l'objet Error , c'est-à-dire l'exécution d'une commande de la forme new Error() , n'entraîne aucune conséquence particulière. Des choses intéressantes commencent à se produire après l'application de l'opérateur de throw , ce qui génère une erreur. Comme déjà mentionné, si une telle erreur n'est pas traitée, l'exécution du script s'arrêtera. Dans ce cas, cela ne fait aucune différence si l'opérateur throw été utilisé par le programmeur lui-même, si une erreur s'est produite dans une certaine bibliothèque ou dans le langage d'exécution (dans un navigateur ou dans Node.js). Parlons de divers scénarios de gestion des erreurs.

Tryconstruction essayer ... attraper


Le try...catch est le moyen le plus simple de gérer les erreurs souvent oubliées. Aujourd'hui, cependant, il est utilisé de manière beaucoup plus intensive qu'auparavant, car il peut être utilisé pour gérer les erreurs dans les constructions async/await .

Ce bloc peut être utilisé pour gérer les erreurs qui se produisent dans le code synchrone. Prenons un exemple.

 const a = 5 try {   console.log(b) //  b   -   } catch (err) {   console.error(err) //          } console.log(a) //    ,    

Si dans cet exemple, nous n'avons pas console.log(b) commande ratée console.log(b) dans un try...catch , le script serait arrêté.

▍ enfin bloquer


Parfois, il arrive que du code doive être exécuté, qu'une erreur soit survenue ou non. Pour ce faire, vous pouvez utiliser le troisième bloc facultatif, finally dans la construction try...catch . Souvent, son utilisation est équivalente à du code qui vient immédiatement après try...catch , mais dans certaines situations, il peut être utile. Voici un exemple de son utilisation.

 const a = 5 try {   console.log(b) //  b   -   } catch (err) {   console.error(err) //          } finally {   console.log(a) //        } 

MechanismsMécanismes asynchrones - rappels


Lors de la programmation en JavaScript, vous devez toujours faire attention aux sections de code qui s'exécutent de manière asynchrone. Si vous disposez d'une fonction asynchrone et qu'une erreur s'y produit, le script continuera de s'exécuter. Lorsque des mécanismes asynchrones dans JS sont implémentés à l'aide de rappels (d'ailleurs, ce n'est pas recommandé), le rappel correspondant (fonction de rappel) reçoit généralement deux paramètres. C'est quelque chose comme le paramètre err , qui peut contenir une erreur et le result - avec les résultats d'une opération asynchrone. Cela ressemble à ceci:

 myAsyncFunc(someInput, (err, result) => {   if(err) return console.error(err) //           console.log(result) }) 

Si une erreur se produit dans le rappel, elle y est visible en tant que paramètre err . Sinon, ce paramètre obtiendra la valeur undefined ou null . S'il s'avère que err quelque chose, il est important d'y répondre soit parce que dans notre exemple, en utilisant la commande return , soit en utilisant la if...else et en plaçant des commandes dans le bloc else pour travailler avec le résultat de l'opération asynchrone. Il s'agit, en cas d'erreur, d'exclure la possibilité de travailler avec le résultat, le paramètre de result , qui dans ce cas peut être undefined . L'utilisation de cette valeur, si l'on suppose, par exemple, qu'elle contient un objet, peut elle-même provoquer une erreur. Disons que cela se produit lorsque vous essayez d'utiliser la construction result.data ou result.data similaire.

MechanismsMécanismes asynchrones - promesses


Pour effectuer des opérations asynchrones en JavaScript, il est préférable d'utiliser des promesses plutôt que des rappels. Ici, en plus d'une meilleure lisibilité du code, il existe des mécanismes de gestion des erreurs plus avancés. À savoir, vous n'avez pas besoin de vous soucier de l'objet d'erreur qui peut tomber dans la fonction de rappel lorsque vous utilisez des promesses. Ici, à cet effet, un bloc de catch spécial est fourni. Il intercepte toutes les erreurs qui se sont produites dans les promesses qui le précèdent, ou toutes les erreurs qui se sont produites dans le code après le catch précédent. Veuillez noter que si une erreur se produit dans la promesse qui n'a pas de bloc catch à traiter, cela n'arrêtera pas l'exécution du script, mais le message d'erreur ne sera pas particulièrement lisible.

 (node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */ 

Par conséquent, vous pouvez toujours recommander, lorsque vous travaillez avec des promesses, d'utiliser le catch . Jetez un oeil à un exemple.

 Promise.resolve(1)   .then(res => {       console.log(res) // 1       throw new Error('something went wrong')       return Promise.resolve(2)   })   .then(res => {       console.log(res) //        })   .catch(err => {       console.error(err) //  ,     ,         return Promise.resolve(3)   })   .then(res => {       console.log(res) // 3   })   .catch(err => {       //      ,      -        console.error(err)   }) 

MechanismsMécanismes asynchrones et essayez ... attraper


Après que la construction async/await apparue en JavaScript, nous sommes revenus à la manière classique de gérer les erreurs - pour try...catch...finally . La gestion des erreurs avec cette approche est très simple et pratique. Prenons un exemple.

 ;(async function() {   try {       await someFuncThatThrowsAnError()   } catch (err) {       console.error(err) //       }   console.log('Easy!') //   })() 

Avec cette approche, les erreurs dans le code asynchrone sont traitées de la même manière que dans le mode synchrone. Par conséquent, maintenant, si nécessaire, dans un seul catch vous pouvez gérer un plus large éventail d'erreurs.

2. Génération et traitement des erreurs dans le code du serveur


Maintenant que nous avons les outils pour travailler avec les erreurs, regardons ce que nous pouvons en faire dans des situations réelles. La génération et la gestion correcte des erreurs est un aspect essentiel de la programmation côté serveur. Il existe différentes approches pour traiter les erreurs. Ici, nous allons démontrer une approche utilisant notre propre constructeur pour les instances de l'objet Error et les codes d'erreur qui sont commodément transmis au front-end ou à tout mécanisme utilisant des API de serveur. La façon dont le backend d'un projet spécifique est structuré n'a pas vraiment d'importance, car avec n'importe quelle approche, vous pouvez utiliser les mêmes idées concernant le traitement des erreurs.

En tant que cadre serveur responsable du routage, nous utiliserons Express.js. Réfléchissons à la structure dont nous avons besoin pour organiser un système efficace de gestion des erreurs. Voici donc ce dont nous avons besoin:

  1. La gestion des erreurs universelle est un mécanisme de base adapté pour gérer les erreurs, au cours duquel un message comme Something went wrong, please try again or contact us , demandant à l'utilisateur d'essayer d'effectuer l'opération qui a échoué, à nouveau ou contactez le propriétaire du serveur. Ce système n'est pas particulièrement intelligent, mais au moins il est capable d'informer l'utilisateur que quelque chose s'est mal passé. Un tel message est bien meilleur que "téléchargement sans fin" ou quelque chose de similaire.
  2. Traitement d'erreurs spécifiques - un mécanisme qui vous permet d'informer l'utilisateur des informations détaillées sur les causes d'un comportement incorrect du système et de lui donner des conseils spécifiques sur la façon de traiter le problème. Par exemple, cela peut être lié à l'absence de certaines données importantes dans la demande que l'utilisateur envoie au serveur, ou qu'il existe déjà un certain enregistrement dans la base de données qu'il essaie d'ajouter à nouveau, etc.

▍ Développer votre propre constructeur d'objets d'erreur


Ici, nous allons utiliser la classe d' Error standard et l'étendre. L'utilisation de mécanismes d'héritage en JavaScript est risquée, mais dans ce cas, ces mécanismes sont très utiles. Pourquoi avons-nous besoin d'héritage? Le fait est que pour que nous puissions facilement déboguer le code, nous avons besoin d'informations sur la trace de la pile d'erreurs. En étendant la classe d' Error standard, nous avons la possibilité de tracer la pile sans effort supplémentaire. Nous ajoutons deux propriétés à notre propre objet d'erreur. La première est la propriété de code , accessible à l'aide d'une structure de la forme err.code . Le second est la propriété status . Il enregistrera le code d'état HTTP, qui devrait être transmis à la partie cliente de l'application.

Voici à quoi ressemble la classe CustomError , dont le code est conçu comme un module.

 class CustomError extends Error {   constructor(code = 'GENERIC', status = 500, ...params) {       super(...params)       if (Error.captureStackTrace) {           Error.captureStackTrace(this, CustomError)       }       this.code = code       this.status = status   } } module.exports = CustomError 

▍ Routage


Maintenant que notre objet d'erreur est prêt à l'emploi, nous devons configurer la structure de l'itinéraire. Comme mentionné ci-dessus, nous devons mettre en œuvre une approche unifiée de la gestion des erreurs, ce qui nous permet de gérer également les erreurs pour toutes les routes. Par défaut, le framework Express.js ne prend pas totalement en charge un tel schéma. Le fait est que toutes ses routes sont encapsulées.

Afin de faire face à ce problème, nous pouvons implémenter notre propre gestionnaire d'itinéraire et définir la logique de l'itinéraire sous la forme de fonctions ordinaires. Grâce à cette approche, si la fonction route (ou toute autre fonction) génère une erreur, elle tombera dans le gestionnaire de route, qui pourra ensuite la transmettre à la partie cliente de l'application. Si une erreur se produit sur le serveur, nous prévoyons de la transférer vers le frontal au format suivant, en supposant que l'API JSON sera utilisée pour cela:

 {   error: 'SOME_ERROR_CODE',   description: 'Something bad happened. Please try again or contact support.' } 

Si à ce stade ce qui se passe vous semble incompréhensible - ne vous inquiétez pas - continuez à lire, essayez de travailler avec ce qui est discuté, et progressivement vous le comprendrez. En fait, si nous parlons de formation en informatique, l'approche «descendante» est utilisée ici, lorsque les idées générales sont d'abord discutées, puis la transition vers les détails est effectuée.

Voici à quoi ressemble le code du gestionnaire d'itinéraire.

 const express = require('express') const router = express.Router() const CustomError = require('../CustomError') router.use(async (req, res) => {   try {       const route = require(`.${req.path}`)[req.method]       try {           const result = route(req) //    route           res.send(result) //   ,     route       } catch (err) {           /*                ,    route             */           if (err instanceof CustomError) {               /*                   -                                  */               return res.status(err.status).send({                   error: err.code,                   description: err.message,               })           } else {               console.error(err) //                  //   -                   return res.status(500).send({                   error: 'GENERIC',                   description: 'Something went wrong. Please try again or contact support.',               })           }       }   } catch (err) {       /*          ,    ,  ,            ,  ,          ,                       */       res.status(404).send({           error: 'NOT_FOUND',           description: 'The resource you tried to access does not exist.',       })   } }) module.exports = router 

Nous pensons que les commentaires dans le code l'expliquent assez bien. Nous espérons que leur lecture est plus pratique que les explications d'un tel code données après.

Jetez maintenant un œil au fichier d'itinéraire.

 const CustomError = require('../CustomError') const GET = req => {   //       return { name: 'Rio de Janeiro' } } const POST = req => {   //       throw new Error('Some unexpected error, may also be thrown by a library or the runtime.') } const DELETE = req => {   //  ,      throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.') } const PATCH = req => {   //      CustomError   try {       //   -        throw new Error('Some internal error')   } catch (err) {       console.error(err) //    ,           throw new CustomError(           'CITY_NOT_EDITABLE',           400,           'The city you are trying to edit is not editable.'       )   } } module.exports = {   GET,   POST,   DELETE,   PATCH, } 

Dans ces exemples, rien n'est fait avec les requêtes elles-mêmes. Il considère simplement différents scénarios de survenance d'erreurs. Ainsi, par exemple, la requête GET /city tombera dans la fonction const GET = req =>... , la POST /city tombera dans la fonction const POST = req =>... et ainsi de suite. Ce schéma fonctionne également lors de l'utilisation des paramètres de requête. Par exemple, pour une demande du formulaire GET /city?startsWith=R En général, il a été démontré ici que lors du traitement des erreurs, le frontend peut obtenir soit une erreur générale contenant uniquement une offre pour réessayer ou contacter le propriétaire du serveur, soit une erreur générée à l'aide du constructeur CustomError qui contient des informations détaillées sur le problème.
Les données d'erreur générales seront transmises à la partie client de l'application sous la forme suivante:

 {   error: 'GENERIC',   description: 'Something went wrong. Please try again or contact support.' } 

Le constructeur CustomError est utilisé comme ceci:

 throw new CustomError('MY_CODE', 400, 'Error description') 

Cela donne le code JSON suivant transmis au frontend:

 {   error: 'MY_CODE',   description: 'Error description' } 

Maintenant que nous avons minutieusement travaillé sur la partie serveur de l'application, les journaux d'erreurs inutiles ne tombent plus dans la partie client. Au lieu de cela, le client reçoit des informations utiles sur ce qui s'est mal passé.

N'oubliez pas que se trouve ici le référentiel avec le code considéré ici. Vous pouvez le télécharger, l'expérimenter et, si nécessaire, l'adapter aux besoins de votre projet.

3. Travailler avec des erreurs sur le client


Il est maintenant temps de décrire la troisième partie de notre système frontal de gestion des erreurs. Ici, il sera nécessaire, d'une part, de gérer les erreurs qui se produisent dans la partie cliente de l'application, et d'autre part, il sera nécessaire d'informer l'utilisateur des erreurs qui se produisent sur le serveur. Nous allons d'abord traiter de l'affichage des informations d'erreur du serveur. Comme déjà mentionné, la bibliothèque React sera utilisée dans cet exemple.

▍Enregistrer les informations d'erreur dans l'état de l'application


Comme toutes les autres données, les erreurs et les messages d'erreur peuvent changer, il est donc logique de les mettre dans l'état des composants. Lorsque le composant est monté, les données d'erreur sont réinitialisées. Par conséquent, lorsque l'utilisateur voit la page pour la première fois, aucun message d'erreur ne s'affiche.

La prochaine chose à traiter est que les erreurs du même type doivent être affichées dans le même style. Par analogie avec le serveur, on distingue ici 3 types d'erreurs.

  1. Erreurs globales - cette catégorie comprend les messages d'erreur généraux provenant du serveur ou des erreurs qui, par exemple, se produisent si l'utilisateur n'est pas connecté au système dans d'autres situations similaires.
  2. Erreurs spécifiques générées par le côté serveur de l'application - cela inclut les erreurs signalées par le serveur. Par exemple, une erreur similaire se produit si un utilisateur tente de se connecter et envoie un nom d'utilisateur et un mot de passe au serveur, et que le serveur l'informe que le mot de passe est incorrect. Ces éléments ne sont pas vérifiés dans la partie client de l'application, les messages sur ces erreurs doivent donc provenir du serveur.
  3. Erreurs spécifiques générées par la partie client de l'application. Un exemple d'une telle erreur est un message sur une adresse e-mail non valide entrée dans le champ correspondant.

Les erreurs des deuxième et troisième types sont très similaires, vous pouvez les utiliser en utilisant le magasin d'état des composants du même niveau. Leur principale différence est qu'ils proviennent de différentes sources. Ci-dessous, en analysant le code, nous verrons comment travailler avec eux.

Il utilisera le système intégré pour gérer l'état de l'application dans React, mais, si nécessaire, vous pouvez utiliser des solutions spécialisées pour gérer l'état - telles que MobX ou Redux.

▍ Erreurs globales


En règle générale, ces messages d'erreur sont stockés dans le composant de plus haut niveau avec un état. Ils sont affichés dans un élément d'interface utilisateur statique. Cela peut être une boîte rouge en haut de l'écran, une fenêtre modale ou autre chose. La mise en œuvre dépend du projet spécifique. Voici à quoi ressemble le message d'erreur.


Message d'erreur global

Jetez maintenant un œil au code qui est stocké dans le fichier Application.js .

 import React, { Component } from 'react' import GlobalError from './GlobalError' class Application extends Component {   constructor(props) {       super(props)       this.state = {           error: '',       }       this._resetError = this._resetError.bind(this)       this._setError = this._setError.bind(this)   }   render() {       return (           <div className="container">               <GlobalError error={this.state.error} resetError={this._resetError} />               <h1>Handling Errors</h1>           </div>       )   }   _resetError() {       this.setState({ error: '' })   }   _setError(newError) {       this.setState({ error: newError })   } } export default Application 

, , Application.js , . , .

GlobalError , x , . GlobalError ( GlobalError.js ).

 import React, { Component } from 'react' class GlobalError extends Component {   render() {       if (!this.props.error) return null       return (           <div               style={{                   position: 'fixed',                   top: 0,                   left: '50%',                   transform: 'translateX(-50%)',                   padding: 10,                   backgroundColor: '#ffcccc',                   boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',                   display: 'flex',                   alignItems: 'center',               }}           >               {this.props.error}                              <i                   className="material-icons"                   style={{ cursor: 'pointer' }}                   onClick={this.props.resetError}               >                   close               </font></i>           </div>       )   } } export default GlobalError 

if (!this.props.error) return null . , . . , , , . , , x , - , .

, , _setError Application.js . , , , , ( error: 'GENERIC' ). ( GenericErrorReq.js ).

 import React, { Component } from 'react' import axios from 'axios' class GenericErrorReq extends Component {   constructor(props) {       super(props)       this._callBackend = this._callBackend.bind(this)   }   render() {       return (           <div>               <button onClick={this._callBackend}>Click me to call the backend</button>           </div>       )   }   _callBackend() {       axios           .post('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               }           })   } } export default GenericErrorReq 

, . , , . . -, , -, UX-, , — .

▍ ,


, , , .




, . . . SpecificErrorReq.js .

 import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component {   constructor(props) {       super(props)       this.state = {           error: '',       }       this._callBackend = this._callBackend.bind(this)   }   render() {       return (           <div>               <button onClick={this._callBackend}>Delete your city</button>               <InlineError error={this.state.error} />           </div>       )   }   _callBackend() {       this.setState({           error: '',       })       axios           .delete('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               } else {                   this.setState({                       error: err.response.data.description,                   })               }           })   } } export default SpecificErrorRequest 

, , , x . , , . , , — , , , . , , . , , , — .

▍,


, , , . , , - . .


,

SpecificErrorFrontend.js , .

 import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component {   constructor(props) {       super(props)       this.state = {           error: '',           city: '',       }       this._callBackend = this._callBackend.bind(this)       this._changeCity = this._changeCity.bind(this)   }   render() {       return (           <div>               <input                   type="text"                   value={this.state.city}                   style={{ marginRight: 15 }}                   onChange={this._changeCity}               />               <button onClick={this._callBackend}>Delete your city</button>               <InlineError error={this.state.error} />           </div>       )   }   _changeCity(e) {       this.setState({           error: '',           city: e.target.value,       })   }   _validate() {       if (!this.state.city.length) throw new Error('Please provide a city name.')   }   _callBackend() {       this.setState({           error: '',       })       try {           this._validate()       } catch (err) {           return this.setState({ error: err.message })       }       axios           .delete('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               } else {                   this.setState({                       error: err.response.data.description,                   })               }           })   } } export default SpecificErrorRequest 


, , ( GENERIC ), , . , , , , , , , , . .

Résumé


Nous espérons que vous comprenez maintenant comment gérer les erreurs dans les applications Web. Quelque chose comme ça console.error(err)ne devrait être utilisé qu'à des fins de débogage, de telles choses oubliées par le programmeur ne devraient pas pénétrer dans la production. Simplifie la solution au problème de la journalisation à l'aide d'une bibliothèque appropriée comme loglevel .

Chers lecteurs! Comment gérez-vous les erreurs dans vos projets?

Source: https://habr.com/ru/post/fr431078/


All Articles