RSS

Archives d’Auteur: chrisbertrandprog

Le dilemme du XML

Le dilemme de la propriété d’un élément

Lorsqu’on construit un modèle de document xml, on est confronté à dilemme:
Cette propriété doit-elle être un attribut ou bien un sous-élément ?

La réponse à cette question est lourde de conséquences en cas d’erreur, car un changement de statut dans l’avenir obligera à modifier le modèle et posera de gros problèmes aux logiciels qui utilisent le format que l’on aura modifié. Le fonctionnement interne du logiciel devra être modifié, et il devra transcoder les documents entre l’ancien format et le nouveau.

Exemple d’un changement de modèle:

Dans l’élément « a », on a une propriété « value1 » que l’on écrit sous la forme simple d’un attribut:

<a value1="100" value2="200"/>

Plus tard on décide que cette propriété pourrait être plus complexe dans certains cas.
On la transforme donc en un sous-élément de « a »:

<a value2="200">
    <
value1>
        <
pixel>
            <
x>10</x>
            <
y>20</y>
        </
pixel>
    </value1>
</
a>

La propriété « value1 » ayant changé de structure, on doit donc transcoder tous les documents qui suivent ce modèle.

Palliatif

Il existe une astuce pour pallier à cet inconvénient, en gardant la propriété sous forme d’attribut mais en lui donnant une syntaxe plus complexe:

<a value1="pixel x=10 y=20" value2= "200">

L’ennui, c’est qu’on se retrouve avec une syntaxe particulière pour chaque attribut un peu complexe, ce qui est précisément l’inverse de ce qu’on recherche en xml: une syntaxe régulière et vérifiable par n’importe quel outil xml.

Et en XAML ?

Il est intéressant de noter que les concepteurs du langage XAML, le principal descripteur d’interface graphique utilisateur sous Windows, ont choisit de laisser le choix aux programmeurs qui écrivent un document XAML.

Voici un élément XAML représentant un contrôle graphique, TextBox, qui affiche et permet d’éditer un texte simple.
On peut exprimer le texte dans un attribut:

<TextBox Text="mon texte"/>

Ou bien dans un sous-élément XML:

<TextBox>
	<TextBox.Text>mon texte</TextBox.Text>
</TextBox>

En fait, pour l’élément TextBox, le Texte est son contenu principal, on peut donc aussi l’exprimer comme la valeur de l’élément XML:

<TextBox>mon texte</TextBox>

On peut dire que les concepteurs du XAML ont rendu leur langage assez souple pour faciliter la vie des utilisateurs (développeurs) et leur permettre d’écrire le code le plus court possible selon le cas.
Certains pourraient critiquer ce choix, qui assimile allègrement les concepts d’attribut, de sous-élément et de valeur. Mais, au fond, est-ce que ça n’est pas le signe qu’ici le Texte a tout simplement une seule signification, être la propriété principale de l’élément TextBox, et que si XML a plusieurs façons d’exprimer cela, c’est peut-être sa faute à lui ?
Je trouve que le XAML met bien en valeur les incohérences du XML, en s’y adaptant tant bien que mal.

Et ailleurs ?

Il existe des méta-langages textuels qui ne font pas de distinction entre propriétés simples et propriétés complexes.
Par exemple en JSON:

{ "a" : { "value1":"100", "value2":"200" } }

peut devenir, lorsqu’on choisit de rendre la propriété « value1 » plus complexe:

{ "a" : { "value1": { "Pixel" : { "x":"10", "y":20 } } , "value2":"200" } }

J’ai pu apprécier la différence lorsque j’ai un écrit un sérialiseur généraliste (UniversalSerializer).
En JSON, j’ai pu écrire un modèle de données en arbre très vite.
Alors qu’en XML, il m’a fallu cogiter longuement pour de nombreuses propriétés d’éléments, au risque de devoir modifier mon modèle plus tard, avec tous les inconvénients que ça suppose.

Le problème du XML

En fait, la syntaxe XML ne permet pas l’imbrication des balises.
Pour obtenir l’équivalent de ce que j’ai écrit plus haut en JSON, il faudrait que le XML permette ceci:

<a <value1><Pixel><x>10</x><y>20</y></value1> value2="200"/>

Ce qui bien entendu est strictement interdit en XML: un élément (une balise en fait) ne peut pas contenir une autre balise (c’est à dire un signe « <« ).

Dans cet exemple imaginaire et impossible, on voit que le vrai problème du XML, c’est justement qu’il propose deux façons d’exprimer la propriété d’un élément: attribut et sous-élément.
Cette particularité est non seulement inutile d’un point de vue structurel, mais elle rend le concept même du XML plutôt irrégulier. Un comble pour un langage qui se veut à la fois simple et très régulier.
Tout système régulier où une chose peut être exprimée de deux façons a forcément un problème.

Hors, les attributs et les sous-éléments sont justement la base du XML. Tout le reste de sa structure en découle ou s’y adapte.
La seule façon de résoudre le problème serait de supprimer les attributs, de permettre l’imbrication des balises, et, tant qu’on y est, de ne pas permettre de contenu/valeurs dans les éléments.
Toute structure se réduirait à un arbre.
Autant dire que ce serait tout simplement un autre langage que le XML.

Que faire ?

On peut très bien décider de continuer à utiliser un langage à syntaxe bancale, sans se poser de questions, par habitude ou par nécessité. Et continuer d’en subir les défauts: sémantique discutable, problème d’évolution des modèles, transcodage à revoir régulièrement, etc.

D’ailleurs on a de bonnes raisons de continuer: il existe une masse énorme de standards, de formats de fichiers, basés sur le XML, dans tous les domaines, et dans les principaux logiciels.
Et puis, les habitudes ont la vie dure. Et on peut se consoler en se disant qu’on a à notre disposition toute une infrastructure, des milliers de logiciels et de bibliothèques qui gèrent, éditent, vérifient, transcodent le code xml.

Ou bien on peut se mettre en quête d’autre chose, un méta-format de donnée plus intéressant.
Reste à savoir lequel, et s’il restera ou deviendra un standard sur lequel on peut miser.

Personnellement, j’ai beaucoup travaillé avec le XML, bien qu’avec une maitrise modeste de ses possibilités, et je sens que le passage à autre chose sera long et difficile (beaucoup de programmes à modifier, et d’habitudes à changer).

Par ailleurs, il existe un autre problème au XML, et en fait à tous les méta-langages textuels comme JSON, c’est leur incapacité à gérer efficacement de gros fichiers, surtout lorsqu’il faut y insérer ou y retirer des informations.
Beaucoup ont proposé des versions binaires de XML, aucune n’a l’air de devenir un standard reconnu.
D’ailleurs, à partir d’une certaine complexité et d’une certaine taille, les bases de données semblent incontournables.. et toujours aussi peu lisibles par le commun des mortels.

Publicités
 
Poster un commentaire

Publié par le 2017-05-08 dans Programmation, XML

 

Ne sérialisez pas des listes vides avec XmlSerializer

Lorsque vous sérialisez une collection/liste vide avec XmlSerializer, un élément xml est écrit.
Si vous voulez que rien ne soit sérialisé, vous pouvez utiliser ce truc: ajouter une fonction ShouldSerialize..().

Exemple:

public class MaClasse
{
    public List Éléments;
    public bool ShouldSerializeÉléments()
    { return Éléments != null && Éléments.Count > 0; }
}

Le nom de la fonction est « ShouldSerialize »+<le nom du champ ou propriété de la collection>

XmlSerializer détectera automatiquement cette fonction et lui demandera s’il doit sérialiser ou non le champ ou la propriété, ou non.

 
Poster un commentaire

Publié par le 2017-05-08 dans .Net, C#, Programmation

 

Dispose parallèle et efficace

Dans .NET, pour chaque classe utilisée en parallèle (par plusieurs tâches, ou « thread safe » pour les anglophones) et implémentant IDisposable, il faut péniblement implémenter une méthode « Dispose » fonctionnant en parallèle aussi.
Ici je propose une petite structure qui nous facilite la vie, et qui en plus a le mérite d’être efficace: rapide et petite.

Code de la structure:

internal struct DisposeParallèle
{
enum LibéréOuPas { PasEncoreSupprimé, DéjàSupprimé }

int déjàSuppriméOuPas;

internal void Dispose(Action MéthodeDeSuppression)
{
// "Interlocked" est bien plus rapide que "lock".

LibéréOuPas l = (LibéréOuPas)
System.Threading.Interlocked.CompareExchange(
ref this.déjàSuppriméOuPas,
(int)LibéréOuPas.DéjàSupprimé,
(int) LibéréOuPas.PasEncoreSupprimé);

// Ici, "déjàSuppriméOuPas" a toujours pour valeur DéjàSupprimé, et il n'en changera plus. 😉

if (l == LibéréOuPas.PasEncoreSupprimé)
MéthodeDeSuppression();
}
}

Remarques:

  1. On utilise « Interlocked », qui est bien plus rapide que « lock ».
    La preuve: http://www.drdobbs.com/windows/boosting-performance-with-atomic-operati/226900048
  2. On se contente de mémoriser un simple entier 32 bits, là où un « lock » aurait nécessité un objet et un booléen.
    J’aurais aimé ne mémoriser qu’un booléen, d’ailleurs, mais « Interlocked »
    ne propose pas de méthode « CompareExchange » sur un booléen.
  3. En tant que simple structure, on économise les ressources objets, et on n’a pas besoin d’appeler un constructeur.

Exemple d’utilisation:

internal class MaClasse : IDisposable
{
DisposeParallèle disposeParallèle;

void IDisposable.Dispose()
{
this.disposeParallèle.Dispose(this.suppression);
}

void suppression()
{
// votre code...
}

~MaClasse() // si nécessaire
{
((IDisposable)this).Dispose();
}
}

Ça reste assez simple à utiliser, avec un code bien découpé donc facile à maintenir.

 

Étiquettes : , , , , , , , ,

Dans Firefox, refaire apparaître l’annonce de mise à jour

Contexte

Depuis quelques années, Firefox propose une mise à jour
(semi-)automatique. Une petite fenêtre apparaît en bas à droite de
l’écran pour nous avertir qu’une nouvelle version du logiciel a été
publiée, et nous propose un lien facilitant cette mise à jour.

Malheureusement, ce message n’apparait qu’une seule fois, et reste
même très peu de temps présent sur l’écran.
Une fois cette occasion de cliquer partie, une mise à jour de Firefox
devient nettement plus difficile, obligeant en fait à télécharger
l’installeur à partir du site de Mozilla, à entrer le mot de passe
administrateur, etc.

Il nous faut donc un moyen de faire réapparaître la petite fenêtre
annonçant la mise à jour.

La solution

Une méthode assez simple consiste à modifier un paramètre dans Firefox:

  1. Dans la barre d’adresse de Firefox, entrez ce texte:
    about:config
    Puis validez.
  2. Confirmez l’accès à la zone des paramètres, si cela vous est
    demandé.
  3. En haut, dans la partie permettant d’effectuer des recherches
    dans les paramètres, entrez ce texte:
    app.update.lastUpdateTime.background-update-timer
  4. Faites un clic droit sur la seule ligne qui apparait dans la
    liste en dessous, qui porte le même nom,
    puis choisissez l’option Modifier.
  5. Remplacez le nombre par 0 (zéro).
  6. Redémarrez Firefox.
  7. Naviguez normalement avec Firefox.
    Au bout d’environ 1 minute, notre fameuse petite fenêtre indiquant la
    disponibilité d’une mise à jour apparait. À vous de cliquer assez vite
    sur le lien qu’elle contient !

Voila.

 
Poster un commentaire

Publié par le 2013-11-12 dans Astuces

 

Étiquettes : , ,

UniversalSerializer

[ Le code source se trouve dans l’article original en anglais et mis à jour, voir ici ]

Un sérialiseur universel pour .Net .

C’est quoi ?

UniversalSerializer est un sérialiseur pour .Net.
En d’autres termes, il transcode un arbre d’instances d’objets en un tableau d’octets, et vice versa.

En résumé

L’objectif de UniversalSerializer est d’être capable de sérialiser tout type sans effort.

  • Pas besoin d’ajouter des attributs ni des interfaces aux types (classes & structures).

  • Lorsqu’une instance de classe est référencée plusieurs fois, elle n’est sérialisée qu’une seule fois.

  • Les références circulaires sont autorisées.

  • Les mécanismes de sérialisation et de transcodage existant sont réutilisés.
    Actuellement: [Serializable], ISerializable, [ValueSerializer] et [TypeConverter].

  • Les types sans constructeur par défaut (sans paramètres) sont autorisés, si un constructeur paramétrique peut être exploité (c’est automatique).

  • Les classes ICollection non génériques peuvent être sérialisées si une méthode Add ou Insert peut être exploitée (c’est automatique).

Bien entendu, toutes les constructions ordinaires telles que les classes, structures, propriétés publiques, champs publics, énumérations, collections, dictionnaires, etc.. sont sérialisées par UniversalSerializer.

Lorsqu’un type n’est pas directement sérialisable, UniversalSerializer propose deux mécanismes:

  • ITypeContainers
    Nous pouvons englober le type (ou l’ensemble de types) qui pose problème dans une classe personnalisée qui gérera sa sérialisation et sa désérialisation.

  • Un ensemble de filtres
    Nous pouvons bloquer certains types, et demander au sérialiseur de stocker certains champs privés de la source.

Mon plus grand souhait est que les gens ajoutent de nouveaux Conteneurs et filtres dans le futur, et qu’ainsi peut-être un jour tous les types pourront être sérialisés.

Exemples d’utilisation

var data = new Window();
byte[] DataBytes = UniversalSerializer.Serialize(data);
var data2 = UniversalSerializer.Deserialize<Window>(DataBytes);

C’est aussi simple que ça !

Exemple pour WPF

Il existe une DLL spécialisée pour WPF, qui gère plus de types de WPF:

var data = new Window();
byte[] bytesData = UniversalSerializerWPF.SerializeWPF(data);
var data2 = UniversalSerializerWPF.DeserializeWPF<Window>(bytesData);

Exemple pour WinForms

Il existe une DLL spécialisée pour WinForms, qui gère plus de types de WinForms:

var data = new Form();
byte[] DataBytes = UniversalSerializerWinForm.SerializeWinForm(data);
var data2 = UniversalSerializerWinForm.DeserializeWinForm<Form>(DataBytes);

Pourquoi et comment est-ce fait ?

J’avais besoin d’un sérialiseur universel, capable de sérialiser n’importe quoi sans modification.

Mais les sérialiseurs existant posent des problèmes:

  • Certains nécessitent des attributs ou des interfaces spéciaux.
  • D’autres ne prennent pas en compte les champs.
  • Les références sont un vrai problème. En général, les instances sont dupliquées, et les références des objets désérialisés pointent vers différentes instances.
    Et les références circulaires ne sont jamais gérées.
  • Aucun ne peut gérer des types sans constructeur par défaut (sans paramètres), pour autant que je sache.
  • Certaines classes .Net particulières nécessitent plus d’attention mais sont scellées et donc ne peuvent pas être héritées avec des attributs de sérialisation.

Donc la solution nécessitait un ensemble de techniques et de mécanismes.
Nous allons passer en revue ces mécanismes dans les prochains chapitres.

Le code original

UniversalSerializer est construit à partir de fastBinaryJSON 1.3.7, de Mehdi Gholam, qui sérialise vers un format binaire inspiré par JSON.
UniversalSerializer y ajoute une certaine universalité en permettant de sérialiser n’importe quel type.
Notez que j’ai dû modifier fastBinaryJSON en profondeur, donc je ne peux plus me synchroniser avec le code source original de fastBinaryJSON. Par conséquent, UniversalSerializer n’est une extension de fastBinaryJSON et je ne maintiendrai pas de compatibilité dans le futur.

Sérialiser des classes sans constructeur par défaut

Dans .Net, les constructeurs de classes par défaut (sans paramètres) ne sont pas imposés. Mais presque tous les sérialiseurs en ont besoin.
Et des classes sans constructeur par défaut sont fréquentes dans la plateforme. Exemple: System.Windows.Controls.UIElementCollection.

La solution dans UniversalSerializer est de rechercher les autres constructeurs, et de trouver une correspondance entre leurs paramètres et des champs de la classe, même s’ils sont privés.

Par exemple, dans UIElementCollection, nous trouvons ce constructeur:

public UIElementCollection( UIElement visualParent, FrameworkElement logicalParent )

Et ces champs sont disponibles dans la même classe:

  • private readonly UIElement _visualParent;
  • private readonly FrameworkElement _logicalParent;

Les types sont les mêmes et leur nom très proches. Assez pour que UniversalSerializer essaie de créer une instance avec ces valeurs.
Et ça marche !

Sérialiser des classes ICollection non génériques

Pour d’obscures raisons, l’interface ICollection ne fournit pas de méthodes Add et Insert.
En d’autres termes, elle définit une collection en lecture-seule, au contraire de l’interface générique ICollection<T>.
Normalement, nous ne pourrions pas désérialiser une classe qui implémente ICollection mais pas ICollection<T>.

Heureusement, dans le monde réel, la quasi-totalité de ces classes possèdent une méthode Add ou Insert, au moins pour un usage interne.
UniversalSerializer détecte ces méthodes et les utilise pour désérialiser une instance de classe collection.

Réutiliser les mécanismes existant et les gérer à l’aide de ITypeContainers

Dans certains cas, il est plus efficace ou seulement possible d’utiliser les mécanismes de transcodage existant.

Lorsque j’ai essayé de sérialiser des contrôles WPF, j’ai découvert que:

  • [TypeConverter] permet de transcoder un objet vers des types facilement sérialisables (string, byte[ ], etc.).
    Exemple: System.Windows.Input.Cursor
  • [ValueSerializer] permet de transcoder à partir et vers un string.
    Exemple: System.Windows.Media.FontFamily
  • [Serializable] (et ISerializable) permet d’utiliser BinaryFormatter.
    Exemple: System.Uri

Si vous examinez FontFamily, vous vous apercevrez que la transcoder vers un string est bien plus facile que d’essayer de sauvegarder ses propriétés.
Et plus sûr, car modifier une propriété peut entraîner des conséquences imprévisibles sur des classes inconnues ou complexes.

Pour ces attributs, j’ai créé le mécanisme de ITypeContainer. Un conteneur remplace l’instance source par sa valeur transcodée, en général un string ou un byte[ ].
Un conteneur peut s’appliquer à un ensemble de types, par exemple tous les types affublés d’un certain attribut.

Des exemples et des détails suivent.

Les Filtres

Certains types nécessitent une gestion particulière, qui peut être faite par un ensemble de filtres.

Le filtre de validation de type

Ce filtre permet d’éviter à UniversalSerializer de sérialiser des types problématiques.

Par exemple, j’ai rencontré certaines classes qui utilisent System.IntPtr .
Sérialiser ce type mène droit à des problèmes vu que ses instances sont uniquement utilisées en interne dans les classes, même lorsqu’elles sont stockées dans des propriétés publiques.

Les filtres d’ajout de champs privés

Ce filtre ordonne au sérialiseur d’ajouter certains champs privés aux informations de sérialisation.

Par exemple, System.Windows.Controls.Panel a besoin de _uiElementCollection pour remplir sa propriété Children, car Children est en lecture-seule.
Grâce à ce filtre, la solution est simple. Et tous les types héritant de Panel, comme StackPanel, bénéficient du même filtre.

ForcedParametricConstructorTypes

Ce n’est pas un filtre mais une liste de types.
Lorsqu’un type est dans cette liste, UniversalSerializer ignore son constructeur par défaut (sans paramètres) et recherche un constructeur paramétrique.
Exemple: System.Windows.Forms.PropertyManager . Il est ici bien plus simple d’utiliser son constructeur paramétrique que d’écrire un ITypeContainer pour ce type.

Les références

Examinons ce code:

{ // Two references to the same object.
  var data = new TextBox[2];
  data[0] = new TextBox() { Text = "TextBox1" };
  data[1] = data[0]; // Same reference
  byte[] bytesData = UniversalSerializer.Serialize(data);
  var data2 = UniversalSerializer.Deserialize<TextBox[]>(bytesData);
  data2[0].Text = "New text"; // Affects the two references.
  bool sameReference = object.ReferenceEquals(data2[0], data2[1]);
}
  1. UniversalSerializer sérialise une seule instance du TextBox.
  2. Il désérialise ensuite seulement une seule instance de TextBox, et deux références pointant vers elle.
    Les preuves: sameReference est vrai, et Text est identique dans les deux références.

Des filtres et des ITypeContainer personnalisés

Étant donné que l’objectif majeur de UniversalSerializer est de permettre de sérialiser n’importe quel type, il est essentiel que nous partagions notre expérience.

Par conséquent, j’ai essayé de rendre la création de conteneurs et de filtres aussi facile que possible, pour vous permettre de les essayer dans tous les sens et de partager ensuite vos solutions.

Créer un ITypeContainer

Le but est de remplacer une instance problématique par une instance facile à sérialiser. Le plus souvent, le conteneur contiendra des types très simples (string, int, byte[ ], etc..).

Prenons un exemple:

/// <summary>
/// . No default (no-param) constructor.
/// . The only constructor has a parameter with no corresponding field.
/// . The field ATextBox has no public 'set' and is different type from constructor's parameter.
/// </summary>
public class MyStrangeClassNeedsACustomerContainer
{
    /// <summary>
    /// It is built from the constructor's parameter.
    /// Since its 'set' method is not public, it will not be serialized directly.
    /// </summary>
    public TextBox ATextBox { get; private set; }
    public MyStrangeClassNeedsACustomerContainer(int NumberAsTitle)
    {
        this.ATextBox = new TextBox() { Text = NumberAsTitle.ToString() };
    }
}

Comme noté dans son résumé, cette classe pose quelques problèmes au(x) sérialiseur(s).

Pour régler ce problème, nous créons un conteneur:

class ContainerForMyStrangeClass : UniversalSerializerLib.ITypeContainer
{
#region Here you add data to be serialized in place of the class instance
public int AnInteger; // We store the smallest, sufficient and necessary data.
#endregion Here you add data to be serialized in place of the class instance

public UniversalSerializerLib.ITypeContainer CreateNewContainer(object ContainedObject)
{
MyStrangeClassNeedsACustomerContainer sourceInstance = ContainedObject as MyStrangeClassNeedsACustomerContainer;
return new ContainerForMyStrangeClass() { AnInteger = int.Parse(sourceInstance.ATextBox.Text) };
}
public object Deserialize()
{
return new MyStrangeClassNeedsACustomerContainer(this.AnInteger);
}
public bool IsValidType(Type type)
{
return Tools.TypeIs(type, typeof(MyStrangeClassNeedsACustomerContainer));
}
public bool ApplyEvenIfThereIsANoParamConstructor
{
get { return false; }
}
public bool ApplyToStructures
{
get { return false; }
}
}

Un détail: toutes les méthodes se comportent comme des méthodes statiques (mais sans l’être officiellement), à l’exception de Deserialize().

Voyons-les plus en détail:

  • public int AnInteger

    Ça ne fait pas partie de l’interface ITypeContainer.
    C’est ici que nous stockerons les informations qui serviront plus tard à la désérialisation.

  • ITypeContainer CreateNewContainer(object ContainedObject)

    Utilisé durant la sérialisation.
    C’est un genre de constructeur pour une instance de ce conteneur. Le paramètre sera l’instance de la classe source à sérialiser.

  • object Deserialize()

    Utilisé durant la désérialisation.
    L’instance du conteneur produira une nouvelle instance, une copie de l’instance de la classe source, en utilisant notre champ AnInteger.

  • bool IsValidType(Type type)

    Utilisé durant la sérialisation.
    Renvoie vrai si le type hérite, ou est, du type source.
    C’est un filtre.
    Nous pouvons choisir d’accepter les types hérités ou non, d’accepter des types compatibles, etc..

  • bool ApplyEvenIfThereIsANoParamConstructor

    Utilisé durant la sérialisation.
    Renvoie vrai si ce conteneur s’applique aux types de classes possédant un constructeur par défaut (sans paramètres).
    Peut être utile avec des conteneurs très généraux.

  • bool ApplyToStructures

    Utilisé durant la sérialisation.
    Renvoie vrai si ce conteneur s’applique aux types structure, et pas seulement aux types classe.
    Peut être utile avec des conteneurs très généraux.

Les étapes sont:

  1. Le sérialiseur teste si le type source (MyStrangeClassNeedsACustomerContainer) est géré par ce conteneur.
    Notre classe de conteneur (ContainerForMyStrangeClass) réponds que oui, via IsValidType(), ApplyEvenIfThereIsANoParamConstructor et ApplyToStructures.
  2. Le sérialiseur construit une instance de notre conteneur, via CreateNewContainer().
    CreateNewContainer construits une instance et modifie son champ AnInteger.
  3. Le sérialiseur stocke (sérialise) ce conteneur à la place de l’instance source.
  4. Le sérialiseur retrouve (désérialise) l’instance du conteneur.
  5. Le désérialiseur appelle Deserialize() et obtient une copie de l’instance de la classe source.
    Deserialize() crée cette copie en utilisant son champ AnInteger.

Plus qu’a sérialiser:

/* This example needs a custom ITypeContainer.
Normally, this class can not be serialized (see details in its source).
But thanks to this container, we can serialize the class as a small data (an integer).
*/
var Params = new UniversalSerializerLib.CustomParameters();
Params.Containers = new UniversalSerializerLib.ITypeContainer[] {
new ContainerForMyStrangeClass()
};
var data = new MyStrangeClassNeedsACustomerContainer(123);
byte[] bytesData = UniversalSerializer.Serialize(data, Params);
var data2 = UniversalSerializer.Deserialize<MyStrangeClassNeedsACustomerContainer>(bytesData, Params);
bool ok = data2.ATextBox.Text == "123";

Comme vous pouvez le voir, cette implémentation est très facile.

La classe statique Tools nous offre de l’aide:

  • Type Tools.TypeIs(Type ObjectType, Type SearchedType)

    C’est l’équivalent du ‘is’ en C#, mais pour les Types.
    Par exemple, TypeIs((typeof(List<int>), typeof(List<>)) renvoie true.

  • Type DerivedType(Type ObjectType, Type SearchedType)

    Renvoie le type correspondant à SearchedType qui est hérité par OjectType.
    Par exemple, DerivedType(typeof(MyList), typeof(List<>)) renvoie typeof(List<int>) lorsque MyList est un

    MyList: List<int> { }.

  • FieldInfo FieldInfoFromName(Type t, string name)

    Renvoie le FiedInfo du champ nommé de ce type.
    Nous l’utiliserons dans le prochain chapitre.

Créer un ensemble de filtres

Notez que le mécanisme des filtres est complètement indépendant des ITypeContainers.
On peut les utiliser ensemble, ou séparément.

Prenons un exemple:

public class ThisClassNeedsFilters
{
public ShouldNotBeSerialized Useless;
private int Integer;
public string Value { get { return this.Integer.ToString(); } }
public ThisClassNeedsFilters()
{
}
public ThisClassNeedsFilters(int a)
{
this.Integer = a;
this.Useless = new ShouldNotBeSerialized();
}
}
public class ShouldNotBeSerialized
{
}

Cette classe (ThisClassNeedsFilters) pose quelques problèmes:

  • Elle contient un ShouldNotBeSerialized.
    Nous supposerons que la classe ShouldNotBeSerialized doit être évitée pour certaines raisons, je ne sais pas pourquoi, peut-être qu’elle est empoisonnée !
  • Le champ Integer n’est pas public et donc est ignoré par le(s) sérialiseur(s).
  • Même le nom du paramètre du constructeur est différent d’aucun champ ou propriété.
    De toute façon, le sérialiseur n’a pas besoin du constructeur, puisqu’il y a un constructeur par défaut.

Pour dépasser ces problèmes, nous écrivons un ensemble personnalisé de filtres:

/// <summary>
/// Tells the serializer to add some certain private fields to store the type.
/// </summary>
FieldInfo[] MyAdditionalPrivateFieldsAdder(Type t)
{
if (Tools.TypeIs(t, typeof(ThisClassNeedsFilters)))
return new FieldInfo[] { Tools.FieldInfoFromName(t, "Integer") };
return null;
}
/// <summary>
/// Returns 'false' if this type should not be serialized at all.
/// That will let the default value created by the constructor of its container class/structure.
/// </summary>
bool MyTypeSerializationValidator(Type t)
{
return ! Tools.TypeIs(t, typeof(ShouldNotBeSerialized));
}

Ils parlent d’eux-même:

  • FieldInfo[] MyAdditionalPrivateFieldsAdder(Type t)

    dit au sérialiseur d’ajouter un champ privé (Integer) à chaque instance source de ce type (ThisClassNeedsFilters).

  • bool MyTypeSerializationValidator(Type t)

    empêche le sérialiseur de stocker toute instance de ce type (ShouldNotBeSerialized).
    En conséquence, tout instance de ThisClassNeedsFilters ne fixera pas son champ Useless (il sera null après la désérialisation).

Maintenant on sérialise la classe:

/* This example needs custom filters.
Normally, this class can be serialized but with wrong fields.
Thanks to these filters, we can serialize the class appropriately.
*/
var Params = new UniversalSerializerLib.CustomParameters();
Params.FilterSets = new FilterSet[] {
new FilterSet() {
AdditionalPrivateFieldsAdder=MyAdditionalPrivateFieldsAdder,
TypeSerializationValidator=MyTypeSerializationValidator } };
var data = new ThisClassNeedsFilters(123);
byte[] bytesData = UniversalSerializer.Serialize(data, Params);
var data2 = UniversalSerializer.Deserialize<ThisClassNeedsFilters>(bytesData, Params);
bool ok = data2.Value == "123" && data2.Useless == null;

L’implémentation est même plus facile qu’avec ITypeContainer.

Le code source de UniversalSerializer

Toutes les sources sont en C#.

La solution a été créée avec Visual Studio 2012, mais elle doit être compatible avec VS 2010.
La compilation nécessite seulement .Net 4, aucune DLL tierce, le code source est complet.

Il y a 3 DLLs:

  • UniversalSerializer1.dll
    La DLL principale générale, avec peu de dépendances.
  • UniversalSerializerWPF1.dll
    Une DLL spécialisée pour WPF, qui gère plus de types de WPF.
  • UniversalSerializerWinForm1.dll
    Une DLL spécialisée pour WinForms, qui gère plus de types de WinForms.

De cette façon, votre application n’aura pas de dépendances inutiles.

Dans l’archive, vous trouverez ces solutions pour VS:

  • \UniversalSerializer Tester\UniversalSerializer Tester.sln
    Une application WPF avec de nombreux exemples.
  • \UniversalSerializerWinForm\UniversalSerializerWinForm.sln
    Une application WinForm avec quelques exemples.
  • \UniversalSerializerReleaseLibs\UniversalSerializerReleaseLibs.sln
    Cette solution génère la version Release des trois DLLs.

Quelques points important

  • Le processus de sérialisation/désérialisation  identifie les types par leur nom complet (grâce à type.AssemblyQualifiedName).
    Ce genre de nom dépend de la version de l’assemblage (assembly), alors faites attention aux problèmes de versions pour la plateforme et pour les DLLs !

  • Le format produit actuel est sujet à changements dans le futur.
    C’est pour cela que le nom de la DLL porte un numéro de version.
    Si vous sauvegardez le tableau d’octets dans un fichier, je vous suggère d’ajouter une information de version à votre arbre d’objets.

Le futur

Je voudrais améliorer certains aspects:

  • Ajouter de nouveaux ITypeContainers et filtres.
    Vous pouvez m’aider. Prévenez-moi lorsque des Types ne sont pas sérialisés correctement. Et merci de partager votre solution, telle que des conteneurs et des filtres.
  • Réduire la taille des données produites.
    J’ai besoin de créer un nouveau format binaire.
  • Accélérer le traitement.

Faciliter le travail des sérialiseurs

Cette expérience m’a enseigné quelles difficultés rencontrent les sérialiseurs avec certains types et comment on peut leur faciliter le travail lorsque nous créons une classe.

  1. Écrivez un constructeur par défaut (sans paramètres) lorsque c’est possible.
    Le sérialiseur reconstruira l’instance à partir des champs et des propriétés.
  2. Si vous ne pouvez pas écrire un constructeur par défaut (sans paramètres), écrivez un constructeur paramétrique dont les paramètres correspondent à des champs privés.
    Chaque paramètre doit être du même type que le champ, et leurs noms doivent être semblables. En fait les noms peuvent différer légèrement: Param -> param ou _param ou encore _Param.
  3. Implémentez un ValueSerializerAttribute ou un TypeConverterAttribute lorsqu’une instance peut être construite à partir de quelque chose d’aussi simple qu’un string.
    En particulier lorsque votre classe contient de nombreux champs et propriétés d’optimisation publics.
    Par exemple, FontFamily peut être construit à partir d’un simple string; peu importe s’il contient bien d’autres informations, elles peuvent toutes être retrouvées à partir de ce simple string.
    Je ne sais pas si beaucoup de sérialiseurs prennent ces attributs en compte, mais au moins UniversalSerializer le fait.
  4. Tous les champs et les propriétés d’optimisation devraient être privés, lorsque c’est possible. Voir plus haut.
  5. Lorsque vous créez une collection d’objets, implémentez IList.
      Parce que ICollection ne permet pas d’ajouter des éléments à une collection.
    Lorsque vous créez une collection générique, implémentez ICollection<>.
      Parce que IEnumerable<> ne permet pas d’ajouter des éléments à une collection.

Remerciements

Je remercie Mehdi Gholam pour son fastBinaryJSON.
J’ai appris des choses intéressantes en lisant son code.
Merci pour avoir partagé cela.

 
Poster un commentaire

Publié par le 2013-07-13 dans .Net, Programmation

 

Étiquettes : , , ,

Le respect du client… de l’API

Les API changent, évoluent, s’améliorent, et ne sont pas toujours compatibles entre leurs versions.

C’est ce qui fait qu’un logiciel vieux de deux ans ne fonctionne parfois plus correctement, souvent après une mise à jour du système d’exploitation.
Le problème est général à la programmation, donc il concerne tous les systèmes d’exploitation.

Au final, ce qui cause ces problèmes, particulièrement énervants pour l’utilisateur final, c’est avant-tout un choix.

Choix de la compatibilité entre versions d’une API

[ Ici, je parle de versions majeures des API, et non des mises à jour mineures ]

Pour clarifier un peu le problème, voici une liste des différents choix que j’ai pu rencontrer jusqu’à présent.

Aucune compatibilité

Ça peut paraître absurde à beaucoup, mais certains programmeurs ou architectes font le choix de ne tout simplement pas permettre à un logiciel prévu pour une version A d’une API de fonctionner avec la version B de la même API.

Schématiquement, ça ressemble à ceci:

Ici les différentes versions ne proposent aucune compatibilité.
C’est évidemment ce qui demande le moins de travail aux programmeurs.
Par contre, un logiciel basé sur une API A devra être livré avec la même version A de la bibliothèque de fonctions, ce qui peut représenter une grosse taille, du temps de téléchargement et des incompatibilités à l’exécution.

Redirection vers la version immédiatement suivante

Note: C’est un concept différent de la compatibilité descendante.

Schématiquement:

Ici, lorsqu’on crée une nouvelle version (B) de l’API, la 1ère version (A) est réécrite pour renvoyer vers la version suivante (B). Et ainsi de suite.

L’intérêt de cette technique, c’est qu’un logiciel écrit pour une très vieille version de l’API (A) peut utiliser l’API la plus récente (E), sous forme de sa bibliothèque de fonctions.
Cela évite le problème de l’Enfer des bibliothèques, c’est à dire essentiellement qu’on n’a pas besoin de livrer chaque logiciel avec ses bibliothèques associées.

Avantage supplémentaire: les anciennes API étant maintenues, par le biais des renvois vers des versions plus récentes, on trouve moins de bogues.

Du point de vue du travail, cela oblige, à chaque nouvelle version d’un API, de revoir la précédente pour remplacer ses fonctions obsolètes par des renvois vers la nouvelle.
Ça peut sembler représenter beaucoup de travail, mais en pratique ce n’est pas tellement le cas, car:

  1. En général une faible partie de l’API change d’une version à une autre.
    Les plus gros changements ont lieu dans l’implémentation des fonctions, ce qui en soi ne change rien à l’API.
  2. Il est classique que les fonctions et les concepts d’une nouvelle version d’une API englobent et surpassent ceux de l’ancienne version.
    Par exemple, une fonction pouvant calculer le sinus d’un nombre flottant sera étendue pour pouvoir calculer aussi le sinus d’un nombre imaginaire (pourquoi pas ?).
    Dans un tel cas, l’ancienne fonction peut tout simplement renvoyer vers la nouvelle, avec des paramètres par défaut. Car qui peut le plus peut le moins.

Ce système, bien que limité à des paires de versions, permet tout de même à la plus ancienne version (A) de renvoyer, par bonds successifs, vers la plus récente (E). Pour une somme de travail acceptable, ça reste d’un intérêt non négligeable.

Redirection vers la dernière version

Schématiquement:

L’avantage de ce principe, c’est que toutes les anciennes versions renvoient vers la dernière, ce qui les maintient le plus à jour possible, évitant les vieux bogues et utilisant les toutes dernières techniques.

Par exemple, si c’est l’API d’un système de fenêtrage, alors même une très vieille application utilisera la toute dernière bibliothèque et donc elle aura un affichage dernier cri, tout-à-fait compatible avec les applications les plus récentes, et elle sera bien intégrée dans le système.

L’inconvénient est bien sûr que le travail est de plus en long à chaque version.
De plus il faut avoir des programmeurs connaissant bien les plus anciennes versions de l’API, ce qui est de plus en plus rare au fil du temps car les plus jeunes gens sont formés sur les API les plus récentes.

Par contre, du point de vue de l’utilisateur final, c’est la meilleure solution.

Réutilisation d’une plus ancienne version

Schématiquement:

Ici, une nouvelle version renvoie vers une plus ancienne.
Étrangement, ce concept rétrograde est très présent dans les systèmes d’exploitation.

Il est d’ailleurs systématiquement utilisé dans cet autre concept: les plateformes.

Exemple de Windows (8)

Ce choix paraît justifié par la grande différence qui existe entre l’API de la nouvelle plateforme et celle de l’ancienne.
Toutefois, je ne peux pas m’empêcher de penser qu’il serait bien plus logique de choisir un des autres concepts de compatibilité.

Dans ce dernier exemple, DotNet aurait pu être créé à partir de zéro et les parties obsolètes de Win32 réécrites pour n’être plus qu’un renvoi vers l’API la plus récente.
Idem pour WinRT.
Bien entendu, il est clair que cela aurait demandé bien plus de travail et causé bien plus de difficultés.
Il aurait fallu réfléchir longuement et mettre au point de vrais nouveaux concepts, les expérimenter tout aussi longuement. Et ensuite il aurait fallu réécrire toute la partie de Win32 devant renvoyer vers WinRT (par exemple).

Ceci dit, le résultat aurait été d’un grand avantage: Windows 8 aurait été un système homogène, au lieu de proposer deux types d’interface graphique différents et séparés.

On peut aussi remarquer que pour Windows 8 Microsoft a dû réécrire de nombreux logiciels et panneaux de configuration pour WinRT, ce qui représente également du travail. D’un certaine façon, ils ont économisé du travail avec ce choix de sur-plateforme, mais en ont gaspillé avec la réécriture des applications.

Toutefois, il faut aussi prendre en compte le bénéfique renouvellement des applications. Une application réécrite l’est en tentant compte de nouveaux concepts et des dernières techniques (par nécessairement liés à la plateforme).

Conclusion

Il me semble clair qu’il n’existe aucune méthode parfaite, et qu’on doit faire des choix difficiles.

Toutefois si on a une vision d’une informatique pérenne, alors il existe au moins une façon de faciliter l’évolution d’une API: la créer la plus simple et de plus haut niveau d’abstraction que possible, pour éviter que ses fonctions et ses concepts ne soient vite périmés.

Il me semble nécessaire d’éviter les fonctions trop spécialisées.
Nous avons une culture de l’optimisation qui nous vient des temps anciens lorsque les ordinateurs étaient si peu puissants qu’il fallait compter chaque octet et chaque temps d’horloge.
Il faut donc désormais penser différemment, plus tournés vers l’avenir et donc vers des solutions plus universelles et plus pérennes, au moins pour les API.
Cela ne nous empêchera pas d’optimiser l’implémentation des fonctions. Après tout, l’API n’est qu’une façade, une porte d’accès.

 
Poster un commentaire

Publié par le 2012-11-06 dans Programmation

 

Étiquettes : , , , , ,

HTML 5 n’est pas une interface riche !

HTML 5 ne permet pas plus de créer des « interfaces riches » que des applications.

Depuis quelques temps, l’industrie informatique semble devenue follement attirées par HTML 5. On lit partout qu’il va permettre de créer de l’ « Internet riche » multi-plateformes. On en arriverait presque à croire qu’on va (enfin) pouvoir créer des applications portables, qu’on ne programmerait qu’en une seule version et qui tournerait non seulement sur tous les systèmes d’exploitation mais tout aussi bien dans les navigateurs Web.

Et chaque entreprise y va de son discours montrant à quel point elle se sent impliquée dans ce merveilleux mouvement, et comment ses produits vont même contribuer à son épanouissement.
Tout le monde il est beau, tout le monde il est gentil, sympa, fantastique. Quel jubilation !
Dommage que ça ne protège pas la couche d’ozone tant qu’on y est.  😉

Microsoft, lorsqu’il nous a montré les premières images de Windows 8, nous a abreuvé d’explications concernant sa nouvelle stratégie: à l’entendre, il misait dorénavant à fond sur l’HTML 5.
Adobe, lui aussi, nous parle de sa nouvelle stratégie de transformation vers l’HTML 5. Il est même prêt à abandonner son produit phare dans le domaine: Flash, et a publié un outil permettant de transformer les anciennes applications flash en HTML 5.
Apple nous dit pour sa part: « Apple ne supporte pas Flash parce qu’il est trop bogué. Chaque fois qu’un Mac plante, le plus souvent c’est à cause de Flash. Personne n’utilisera plus Flash. Le Monde est en train de se tourner vers HTML 5. » (Apple Town Hall Meeting, fin Janvier 2010).
Il semble qu’aucun éditeur de logiciels ne soit avare de belles promesses concernant l’HTML 5.

Seulement voila, il y a un hic:

HTML 5 n’est pas, et ne sera jamais, une plateforme d’applications

Les belles annonces des différents acteurs de l’informatique ont peu de rapport avec la réalité, du moins en substance.

Pour comprendre, il faut clarifier un peu les choses, c’est à dire séparer les faits techniques bruts et les discours commerciaux aussi grandiloquents qu’hypocrites et trompeurs.

Qu’est ce que le « Web riche » ?

Appelé aussi « Internet riche« .
Cette expression plutôt floue, en fait aussi mal définie que d’autres expressions commercialo-pseudo-techniques comme « Internet 2.0 », indique surtout une (relativement) nouvelle utilisation que l’on fait du Web.
[ Par Web, je me place du côté client, avec surtout HTML et Javascript. ]

Au fil du temps, le Web s’est un peu divisé en deux types de fonctionnements:

  1. Historiquement, il y a d’abord eu l’utilisation de documents textes, avec quelques ajouts simples comme des images, du son ou des vidéos.
  2. Puis petit-à-petit les sites Internet ont aussi voulu présenter et utiliser les informations d’une façon qui ressemble plus aux logiciels, notamment avec un contenu mis à jour sans changer de page.

Le problème pour passer d’un mode à l’autre était que les pages Web ne sont à la base que des documents textes statiques.
Ils sont comparables aux documents produits par Word ou par LibreOffice.

Malgré une légère évolution vers le « dynamisme » (les pages peuvent modifier leur apparence elles-mêmes), il restait difficile de faire correspondre les habitudes des utilisateurs, basées sur les interfaces graphiques et les applications, à la réalité textuelle du Web.

Imaginez que vous utilisez une messagerie en ligne pour lire votre courrier, et qu’à chaque arrivée de courrier la page se recharge entièrement, vous faisant perdre ce que vous étiez en train de faire.
Ça n’est évidemment pas pratique à utiliser, et il semble bien plus logique de réutiliser les principes classiques des applications et donc des interfaces graphiques. Au moins, une vraie application est construite autour de l’information qu’elle manipule (le courrier dans cet exemple), et non autour d’un texte qui changerait de forme selon l’arrivée du courrier.

Alors on a progressivement utilisé le Web d’une façon pour laquelle il n’était pas fait, tentant de passer d’un document texte à une interface logicielle et graphique.
On a d’abord ajouté un langage de programmation, Javascript, puis quelques années plus tard on a écrit des bibliothèques de fonctions pour permettre aux pages de mettre à jour leur contenu sans devoir être rechargées, comme Ajax.

À ce stade, on a donc des pages Web partiellement dynamiques, c’est à dire dont le contenu peut se mettre à jour en fonction des évènements.
Ceci dit, on est très loin d’avoir l’équivalent d’une interface graphique d’application.

Qu’est-ce qu’une application, une interface graphique, et une plateforme d’applications  ?

Une application avec interface graphique, c’est tout simplement l’apparence des logiciels que vous utilisez tous les jours sur votre ordinateur, comme votre éditeur de textes, votre navigateur Internet, ou votre lecteur audio. Bref c’est la façon d’utiliser les ordinateurs depuis le milieu des années 1980 (pour le grand public, et depuis les années 1970 pour quelques universitaires chanceux).

Ce qui différencie les interfaces graphiques des précédentes interfaces textuelles, c’est:

  1. Tout se manipule à la souris, grâce à des contrôles graphiques réutilisables.
    Chaque fenêtre est en fait constituée de ces contrôles: menus, boutons, listes, barres de défilement, zones d’édition de texte, arbres, etc..
  2. Les applications sont homogènes.
    Toutes sont dans des fenêtres, s’utilisent de la même façon, et sont construites à partir des mêmes contrôles graphiques de base.
    Lorsqu’on sait en utiliser une, on sait toutes les utiliser.
  3. On peut facilement échanger des informations entre les applications.
    Typiquement: le copier-coller. Et aussi le glisser-déplacer.

Voici un exemple des contrôles graphiques principaux dans la plateforme WPF:

Bien entendu, il existe bien plus de contrôles disponibles, pour afficher et modifier un arbre d’informations (comme la liste de répertoires dans l’explorateur de fichiers par exemple), ajouter des barres d’outils, des graphiques en courbes, des objets en 3D, etc.

Maintenant, ce que j’appelle plateforme d’applications avec interface graphique, c’est tout simplement l’environnement qui permet de lancer des applications, de les utiliser.
J’utilise ce terme général de plateforme, car elles se présentent sous plusieurs formes, et font plus ou moins partie intégrante des systèmes d’exploitation.

Dans Windows, l’ensemble GDI, User et Explorer forment en quelque sorte la plateforme graphique de base originale.
Mais par-dessus celle-ci, on trouve aussi des (sur-) plateformes graphiques, comme WinForms, WPF, Metro, voire Qt, etc..

Dans Linux, on divise traditionnellement ça entre un système de fenêtrage (ex: X Window), un gestionnaire de fenêtres (ex: Metacity) et un environnement graphique (ex: Gnome, KDE), et il en existe de nombreuses variantes.

En quoi HTML 5 n’est-il pas une plateforme d’applications ?

On a vu qu’une telle plateforme obéit à plusieurs règles générales assez simples.
La première repose sur des contrôles graphiques, qui doivent être assez nombreux pour servir à construire tout type d’application.

Bien trop peu de contrôles graphiques

Voyons comment créer une application avec HTML (5), par exemple un gestionnaire de fichiers tel que l’Explorateur de fichiers de Windows.
On crée un document, dont la forme générale sera celle d’une telle application: on voudrait placer:

  • en haut, une zone d’édition de texte pour entrer manuellement le nom d’un répertoire,
  • à gauche un contrôle graphique en arbre pour y placer les répertoires,
  • à droite un contrôle graphique de liste pour y placer les fichiers du répertoire sélectionné dans l’arbre,
  • tout en haut, un menu pour présenter des options plus détaillées, ou un ruban d’icônes si vous préférez ça.

Petit problème, HTML (5) ne dispose que de quelques rares contrôles graphiques, qui sont en fait adaptés à l’envoi de formulaires d’informations:

Exemple en HTML 4:
On a ici des zones d’édition de texte, une boîte à choix et des boutons.
C’est maigre.

Exemple en HTML 5:
Ici on trouve en plus une jauge, un éditeur d’heure et un choix de date par calendrier.
Ça reste maigre !

Comme vous le voyez, c’est très rudimentaire, et surtout les contrôles ont été choisis pour créer des formulaires, et non des applications.

Résultat, pour construire notre application en HTML, il nous manque donc tous les contrôles le plus évolués: menu, arbre, liste, ruban, pour ne mentionner que ceux que j’ai listé. En pratique, il en manque encore bien d’autres, y compris certains essentiels.

Des sur-plateformes pour le Web

Il existe des bibliothèques de fonctions qui sont écrites en Javascript/HTML, qui s’exécute donc entièrement dans le navigateur Internet, et qui tentent de constituer une plateforme avec interface graphique.
Par exemple, qooxdoo.
Il s’agit essentiellement de contrôles graphiques comblant les manques d’HTML, comme des arbres, des listes, des tableaux de données, des menus, des barres d’outils, etc.

C’est une excellente chose pour les programmeurs qui ont besoin de créer quelque chose d’approchant une application, et qui sont restreints au Javascript/HTML pour certaines raisons propres à leur projet.

Problèmes:

  • C’est très lent.
    Ce qui pose problème dans les téléphones portables et dans les tablettes de faible puissance (type iPad), par exemple.
  • Les applications ne communiquent pratiquement pas entre elles, ni avec celles du système d’exploitation.
    Juste un petit copier-coller de texte brut.
  • Aucune homogénéité entre les applications.
    Chaque sur-plateforme a sa propre apparence pour les contrôles graphiques.
    Plus grave encore: il est facile, et même encouragé, d’utiliser des thèmes graphiques personnalisés pour chaque application, ce qui les rend d’autant dissemblables.
  • Aucune homogénéité avec le système d’exploitation.
    L’apparence et la façon d’utiliser l’application seront assez différentes, perturbant ainsi l’utilisateur final.
    La raison est que les contrôles graphiques ne sont pas ceux du système.
  • Ça reste difficile à programmer.
    Javascript et Ajax sont nécessaires, et ils sont loin d’être aisés.
    Ils sont bien moins adaptés que C++ ou C#. Et je ne parle même pas de les comparer avec des techniques plus évoluées comme les liaisons automatiques sous WPF, par exemple.

De vraies plateformes d’application pour le Web

Il existe depuis assez longtemps de telles plateformes, complètes et performantes.
Ce sont notamment Java, Silverlight, et Flex/Flash.

À la différence des plateformes se plaçant au-dessus de Javascript/HTML, le code est exécuté de façon quasi-native, utilisant ainsi tout le potentiel du microprocesseur et de la machine en général.

Petit comparatif:

Javascript n’a pas directement accès aux fonctions du système d’exploitation.
D’où une énorme limitation de ses possibilités et une vitesse d’exécution très faible.
Java, Silverlight et Flex/Flash ont, eux, directement accès aux fonctions du système d’exploitation.
D’où une richesse et une vitesse d’exécution proches d’une application native.

On voit sur le graphique que ce type de plateforme fait le lien entre l’application Web et le système d’exploitation, sans passer par le « filtre » de JS/HTML/DOM.

Ce type de plateforme propose une interface graphique complète et un ou plusieurs langages de programmation performants (seul Flex/Flash ne propose vraiment qu’un langage).

Problème, pour la majorité, leur éditeur est en train de les abandonner au profit d’HTML 5 (ou Metro/WinRT pour Microsoft).
Seul Java reste activement développé.

Autre défaut, d’une façon générale, elles n’utilisent pas les contrôles graphiques natifs, c’est à dire qu’elles dessinent leurs propres contrôles, en essayant toutefois de ressembler à ceux du système d’exploitation.

Tout de même, ces plateformes sont bien plus évoluées que les dizaines d’éphémères sur-plateformes qui se placent au-dessus de Javascript/HTML.

En les abandonnant, leurs éditeurs condamnent les programmeurs, et toute l’industrie intéressée, à choisir entre la très mauvaise solution du Javascript/HTML et la encore plus mauvaise solution des petites sur-plateformes sur Javascript.

Le cas de Flex/Flash et de son transcodeur

Depuis un an ou deux, Adobe, son éditeur, semble vouloir abandonner Flex.
Pourtant Flex semblait une évolution intéressante de Flash, proposant enfin une plateforme d’applications.

Mais devant le succès médiocre de cette plateforme, Adobe préfère abandonner à la fois Flash et Flex, au profit d’HTML et parait même vouloir refaire sa vie avec les services aux utilisateurs finaux, peut-être inspiré par les bénéfices de Google, Facebook et autres.

Il est vrai que Flex avait pour (gros) défaut de ne proposer que le langage ActionScript aux développeurs, très proche du très peu professionnel Javascript.

Adobe a même publié un outil permettant aux infortunés programmeurs Flash de transformer leur oeuvres en HTML 5.
Petit problème, dont on parle peu, ce transcodeur ne permet pas de transformer des applications Flex à contrôles graphiques en Javascript/HTML. À vrai dire, comment l’aurait-il pu ?
Ce détail me semble hautement révélateur des limites d’HTML.
Si HTML avait eu une collection de contrôles graphiques, ce transcodeur aurait pu les utiliser. Eh bien non.

Le cas de Metro/WinRT

Metro/WinRT est la nouvelle plateforme de Microsoft, apparu avec Windows 8, et disponible uniquement dans ce système.
Elle peut aussi être utilisée comme plateforme Web, pour lancer une application à partir d’Internet Explorer.

Comme je l’ai dit en introduction, j’ai personnellement ressenti l’annonce par Microsoft de son revirement vers HTML 5 comme une hypocrisie, et même un leurre, lorsque comme tout le monde j’ai pu constater qu’en pratique HTML 5 ne leur sert que de fine peau au-dessus de leur nouvelle plateforme: Metro/WinRT.

Je dirais même que cette peau est comme un camouflage, ou un déguisement.
En pratique, loin d’être portable, une application développée pour Metro/WinRT, même s’exécutant dans HTML 5, ne fonctionnera que dans Internet Explorer et uniquement sous Windows 8.
En fait je soupçonne leur discours sur HTML 5 de n’être qu’un appel du pied aux programmeurs Web (qui pratiquent en général le HTML avec Javascript, et le PHP côté serveur). Dans ce but, on peut apparemment programmer une application WinRT en Javascript.
Je ne pourrai toutefois pas témoigner de cela, je n’userai pas mes nerfs à essayer de programmer avec ce langage conceptuellement nullissime de Javascript.

Les plateformes, ou systèmes d’exploitation, d’applications Web

Comment fonctionnent-ils ?

Dans le cas de WebOS, développé par Hewlett-Packard, on a un système d’exploitation complet destiné aux téléphones portables, et une plateforme d’applications qui est une sorte de navigateur Web. Les applications sont donc développées en Javascript dans un environnement HTML auquel s’ajoutent des bibliothèques du système d’exploitation.

Un échec prévisible déjà annoncé

Ce n’est pas de chance, ce système a déjà été abandonné, avant même d’avoir été intégré dans une machine.
Et cela après des années de développement, avec une équipe de plus de 500 personnes, et un coût probablement bien supérieur à un milliard d’Euros.
Selon divers avis internes, il semble qu’HP se soit finalement rendu à l’évidence: les applications développées en JS/HTML sont beaucoup plus lentes que leurs homologues en C++/Contrôles natifs. Étant donné que tout leur système était basé sur ce choix de JS/HTML, il ne leur restait plus qu’à l’abandonner.

Car déjà sur un ordinateur de bureau, les applications écrites en JS/HTML se traînent, alors je ne doute pas qu’elles devaient être carrément insupportables sur des téléphones dotés de processeurs plusieurs fois moins puissants.

Cet échec est à mon sens l’annonciateur du futur échec de ce mouvement de promotion des applications en HTML 5.

Un futur en forme de passé

Si les acteurs majeurs de l’informatique passent réellement à l’HTML 5 comme simili-plateforme d’applications, on va inévitablement se retrouver avec la situation que l’on connaissait à l’ère des interfaces textuelles comme MS-DOS ou Linux en mode console:

  1. Aucune homogénéité dans l’apparence et dans la façon d’utiliser les applications.
    Chacune étant basée sur son propre choix de sur-plateforme et de contrôles graphiques propriétaires.
  2. Une grande difficulté pour échanger des informations entre les applications.
    Un bête copier-coller de texte brut marchera, alors qu’il sera impossible de coller des informations plus complexes telles que des documents.
    Le glisser-déplacer ne fonctionnera pas, car il n’existe aucun mécanisme standard de ce genre en HTML, surtout pas avec des informations plus complexes que du texte.
  3. La création d’applications est difficile, lente et produit d’innombrables bogues.
    Autant dire que JS/HTML a la palme de la pire productivité, et l’application résultante a la palme de la pire qualité de service.
  4. La sécurité des application est très faible, voire inexistante.
    Hors le Web est le milieu le plus dangereux qui existe pour une application.
    Multipliez les facteurs aggravants, vous obtenez une situation explosive.

Et à ces défauts s’ajoutent les défauts propres au Javascript:

  • Sécurité générale du langage faible.
    C’est notamment dû au typage dynamique (en clair il n’existe pas de contrôle de typage).
    Ouvrez la console d’erreurs de votre navigateur Internet et baladez-vous dans plusieurs sites Internet, vous aurez la joie de voir qu’ils sont bourrés de bogues. À se demander comment ils peuvent encore afficher quelque chose.
  • Lenteur patente.
    Mettez à part les querelles et les débats sur les vitesses d’execution des divers langages de programmation, et contentez-vous de regarder la vitesse d’exécution d’une grosse simili-application en Javascript, vous aurez l’impression de revenir 10 ans en arrière (voire plus encore).

Globalement, tout ceci nous ramène 30 ans en arrière, et les premiers à en payer le prix, ce seront les utilisateur finaux, monsieur et madame tout-le-monde.
Ensuite suivra l’industrie du logiciel, par retour de bâton, puis toute l’industrie de l’informatique.
Qu’est-ce-qu’on va se marrer !

Ma conclusion

Si l’industrie du logiciel ne veut pas foncer tout droit vers un mur, elle doit impérativement cesser d’écouter les sirènes du HTML 5.

D’un point de vue technique, il me parait incontestable qu’HTML ne sera jamais autre chose qu’un format de document textuel.

D’ailleurs, lorsque Mozilla a voulu écrire une plate-forme au-dessus des systèmes d’exploitation, qui lui permettrait de n’écrire qu’une seule version de ses logiciels (dont FireFox) elle a créé un langage de description d’interface graphique, appelé XUL. Il est remarquable que la structure de ce langage n’ait rien de commun avec HTML.
La leçon à en tirer, c’est qu’HTML restera définitivement un simple langage décrivant des documents textes, et rien d’autre. Vouloir utiliser HTML pour créer des applications, c’est comme vouloir éplucher des patates avec un tournevis: on peut y arriver, mais il faudrait être idiot pour essayer.

Laissons HTML aux pages web, et encourageons les autres plateformes pour nos chères applications.

 
1 commentaire

Publié par le 2012-10-21 dans Programmation

 

Étiquettes : , , , , ,