HTML5 et API web : les quirks sans quirks‑mode
| Auteur | Message |
|---|---|
|
Les deux propriétés caller et callee qui étaient disponibles en JavaScript dans les années 2010 encore, sont totalement disparues depuis la généralisation du mode strict.
|
|
|
Avec TypeScript, mieux vaut toujours utiliser l’option --strict.
Des messages à propos des erreurs de typage que TypScript ne sait pas capter on été promis, ce sera pour plus tard, ce n’est pas oublié. TypeScript peut être utilisé avec plusieurs options. L’une est l’option --strict. Intuitivement on peut s’attendre à ce que plus la vérification est stricte, moins de choses vont passer le contrôle du typage. Mais ça peut être le contraire dans quelque cas. En voici un exemple : tsc est appelé avec ces options : Code :tsc \ On vérifie cet exemple, dont l’utilité pratique sera décrite plus tard. Code :/** @template {object} TIl y a un message d’erreur : TypeScript a écrit : Argument of type 'T' is not assignable to parameter of type 'object | null'. Pourtant le paramètre o qui est du type T qui dérive de object, ne peut pas être nul, d’après son type. Si on ajoute l’option --strict Code :tsc \ … il n’y a plus d’erreur, alors que le typage est plus stricte. Ça semble incohérent. Ça s’explique par le détail de l’option : Intro to the TSConfig Reference — Strict (typescriptlang.org), à laquelle on peut accéder depuis : tsc CLI Options (typescriptlang.org) Documentation a écrit : Turning this on is equivalent to enabling all of the strict mode family options, which are outlined below. Et dans ce plus loin, il est dit : Documentation a écrit : When set, TypeScript will check that the built-in methods of functions call, bind, and apply are invoked with correct argument for the underlying function La méthode create n’est pas mentionnée, mais peut‑être qu’on peut deviner que dans le premier cas, TypeScript ne fait pas d’inférence de type pour ne pas le contrôler mais que en contrôlant le reste il est alors dans une impasse. Même si l’explication n’est pas certaine, c’est un constat, avec l’option qui rend le typage plus stricte, il ne voit plus d’erreur là où il n’y en a effectivement pas, tandis qu’avec un contrôle moins stricte, il en voit une là où il n’y en a pas, ce qui contraire à l’intuition. On peut toujours valider l’exemple avec un contrôle moins stricte, si on le modifie ainsi : Code :/** @template {object} TLa différence est qu’il est confirmé que le paramètre o est du type object. Mais c’est redondant et alors étrange, parce que ça ne devrait pas être nécessaire. Personnellement, ça ne m’inspire pas confiance et trouve alors qu’il vaut mieux conseiller de toujours utiliser --strict. En marge, utiliser à la fois --strict et --strictNullChecks, est redondant, parce que le premier inclus le second. J’ai toujours ajouté le second parce que ça facilite le suppression du premier pour des testes, sans perdre le second, mais il n’est nécessaire de faire comme ça, --strict tout seul suffit. |
|
|
Un exemple d’erreur que TypeScript ne sait pas signaler.
Code :class C {Le if qui teste la négation de c.b, fait afficher faux quand la négation de c.b est vrai donc que c.b est faux et réciproquement avec l’autre branche. Mais si on retire le get de “ get b () { return false; } ”, ce qu’on appellera ici le cas erroné : Code :class C {b n’est plus une propriété mais une fonction qui n’est pas appelée, puisqu’on ne fait pas c.b() mais c.b. Le test n’a plus de sens, parce qu’il test la présence de la fonction. Elle existe toujours et alors le test fera toujours afficher que sa valeur est vraie, même quand elle renvoi faux (en fait, on se trompe sur le sens de ce qu’on écrit). Si on enlève la négation, c’est à dire qu’on a “ if (c.b) ” au lieu de “ if (!c.b) ” : Code :if (c.b) {Maintenant TypScript remarque l’erreur et mentionne que : TypeScript a écrit : This condition will always return true since this function is always defined. Did you mean to call it instead? Oui, comme tu dis TypeScript, on aurait dut appeler la fonction. Cette erreur est arrivée dans un cas réel, pas seulement dans cet exemple. Pourquoi ce type d’erreur est possible ? Parce que avec les classes, pour les fonctions qui n’ont pas de paramètres, on peut avoir soit une fonction qu’on appel sans paramètre soit un getter, c’est à dire un membre auquel on accède comme à une propriété (elle est en lecture seule s’il n’y a pas de setter associé). Une première chose serait de toujours utiliser un getter pour les fonctions des classes, quand ces fonctions n’ont pas de paramètre, ce qu’il faudrait toujours inspecter. Mais il y aussi un autre problème, c’est que TypeScript n’a pas vu l’erreur dans le cas de la condition avec une négation, mais a remarqué l’erreur avec la condition sans négation. Ce n’est pas le seul problème, il y aussi que la négation en JavaScript, c’est pas très lisible, elle repose sur un seul symbole tout petit qu’on peut facilement ne pas voir à la lecture. En Pascal par exemple, le test s’écrirait ainsi : Code :if not c.b then La présence de la négation est plus lisible, on la manque moins facilement. En marge, en Pascal et d’autres, on écrirait pas les types dans des commentaires, mais dans les opérations et les expressions, parce que les types y font partie du langage. Ce n’est pas disponible dans la syntaxe de JavaScript, mais on peut faire presque pareil, avec une fonction : Code :/** @param {boolean} bAvec le cas erroné, sans get, TypeScript remarque tout de suite une erreur, même en présence de la négation, parce qu’il remarque que le type du paramètre de la fonction not, ne correspond pas : TypeScript a écrit : Argument of type '() => boolean' is not assignable to parameter of type 'boolean'. Le type “ () => boolean ” signifie une fonction qui renvoie un booléen, même si TypeScript a omis que cette fonction a un paramètre. Parce que en effet, dans le cas erroné, on donne à la fonction not, une fonction en guise de paramètre, alors qu’elle y attend un booléen, et c’est flagrant pour TypeScript qui le remarque. La conclusion de ce cas, avec un important bémol plus loin, est que pour la négation de expressions booléennes, mieux vaut utiliser une fonction not qu’il faut définir parce qu’elle n’existe pas par défaut. Et aussi, il faut s’assurer de la constance avec les getters, c’est à dire toujours faire que les fonctions sans paramètres dans les classes, soient toujours des getters et parfois le sont parfois ne le sont pas. Attention à ne pas confondre les fonctions et les procédures, comme en JavaScript les deux s’appellent pareil (la différence est expliquée plus loin). Pour les fonctions en dehors des classes, ce n’est pas disponible, ces fonctions restent des fonctions, mais l’utilisation de la fonction not, devrait permettre de capter des erreurs. Le bémol est que TypeScript ne sait pas interpréter cette négation, qu’il voit comme une fonction quelconque, qu’il n’analyse que pour son type, pas pour ce qu’elle fait (pour ça, il faut des démonstrations, les types ne suffisent plus … cependant que l’air de rien, TypeScript fait un petit peu de démonstration sans en avoir l’air, mais bien trop peu pour tous les cas en général). Par exemple dans ce cas : Code :const element = document.getElementById('bouton');TypeScript sait que dans la quatrième ligne, element est du type HTMLButtonElement, parce que sinon la troisième ligne fait qu’on arrive pas à la quatrième. Mais si on réécrit l’exemple ainsi : Code :const element = document.getElementById('bouton');TypeScript va se plaindre d’une erreur à la quatrième ligne, à propos que le type Element n’a pas la propriété name, faute d’avoir sut interpréter not. Pour compléter la conclusion, dans les cas où la négation permet une inférence de type, il faut toujours utiliser la négation avec le point d’exclamation et pas la fonction not qui peut être toujours utilisée dans les autres cas. Ce bémol est ainsi, parce que la fonction not n’existe pas par défaut en JavaScript. La différence entre fonction et procédure, précédemment mentionnée, est que, le plus souvent, une procédure réalise une opération sans renvoyer de résultat, le type de son résultat est void, tandis que les vraie fonctions, renvoie un résultat qui n’est pas void. La terminologie de JavaScript ne rend pas claire cette distinction. Il existe des cas particulier avec des procédure qui renvoie un résultat, et comme les fonctions réalisent des opérations de calcul, ça peut favoriser encore la confusion. Une autre façon de les distinguer, est le nom qu’on leur donne le plus volontiers. Si ce nom est celui d’un verbe, alors c’est une procédure, si c’est un adjectif, un qualificatif ou une chose, alors c’est le nom d’une fonction. Certaines fonctions ont des paramètres, elles ne peuvent pas être des getters. Il ne faut jamais changer en getter, une procédure qui n’a pas de paramètre, justement pour bien souligner que c’est une procédure. En marge, les procédure sont plus difficiles à vérifier avec les types, c’est avec elles encore plus qu’avec les fonctions, que les preuves seraient nécessaires pour faire les choses correctement. Mais il ne faut pas oublier que à l’intérieur des fonctions, il y a souvent des opérations est que les preuves seraient utiles avec les fonctions aussi. Ce qui est dit là, c’est seulement que comme les procédures ne renvoient pas de résultat, il n’y a pas de contrôle de type sur le résultat de ce qu’elles font, et le contrôle de type, est en pratique le mieux qu’on puisse avoir, sans compter qu’à l’origine, ça n’existait même pas en JavaScript et que si ça a été apporté, ce n’est même pas directement intégré au langage (pas pour le moment), c’est obtenu avec des choses à côté, comme par exemple TypeScript. |
|
|
Pour savoir quand une erreur se produit sur une page, sans avoir besoin de garder la console du navigateur ouverte, parce qu’elle prend quand‑même beaucoup de place, à ajouter aux script principal :
Code :function onError (event) { alert('Alerte au bug'); }L’objet window reçoit en effet un événement error, quand une exception n’est pas traitée, alors on peut avoir un gestionnaire d’événement pour ça. Quand la boite d’alerte s’affiche, il n’y a qu’à ouvrir la console pour avoir des détails. |
