Dévelopement

Dessiner c’est programmer avec Ink To Code

Dot.Blog - dim, 22/04/2018 - 12:51

Un simple croquis qui devient code Xamarin ou UWP ? Oui c’est possible ! Comment ? …

De l’idée à la réalisation

Imaginez que vous venez d'être illuminé par l'inspiration pour votre prochaine application. Vous allez certainement commencer par prendre des notes et esquisser des écrans sur un bout de papier. Mais au lieu de vous saisir d’un carnet de croquis, d’aller noircir un tableau blanc ou même d’écrire sur le le dos d'une serviette de table, pensez plutôt à prendre votre ordinateur et à installer Ink to Code !

Ink to Code est un projet de Microsoft Garage. En quelque sorte c’est la version numérique du dos d'une serviette pour vos idées d'applications. C’est une application Windows 10 qui se lance en quelques secondes et fournit un canevas pour que vous puissiez dessiner des écrans avec votre stylet. Voyons cela de plus près !

Mise en place

La première chose à faire est d'installer Ink to Code à partir du Microsoft Store. Lors de son lancement vous serez accueilli par son interface utilisateur minimaliste et sa grande surface de dessin. Pour savoir quoi dessiner, cliquez sur le bouton Guide dans la barre d'outils. Ceci vous montre les différents éléments de conception que Ink to Code reconnaît :

Dessiner votre application


Imaginons que votre géniale inspiration vous guide vers la prochaine calculatrice de pourboire pour la marché américain. Voici comment vous pourriez la dessiner en quelques secondes avec Ink To Code :

***

Au fur et à mesure que vous dessinez chaque élément de conception, vous remarquerez que Ink to Code convertit les traits d'encre en composants reconnus. En tapotant sur chacun de ces éléments, un menu vous permet de les supprimer ou, s'ils ont été mal reconnus, de les convertir en différents composants. Vous remarquerez également qu'en appuyant sur le deuxième bouton de la barre d'outils de gauche, vous passez en mode contrainte, où vous pouvez dessiner des lignes de contrainte qui peuvent être utilisées pour aligner d'autres éléments.

Comment fonctionne cette magie ? En interne, Ink to Code utilise la puissance de la plate-forme Windows Ink et certaines de ses propres heuristiques pour reconnaître les éléments de conception communs dans vos esquisses d'application et les convertir ensuite en UWP XAML ou en XML de mise en page Android qui peuvent être chargés directement dans Visual Studio.

Exportation vers Visual Studio


Ink to Code est idéal pour esquisser rapidement les grandes lignes de votre idée d'application, mais pour commencer à l'affiner en prototype, vous voudrez exporter vos dessins vers Visual Studio. Pour cet exercice, nous allons construire une application Xamarin.Android :

Cliquez sur le bouton Exporter dans la barre d'outils Encre vers code et choisissez Android dans le menu. Ensuite, choisissez un répertoire temporaire dans lequel exporter le fichier XML de mise en page.

Ouvrez ensuite Visual Studio 2017 et créez un nouveau projet d'application Android vierge.

Si vous ne trouvez pas le modèle de projet, assurez-vous d'avoir Xamarin installé. Vous pouvez également utiliser Visual Studio pour Mac et vous assurer qu'Android est sélectionné pendant l'installation.

Trouvez “Main.axml“ dans votre projet Android sous le chemin “Ressources\layout” et remplacez-le par le fichier que vous avez exporté lors de la première étape.

Ink to Code utilise “ConstraintLayout” dans son XML Android exporté, vous devrez donc ajouter le paquet Nuget Android.Support.Constraint.Constraint.Layout à votre projet.

Cliquez sur Exécuter, et vous devriez voir l'écran que vous avez esquissé lancer dans un émulateur Android comme de véritables widgets Android interactifs !

Vous voudrez probablement continuer à affiner votre mise en page dans Visual Studio après l'exportation vers ce dernier. Pour ce faire, double-cliquez simplement sur le fichier Main.axml et le Xamarin Android Designer s'ouvrira, ce qui est la manœuvre usuelle qui n’a rien de particulier.

Libérez votre créativité !

Maintenant que vous avez une idée de la façon dont vous pouvez utiliser Ink to Code pour esquisser rapidement votre prochaines applications, il est temps de laisser libre cours à votre imagination ! Ink to Code est un projet de Microsoft Garage, ce qui signifie qu'il s'agit d'une expérience et que vos retours seront essentiels pour façonner son avenir. N’hésitez pas à faire des remontées à l’équipe.

Installez et Essayez Ink to Code, esquisser votre prochaine grande idée d'application et dites à l’équipe MS Garage ce que vous en pensez !

Conclusion

Ink to Code dévoile ce que sera la programmation dans 10 ou 20 ans. Adossée sur de l’IA, des systèmes de reconnaissance de formes, le développeur donnera ses idées à l’ordinateur sous la forme de croquis, de phrases. Le plus gros et le plus fastidieux sera alors fait par l’ordinateur lui-même et le développeur pourra alors se concentrer sur son vrai métier, affiner le code, le personnaliser, le rendre fonctionnel.

Je doute que M. Tout-le-monde devienne informaticien grâce à de telles avancées. Car l’informatique se complexifie tous les jours un peu plus et qu’il faut une tournure d’esprit, des connaissances et une expérience typiquement humaine pour faire un bon soft. Mais il est possible que pour les petites Apps sans grandes prétentions l’utilisateur soit capable de les fabriquer. Est-ce-que le fait que votre femme (ou mec) sache faire une omelette au champignon a signé la mort des grands chefs étoilés ? Non bien sûr.

Bon sketching et …

Stay Tuned !

Catégories: Dévelopement

Faut-il bouder les warnings ?

Dot.Blog - sam, 31/03/2018 - 22:59

Les warnings semblent moins intéresser que les erreurs, on le comprend, les unes sont bloquantes, les autres non. Mais certains warnings du compilateurs matérialisent des erreurs potentielles aux conséquences qu’il serait dangereux d’ignorer… Les warnings

Posons le tout de suite, les warnings sont des erreurs comme les autres. ils peuvent même être pires encore car une erreur empêche de compiler donc de délivrer le produit. Un warning n’interdit rien et c’est plus tard qu’une erreur potentiellement grave se matérialisera, une fois le logiciel livré et en production…

Tous les warnings ne véhiculent pas le même niveau de danger potentiel c’est vrai. Mais ils devraient être traités avec attention, non par rigidité psychologique mais tout simplement car ce sont des erreurs mais d’une nature un peu différente.

La programmation asynchrone est source de grandes joies mais aussi de surprises étonnantes. Sa maîtrise n’est pas aisée, raison de plus pour ne pas laisser traîner des warnings la concernant ! Prenons deux exemples de warnings concernant la programmation asynchrone pour illustrer le propos.Code CS1998

A part suggérer qu’il y a 1997 autres erreurs possibles – ce qui donne le vertige (mais la prochaine fera encore plus peur !) – ce warning ne vous dit-il rien ?

Le code CS1998 affiche le message suivant (en version US) :

warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

La cause est assez simple : vous avez spécifié le modificateur “async” dans l’entête de la méthode mais vous ne faites aucun usage de “await” dans cette dernière.

C’est un avertissement à prendre au sérieux. Soit vous avez placé cet “async” pour rien, et il faut le retirer… soit vous aviez prévu d’utiliser “await” et vous avez oublié de le faire ce qui risque de fausser “légèrement” le fonctionnement de la méthode. Situation à corriger immédiatement donc.Code CS4014

Le vertige devient digne de celui d’un Baumgartner avant de sauter de son ballon sonde à 39000 m d’altitude… imaginez 4012(*) autres warnings à découvrir ! :-)

(*) les plus futés auront noté l’erreur… j’aurai du dire 4013 n’est-ce pas ? Ceux qui suivent vraiment auront compris qu’il n’y a pas d’erreur, puisque je vous ai déjà fait découvrir la 1998, il en reste bien 4012 à connaître !

Le message de celui-ci est tout aussi important à reconnaître :

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

La cause : Vous appelez une méthode qui possède le modificateur “async” et qui retourne une Task<> et votre code ne fait rien pour attendre le résultat de cette dernière.

Peut-être est-ce volontaire. Le cas peut se produire (si le code qui suit l’appel ne se sert pas du résultat par exemple).

Si ce n’est pas intentionnel il est urgent de vérifier de le code appelant et le code appelé !

Si la situation est voulue, on peut supprimer le warning avec un #pragma. Cela est tout à fait acceptable car l’intention du développeur devient visible : il assume la situation. C’est mille fois préférable à un warning laissé en l’état. Exemple d’utilisation de pragma issu la doc Microsoft pour un autre warning :

On note que la suppression du warning doit s’accompagner d’une “remise en route” par un “restore” en spécifiant à nouveau le code du warning concerné. Cette double écriture est fastidieuse et surtout l’obligation de répéter le code du warning peut s’accompagner d’une faute de frappe masquant le warning pour tout le reste du code sans le savoir.

Une autre astuce consiste à tout simplement déclarer un variable qui ne sert à rien et lui affecter le résultat de l’appel, de type “var dummy = appelDeCodeAsync;”

Ne vous inquiétez pas pour cette variable supplémentaire, le compilateur est assez malin pour la supprimer totalement du code compilé. Son avantage est d’éviter un #pragma (et éviter de se tromper en répétant le code du warning). Comme cette solution ne coûte rien dans le code compilé elle est intéressante. Bien entendu elle sous-entend une parfaite compréhension du warning en question et ne doit pas servir à masquer une situation non maîtrisée ! Les puristes détesteront cette solution d’ailleurs…

Je préfère moi aussi le #pragma qui marque l’intention plus clairement d’une façon standardisée par le langage. Mais le risque de supprimer le warning dans tout le code (en cas d’erreur sur le second code) et de masquer des erreurs potentielles me chagrine tout autant.

A vous de voir…Treat Warnings as Errors

La solution pour éviter la paresse naturelle qui consiste à considérer les Warnings comme des citoyens de seconde zone c’est justement de les élever au niveau des erreurs !

Les warnings de compilation peuvent être traités comme des erreurs bloquantes, il suffit de le paramétrer dans la page build du projet :


Conclusion

La programmation asynchrone est pleine de surprise. Mais il n’y a pas qu’elle qui peut donner lieu à des warnings, ce n’était qu’un exemple parmi des centaines d’autres ! Certains développeurs laissent parfois des tartines de warnings dans leurs projets. Pourtant il faut les traiter avec la même vigilance que des erreurs de compilation car derrière chaque warning peut se cacher de potentiels bugs très sournois.

Le cas des warnings 1998 et 4014 est intéressant à ce titre.

Les warnings sont des erreurs comme les autres, mais de nature plus vicieuse, c’est un peu la leçon à retenir…


Stay Tuned !

Catégories: Dévelopement

Hériter d’une classe Sealed ?

Dot.Blog - dim, 25/03/2018 - 19:08

C’est un peu un piège, bien entendu, une classe “sealed” on ne peut en hériter... Pourtant le besoin existe. Par exemple une String différenciée. Comment contourner l’interdiction du Framework ? Est-ce possible ? Pourquoi ?

C’est la première question, et la plus importante peut-être. Pourquoi vouloir créer des types descendant de classes sealed ? En quoi cela peut-il être utile ?

Si je parle d’utilité c’est bien parce que le code doit répondre à cet impératif, tout code sans exception. On code pour faire quelque chose d’utile. Sinon coder n’a pas de sens.

Prenons la classe String. Le Framework ne permet pas de la création de classes en héritant et pour bloquer toute velléité en ce sens, la classe String est celée (sealed). Les concepteurs du Framework ont définitivement fermé cette porte. Mais ils en ont ouvert une autre : les extensions de classe. Cela permet d’étendre les possibilités de toute classe, même sealed, donc de string aussi.

Cela serait parfait si le besoin d’hériter d’une classe se limitait à vouloir lui ajouter des méthodes... Or ce n’est pas la seule motivation envisageable !

Prenons un cas concret : vous créez un logiciel qui pour autoriser la saisie de nombreux paramètres de classes différentes utilise une PropertyGrid (comme celle de Windows Forms qui peut s’utiliser sans problème sous WPF). Au sein d’un tel mécanisme vous pouvez généralement définir vos propres éditeurs personnalisés, qui dépendent du type de la valeur. Par exemple, pour une propriété de type Color vous pourrez écrire un éditeur offrant un nuancier Pantone et une “pipette”. Cela sera plus agréable à vos utilisateur que de taper à l’aveugle un code hexadécimal pour définir une couleur.

Imaginons une seconde que parmi ces paramètres qui seront saisis dans une PropertyGrid (ou son équivalent WPF ou UWP) il se trouve certaines chaines de caractères définissant par exemple le nom d’un fichier externe.

Dans un tel cas vous souhaitez qu’en plus d’un simple éditeur de string s’affiche aussi un petit bouton ellipse “...” qui permettra à l’utilisateur de browser les disques pour directement sélectionner un nom de fichier existant. Peut-être même la zone gèrera-t-elle le drag’n drop depuis l’explorateur.

Hélas... Soit vous enregistrez le nouvel éditeur pour le nom d’une propriété précise (ce qui est très contraignant et source de bogues), soit vous l’enregistrez pour son type, String, et dès lors ce seront toutes les strings qui bénéficieront du browser de fichiers, ce qui n’a aucun sens !

Que ne serait-il pas plus facile de définir juste “public class NomDeFichier : string {} “ et Hop ! l’affaire serait jouée !

L’éditeur serait enregistré pour le type “NomDeFichier”, les noms de fichiers dans les paramètres ne seraient plus de type “string” mais de type “NomDeFichier” et tout irait pour le mieux dans le meilleur des mondes.

Donc voici concrètement un cas qui montre l’utilité évidente de créer des classes héritant de string (ou d’autres classes sealed), même totalement vides, juste pour créer une CLASSification, à la base même de la programmation objet malgré tout...

Je ne doute pas qu’éclairez par cet exemple vous en trouviez d’autres, même totalement différents.

En tout cas nous avons répondu à la première question. C’est utile, et puis la programmation objet se base sur l’héritage pour régler de nombreux problèmes, il y a donc une légitimité naturelle à vouloir hériter d’une classe. “sealed” est un peu frustrant. C’est presque un contre-sens dans un monde objet. La justification du code plus efficace produit par une classe sealed me semble assez artificielle et ne se défendant que difficilement. Mais C# est ainsi fait, la perfection n’existe pas. Heureusement la grande souplesse du langage permet de contourner assez facilement ce genre de problème !Comment ?

Je vous l’ai déjà dit : ce n’est pas possible, n’insistez pas ! ...

Mais comme ce billet n’existerait pas si je n’avais pas une solution à vous proposer, vous vous dites qu’il doit y avoir un “truc”.

La classe string est sealed. Donc il n’y a pas de “truc” magique. Pas de moyen de bricoler le Framework non plus. Je vous ai dit que ce n’est pas possible !

Mais il y a une solution, un peu moins directe mais tout à fait raisonnable et “propre”.

Elle consiste tout simplement à développer une autre classe qui n’hérite de rien.

Hou là ! Réinventer le type string juste pour une raison de classification semble carrément overkilling !

C’est vrai, et nous ne nous lancerons pas sur une voie aussi complexe. En revanche on peut être rusé et tenter d’en écrire le moins possible tout en se faisant passer par une string...

En fait c’est assez facile mais cela utilise des éléments syntaxiques peu utilisés comme les opérateurs implicites.

L’astuce consiste à créer une classe “normale” n’héritant de rien, et possédant une seule propriété, Value, de type string (ou d’un autre type sealed dont on souhaiterait hériter).

C’est sûr que ce n’est pas compliqué à écrire mais cela ne règle pas la question. Il n’est pas possible de faire passer notre classe pour string. Partout il faudra changer ‘x = “toto”’ par ‘x.Value = “toto”’ et ce n’est pas du tout ce qu’on cherche !

C’est oublier les opérateurs “implicit” qui permettent de convertir une instance d’une classe en d’autres types (et réciproquement). Implicitement. C’est à dire sans avoir à écrire quoi que ce soit dans le code qui utilise la dite classe à convertir.

Pour commencer nous aurons ainsi un code qui ressemble à cela :public class MyString : IEquatable<MyString>, IConvertible
{
private string value;

public MyString() { }

public MyString(string value)
{
this.value = value;
}

public string Value
{
get { return value; }
set { this.value = value; }
}

public override string ToString() { return value; }

public static implicit operator MyString(string str)
{ return new MyString(str); }

public static implicit operator string(MyString myString)
{ return myString.value; } ...

Le type MyString déclare une propriété Value de type string, mais surtout elle déclare deux opérateurs implicites : l’un permettant de convertir une string en MyString, et l’autre s’occupant du sens inverse.

C’est presque tout. Ca marche. Je peux écrire ‘MyString x = “toto”’ et l’inverse aussi (affecter à une variable de type string directement une variable de type MyString).

Dans la réalité il faudra s’occuper d’autres détail, comme les opérateurs d’égalité par exemple, ou bien les conversions de type (interface IConvertible), etc.

Mais la majorité de ce code peut  être directement vampirisé de la classe string puisque la valeur Value est de ce type et que notre classe ne contient rien d’autre à convertir.

On en arrive à un code final de ce type (en supposant comme dans l’exposé plus haut qu’on souhaite avoir une string personnalisée pour saisir le nom d’un dictionnaire, Donc une DictionaryNameString) :public class DictionaryNameString : IEquatable<DictionaryNameString>, IConvertible
{
private string value;

public DictionaryNameString() { }

public DictionaryNameString(string value)
{
this.value = value;
}

public string Value
{
get { return value; }
set { this.value = value; }
}

public override string ToString() { return value; }

public static implicit operator DictionaryNameString(string str)
{
return new DictionaryNameString(str);
}

public static implicit operator string(DictionaryNameString dictionary)
{ return dictionary.value; }

public bool Equals(DictionaryNameString other)
{
if (ReferenceEquals(null, other)) return false;
return ReferenceEquals(this, other) || Equals(other.value, value);
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == typeof(DictionaryNameString) &&
Equals((DictionaryNameString)obj);
}

public override int GetHashCode()
{
return (value != null ? value.GetHashCode() : 0);
}

public static bool operator ==(DictionaryNameString left, DictionaryNameString right)
{ return Equals(left, right); }

public static bool operator !=(DictionaryNameString left, DictionaryNameString right)
{ return !Equals(left, right); }

#region IConvertible Members

public TypeCode GetTypeCode() { return TypeCode.String; }

public bool ToBoolean(IFormatProvider provider)
{ return Convert.ToBoolean(value, provider); }

public byte ToByte(IFormatProvider provider)
{ return Convert.ToByte(value, provider); }

public char ToChar(IFormatProvider provider)
{ return Convert.ToChar(value, provider); }

public DateTime ToDateTime(IFormatProvider provider)
{ return Convert.ToDateTime(value, provider); }

public decimal ToDecimal(IFormatProvider provider)
{ return Convert.ToDecimal(value, provider); }

public double ToDouble(IFormatProvider provider)
{ return Convert.ToDouble(value, provider); }

public short ToInt16(IFormatProvider provider)
{ return Convert.ToInt16(value, provider); }

public int ToInt32(IFormatProvider provider)
{ return Convert.ToInt32(value, provider); }

public long ToInt64(IFormatProvider provider)
{ return Convert.ToInt64(value, provider); }

public sbyte ToSByte(IFormatProvider provider)
{ return Convert.ToSByte(value, provider); }

public float ToSingle(IFormatProvider provider)
{ return Convert.ToSingle(value, provider); }

public string ToString(IFormatProvider provider)
{ return value; }

public object ToType(Type conversionType, IFormatProvider provider)
{ return Convert.ChangeType(value, conversionType, provider); }

public ushort ToUInt16(IFormatProvider provider)
{ return Convert.ToUInt16(value, provider); }

public uint ToUInt32(IFormatProvider provider)
{ return Convert.ToUInt32(value, provider); }

public ulong ToUInt64(IFormatProvider provider)
{ return Convert.ToUInt64(value, provider); }

#endregion
}

Et voici une classe “string” personnalisée, utilisable comme string et offrant globalement les mêmes services dans 99% des cas (affectations dans un sens ou dans l’autre, conversions).

Petit plus : notre classe n’est pas “sealed”... Il suffit de l’appeler “MyStringBase” et d’hériter ensuite de cette classe pour se créer des tas de types “string” personnalisés.

En dehors de l’exemple que je donnais, on peut imaginer de nombreux cas où faire un “if (variable is MySpecialString)...” pourra simplifier beaucoup les choses. Tout en conservant une écriture simple et limpide, un code propre et maintenable.Conclusion

C# est un langage fort subtil, il sait créer des blocages qui évitent les grosses boulettes (comme le fait qu’il n’autorise pas l’héritage multiple ou l’existence des classes sealed) mais en contrepartie il offre de nombreuses portes de sortie permettant de façon élégante de faire ce qu’on veut. Reste à bien le maîtriser pour y arriver, et ça c’est un sacré challenge, plus on travaille avec ce langage, plus on comprend qu’on est loin de tout en savoir !

Stay Tuned !

Catégories: Dévelopement

Générer convenablement des nombres aléatoires

Dot.Blog - sam, 24/03/2018 - 22:31

Les ordinateurs sont des machines déterministes, même si on peut parfois en douter ! Ils sont ainsi très mauvais au jeu des valeurs aléatoires, comme nous, mais pour d’autres raisons (trop de rigidité pour les premiers, trop de poids de l’inconscient pour les seconds). Peut-on se fier à Random ? Comment l’utiliser ? Peut-on mieux faire ? Nous allons voir cela …L’aléatoire

C’est une notion … complexe.

Il ne faut d’ailleurs pas confondre l’aléatoire et le chaos qui est un autre concept. En informatique ce qu’on cherche le plus souvent c’est la génération de nombres aléatoires sans trop se poser de questions… ce qui n’est pas malin !

Mais supposons que nous ayons un tel groupe de nombres aléatoires. Une fois cela en poche on peut tout aussi bien générer des couleurs aléatoires ou des chaînes de caractères ayant cette propriété et c’est ce qui intéresse l’informaticien quand il pense “nombre aléatoire”. En matière de programmation on pourrait ainsi affirmer “ici tout est nombre !” car on sait transformer les nombres en n’importe quoi, musique, image…l’inverse étant vrai (c’est même à cela qu’on doit l’essor du “digital”).

Beaucoup de définitions savantes ont été données au fil du temps à cette notion d’aléatoire.

Mais au final nous en sommes revenus…

Ce que nous appelons “aléatoire” est en réalité une forme de “complexité” puisque la prédiction n’est pas possible. Kolmogorov nous a expliqué ce qu’il entendait par la “complexité aléatoire” qu’il a réduit a sa plus simple expression, la taille du programme nécessaire pour reproduire un contenu, une information. Plus le programme est court et moins l’objet est complexe. Lorsque le programme le plus court consiste à simplement recopier la donnée cela indique que sa complexité est maximale et que rien ne permet de simplifier, de compresser l’information véhiculée.

Complexité et nombre aléatoires sont deux choses différentes, mais un nombre réellement aléatoire est par définition complexe car rien ne permet d’en prédire ou d’en calculer les chiffres. Il n’y a pas de programme plus court que celui qui consiste à le recopier.

D’ailleurs un “nombre aléatoire” qu’est-ce que cela veut dire ? Un nombre peut-il être “aléatoire” comme il peut être pair ou premier ? Non bien sûr, un “nombre aléatoire” est une forme d’abus de langage. C’est une série de nombres qui peut l’être. Un nombre tout seul ne peut être qualifié d’aléatoire. Il faut donc entendre par “nombre aléatoire” un élément d’une série aléatoire de nombres. La complexité se jugera ainsi sur la série et non sur un seul nombre. Mais on peut aussi considérer un nombre comme une série de chiffres et tenter de juger de sa complexité sous cet angle.

Les nombres peuvent parfois être très grands, voire infini, et même ne présenter aucune régularité tout en étant peu complexe ! Prenez le cas de Pi par exemple. Il présente une infinité de décimales, aucun schéma répétitif n’a pu être détecté bref ça semble être du lourd niveau complexité, et bien non.. Car face à l’infini de sa taille, on peut opposer un très court programme qui calcule Pi. Cela peut demander du temps, mais cela prouve que Pi n’est pas complexe puisqu’on peut le “compresser” en quelques lignes de programmation… Certes calculer les décimales va être long, mais cela ne change rien à la faible complexité de Pi. Oui l’infini c’est long, surtout vers la fin, mais ce n’est pas un critère de complexité.

En théorie de l’information on est même allé encore plus loin. Il ne s’agit même plus d’écrire un programme pour reproduire une donnée et d’en jauger la taille par rapport à celle-ci, il suffit d’utiliser … Zip !

La complexité aléatoire d’une information peut se mesurer ainsi à la taille du fichier produit par Zip (entendez un programme de compression réputé efficace peu importe son nom)…

Prenez deux cents pages A4 remplies d’espaces (ASCII 32), prenez 7Zip… le fichier final sera largement plus petit que le document original c’est une évidence (si vous en doutez, faites le test !). En revanche prenez le même document de 200 pages contenant toutes les décimales de Pi que vous pourrez y placer… le fichier compressé final ne sera pas plus petit que votre document original (un simple fichier texte sans artifice).

La nature aléatoire d’une donnée peut se mesurer à sa complexité interne, et cette complexité peut se calculer soit en écrivant un programme qui reproduit la donnée, soit tout simplement en essayant de zipper la donnée ! Pour être totalement précis la complexité de Komogorov est parfaitement définie mais elle n’est pas calculable (au sens mathématique, pas par ignorance). Mais ne nous écartons pas trop du fil aléatoire de cet article…

La nature aléatoire d’un nombre et sa complexité restent des notions différentes mais elles peuvent donc se rejoindre au moins pour juger de la qualité des données.

Il faut aussi savoir séparer la complexité aléatoire de la complexité organisée. Une information peut très bien avoir une complexité organisée élevée mais un complexité aléatoire faible ou l’inverse ou les deux ou aucune… L’exemple de Pi plus haut montre qu’il n’est pas désordonné, sa structure nous est cachée et nous apparait aléatoire mais il existe une organisation forte puisqu’on peut résoudre son infinité à un calcul de quelques lignes. Pi a une haute complexité organisée mais une faible complexité aléatoire (puisqu’on peut prédire avec précision la valeur de n’importe quelle décimale). C’est un fait troublant car en même temps la suite de chiffres de Pi est selon toute évidence aléatoire ! Mais rassurez-vous même pour les spécialistes la séparation entre complexité organisée et complexité aléatoire n’est pas forcément évidente à faire tout le temps.

Les générateurs de nombres aléatoires se “mesurent” d’ailleurs plutôt à l’aide d’outils statistiques. Car tout dépend de ce qu’on cherche, la complexité ou une certaine distribution ce qui n’a rien à voir bien entendu. Le générateur aléatoire le plus sobre qu’on puisse vouloir est celui où tous les nombres ont une équiprobabilité d’apparaître. La moyenne est idéalement de Max/2 avec une grande variance. Mais on peut aussi vouloir utiliser des “nombres aléatoires” suivant une distribution particulière comme celle d’une courbe de Gauss ou de n’importe quelle fonction.

La non prédictibilité et la complexité ne font pas bon ménage… Car plus on veut suivre une loi de distribution plus on introduit un biais fort dans le tirage qui est de moins en moins aléatoire… Par exemple des tirages aléatoires suivant une loi de répartition normale permettent avec certitude d’écarter de nombreuses valeurs (celles qui n’appartiennent pas à l’aire de la fonction).

Il y a de fait une confusion à éviter entre la complexité d’un tirage au sort et sa loi de distribution qui trahit certaines caractéristiques de l’ensemble ce qui rend les données prévisibles. Prévisible != Aléatoire…Ordinateurs et nombres aléatoires

Un ordinateur étant une machine déterministe (même si certains bogues peuvent parfois nous en faire douter !) l’aléatoire est totalement hors du champ de ses possibilités, quelle que soit sa taille, sa puissance, sa mémoire ou la présence d’un coprocesseur mathématique (dont on ne parle plus depuis quelques années puisque systématiquement intégré aux CPU ce qui ne fut pas le cas pendant longtemps).

Au mieux, il produira une suite de nombres “pseudo aléatoires” répondant à certains critères (c’est à dire simulant au plus proche certaines courbes ou lois comme celle de Poisson ou d’autres formes de distribution).

Les nombres aléatoires ne peuvent ainsi être générés qu’en s’appuyant sur des phénomènes physiques eux-mêmes réputés aléatoires comme les phénomènes quantiques. C’est pour cela que pour générer des vraies séries de nombres aléatoires sur un ordinateur il faut absolument utiliser un hardware spécifique. Il en existe de différentes nature selon le degré de précision dans l’aléatoire qu’on désire (s’il est possible de parler de précision ici). Ces boitiers qui peuvent se brancher sur un port USB par exemple, utilisent des phénomènes quantiques qu’ils amplifient et numérisent pour obtenir des nombres : bruit thermique d’une résistance par exemple.

Donc hors de ces hardwares, parler de nombres aléatoires avec un ordinateur est un abus de langage dans le meilleur des cas et une hérésie mathématique dans le pire….NET et Random

Tout le monde la connaît cette classe du framework, c’est le moyen le plus simple d’obtenir des nombres pseudo aléatoires sous .NET.

Mais de ce que j’ai toujours pu constater lorsque j’audite du code, cette classe est mal utilisée dans la grande majorité des cas. Cela était vrai il y a des années, cela reste vrai aujourd’hui. Tout comme la compréhension des bases de données rationnelles (Formes Normales, loi de Codd…) semble restée bloquée à l’ère de DBase II, la compréhension de Random n’a pas évoluée en 10 ans.

Il y a par exemples des erreurs classiques qui montrent cette méconnaissance des principes fondamentaux de Random.

La première consiste à vouloir initialiser l’instance avec l’heure courante comme pour augmenter la nature aléatoire des données qui seront générées. C’est une bévue. La classe Random utilise déjà l’horloge comme graine si on n’utilise son constructeur par défaut…

Inutile donc d’écrirevar r = new Random(DateTime.Now.Millisecond);

Cela ne sert à rien.

Autre erreur classique, multiplier les instances pour des tirages différents en espérant gagner “en aléatoire”…

Le pire que j’ai pu voir dans ce style est une série de Random instanciées l’une derrière l’autre. Si les créations sont réellement à la suite ou peu espacées il y a toutes les chances qu’elles soient initialisées avec la même heure qui a une résolution de 20 ms environ ! Et toutes les instances de Random produiront alors …. exactement la même série de nombres !Une seule instance suffit

Il ne sert donc à rien de multiplier les instances de Random dans une application en espérant avoir “plus” d’aléatoire au final. Bien au contraire on risque d’obtenir, comme l’exemple ci-dessus le démontre, une uniformité qui n’a vraiment plus rien d’aléatoire, même “pseudo” !

Si les exigences mathématiques sont assez faibles on peut parfaitement se contenter de Random. Mais alors, le plus malin consiste à créer une seule instance pour toute l’application. On s’assure bien ainsi que chaque run de l’application se basera sur une série différence et surtout qu’au sein de l’application tous les nombres sembleront bien être aléatoires...

Je vous passe l’exemple d’une classe statique déclarant une instance de Random et exposant des méthodes statiques calquant les méthodes principales de cette dernière. C’est enfantin.

Bref, la façon la plus simple d’avoir réellement des nombres pseudo aléatoires dans une application est de n’utiliser qu’une seule instance centralisée de Random. La classe n’est pas garantie “thread safe” donc si vous l’utiliser dans un contexte multithread le mieux est soit d’avoir une instance par thread. L’idée de créer un wrapper statique utilisant des locks pourrait être séduisante pour certains mais franchement je vous le déconseille, cela créera un goulet d’étranglement écroulant en partie l’avantage du multithreading.Complexité, aléatoire et Random

C’est ici que les deux histoires se rejoignent…

Random génère toujours la même séquence pour la même graine, ce qui n’est déjà pas très aléatoire comme fonctionnement. De plus les séries produites - puisque dépendantes de la graine - sont forcément issues d’un processus calculatoire tout à fait prédictible pour un spécialiste. Utiliser Random pour des opérations sensibles notamment touchant à la sécurité serait ainsi une grave erreur…

Pour cela il existe le namespace System.Security.Cryptography.

Ce namespace, comme son nom le laisse deviner, contient de nombreuses classes fort utiles en cryptographie. Et qui dit cryptographie dit nombres (pseudo) aléatoires. Mais comme il s’agit ici de sécurité les exigences mathématiques placent la barre un peu plus haut. Même si la qualité ne peut rivaliser avec du hardware spécialisé, elle dépasse de loin celle de Random.

Loin de moi l’idée d’aborder le contenu de ce namespace en quelques lignes. Je veux juste attirer votre attention sur son existence et qu’on y trouve un générateur qui remplace Random de façon plus efficace en évitant le risque de répétition des valeurs si plusieurs instances doivent être créées de façon proche dans le temps. Entre autres choses.

Il suffit d’utiliser RNGCryptoServiceProvider du namespace indiqué.

Cette classe permet de créer des instances de RandomNumberGenerator offrant un comportement plus fiable que Random. Il faut utiliser une instance par thread comme Random.

Quelle complexité aléatoire attendre ? Si on utilise la méthode de la compression, Random et RNG ont une complexité assez proche. Faites tourner le petit programme ci-dessous pour vous en convaincre (mode Programme C# dans LinqPad ou faites le tourner dans VS en mode console).

En revanche on obtient des séries ayant une meilleure distribution des valeurs produites et une prédictibilité bien moins grande qu’avec Random.void Main() { var r = new Random(); var t = new double[10000]; for (var i = 0; i < 10000; i++) t[i] = r.NextDouble(); //for (var i = 0; i < 10000; i++) t[i] = NextRandomDouble(); //for (var i = 0; i < 10000; i++) t[i] = 1; Console.WriteLine($"Taille tableau: {sizeof(double) * 1000}"); var tc = SerializeAndCompress(t); Console.WriteLine($"Taille tableau compressé: {sizeof(Byte) * tc.Length}"); var tdc = DecompressAndDeserialize<double[]>(tc); Console.WriteLine($"Taille tableau décompressé: {sizeof(double) * tdc.Length}"); } // Define other methods and classes here public static byte[] SerializeAndCompress(object obj) { using (MemoryStream ms = new MemoryStream()) { using (GZipStream zs = new GZipStream(ms, CompressionMode.Compress)) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(zs, obj); } return ms.ToArray(); } } public static T DecompressAndDeserialize<T>(byte[] data) { using (MemoryStream ms = new MemoryStream(data)) { using (GZipStream zs = new GZipStream(ms, CompressionMode.Decompress, true)) { BinaryFormatter bf = new BinaryFormatter(); return (T)bf.Deserialize(zs); } } } public static double NextRandomDouble() { var rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); var bytes = new byte[8]; rng.GetBytes(bytes); var ul = BitConverter.ToUInt64(bytes, 0) / (1 << 11); return ul / (Double)(1UL << 53); }

On trouvera dans ce code quelques idées annexes qui pourront vous intéresser comme la sérialisation + compression (et son inverse) en mémoire des données utilisant GZip (du framework .NET).Quand utiliser RandomNumberGenerator plutôt que Random ?

Les réponses seront assez simples :L’intention

Si votre code n’a rien à voir avec la sécurité et que la qualité des nombres aléatoires n’a pas grande influence utilisez Random, sinon utilisez RandomNumberGenerator on comprendra alors que vous tenez à utiliser un générateur plus pointu.La rapidité

Si les tirages au sort sont nombreux et peuvent gêner l’exécution de votre programme préférez Random car il est beaucoup plus rapide que RandomNumberGenerator, sauf bien sûr si la sécurité est un point important.La distribution des nombres

La classe Random ne produit pas un spectre très homogène. Si vous désirez des nombres aléatoires mieux répartis utilisez RandomNumberGenerator.La répétabilité

C’est un comble ! Mais oui la répétabilité peut être une bonne raison (mais une bien mauvaise publicité !) de préférer Random… Pour produire des tests identiques il peut s’avérer très intéressant d’utiliser les défauts de Random en prenant soin d’utiliser toujours la même graine lors de la création de l’instance. Ainsi new Random(5); produira à coup sûr la même séquence à chaque exécution. Elle sera différente de celle produite en utilisant une autre graine et la série aura toujours l’apparence de l’aléatoire. Mais cette répétition des mêmes séquences pour les mêmes graines peut s’avérer très utile !Conclusion

Il y aurait bien d’autres choses à dire sur les nombres aléatoires, pseudo ou non. C’est un sujet passionnant. Mais le but était principalement d’attirer votre attention sur les bonnes et les mauvaises utilisations de Random et vous signaler l’existence dans le Framework d’autres classes plus “pointues” pour produire des séries pseudo aléatoires. La mauvaise maîtrise des outils du framework n’est pas un tort en soi, on a tous quelque chose à apprendre, tous les jours. Mais quand je vois les mêmes erreurs se reproduire années après années je me dis que l’information a du mal à passer et que c’est dommage.

Stay Tuned !

Catégories: Dévelopement

Les deux règles pour comprendre XAML

Dot.Blog - mar, 06/03/2018 - 10:00

Des règles et des bonnes pratiques pour développer des applications XAML il en existe bien plus que deux, faire croire le contraire ne serait pas honnête. Ne serait-ce que par la richesse des EDI utilisés (Visual Studio ou Blend). Il faut accumuler de l’expérience et mémoriser de nombreux patterns pour architecturer et designer correctement une application. Mais il y a deux règles à comprendre pour bien programmer en XAML. XAML qui es-tu ?

XAML, le vrai, le dur, le tatoué, il n’y en a qu’un seul. Celui de WPF. Ces deux technologies sont nées ensemble et sont à jamais liées. Le XAML de WPF est la référence absolue. Il y a eu sa variante “everywhere” (WPF/E appelé plus tard Silverlight) qui n’a hélas pas survécu malgré ses grandes qualités, il y a eu Silverlight pour Windows Phone, passé à la trappe aussi, puis le XAML de WinRT version peu aimée du framework associée à Windows 8 mais qui n’est pas morte et qui a donné naissance à UWP, un XAML très honorable mais pas du niveau de celui de WPF, et enfin le XAML des Xamarin.Forms. Ce dernier ne peut rivaliser graphiquement avec WPF car le principe même est d’instancier des contrôles natifs ce qui supprime la possibilité de modifier leur aspect visuel via XAML et ses primitives graphiques, absentes des XF.

Donc aujourd’hui XAML c’est avant tout celui de WPF, et celui de UWP. Malgré mon support inconditionnel depuis des années à Xamarin et aux Xamarin.Forms, côté graphique et pour les raisons évoquées on ne peut pas comparer ce XAML là aux autres.WPF fait de la résistance

Ca fait des années que je le dis, WPF est loin d’être devenu inutile. Je le disais quand Windows 8 et Metro sont sortis, je l’ai dit pour Windows 10 et UWP, et je continue à le dire car rien n’a changé dans la justification : WPF est toujours le seul et unique moyen de créer des applications PC hyper performantes et lookées dont la distribution reste sous le contrôle de son auteur ! UWP oblige à passer par le Store de MS, c’est restrictif.

Pour le Mac, pour les smartphones, Xamarin.Forms est la solution la plus aboutie, d’autant qu’elle couvre simultanément UWP ce qui n’oblige pas à cibler cette plateforme de façon spécifique. Mais comme je le disais, privée de sa facette graphique le XAML des Xamarin.Forms ne peut pas être comparé à celui de WPF et d’ailleurs il n’y a pour l’instant aucune vraie concurrence puisque WPF ne peut pas tourner en cross-plateforme et Xamarin.Forms ne peut pas tourner sur un PC sauf par le biais de UWP.

Le XAML de WPF et celui de UWP qui reste très complet malgré tout, sont ainsi les seules émanations de ce langage fantastique jamais égalé ni de près ni de loin par aucun éditeur de langage de programmation dans le monde.

La philosophie de XAML est totalement reprise dans UWP et dans les Xamarin.Forms ce qui est déjà extraordinaire, mais les deux règles que je vais aborder ici ne concernent que WPF en premier chef et UWP en second lieu.

WPF fait donc bien de la résistance, tellement en avance pour son époque qu’il reste la référence absolue pour XAML aujourd’hui encore.

Les éditeurs de logiciels qui ont fait le dos rond durant les époques troubles de Windows 8 jusqu’à 10 doivent bien mettre à jour leurs logiciels qui ont vieilli… Et quel éditeur de logiciel peut se permettre de perdre 20 à 30% de son CA juste pour le fun en créant des Apps UWP pour le Store ? Cela peut être une démarche complémentaire, mais je ne connais aucun actionnaire qui accepterais de perdre autant sur ses dividendes ! Donc pouf pouf … ben non, pas de pouf pouf car il n’y a qu’un seul choix possible pour rester maître de sa création et la distribuer comme on veut au prix qu’on veut : WPF.

Il existe aussi d’autres raisons, comme le fait qu’UWP nécessite un Windows 10 alors qu’il existe encore beaucoup de Windows anciens dans certains parcs informatiques.

Bref, faire du XAML autonome et complet c’est choisir WPF. Si on ne se base que sur l’aspect graphique UWP peut faire l’affaire avec les restrictions techniques et financières déjà évoquées plus haut.Programmation par Templates

Sous WPF/UWP lâchez les vieilles habitudes. L’une de celles que je rencontre souvent est celle qui consiste à se jeter sur son clavier pour créer un UserControl (ou un Control par héritage) dès qu’on a besoin d’un nouveau composant qui semble absent de la bibliothèque.

Erreur. Méthode du passé.

En effet, WPF et UWP impliquent des changements radicaux de mode de pensée et d’habitudes. Sous ces environnements, la majorité des besoins sont couverts par les composants existants, il “suffit” juste d’en modifier le template.

Nous sommes passés d’une programmation par héritage à une programmation par modèle (template).

Deux exemples pour bien cerner ce que j’entends par là.Sticky Note

J’adore cet exemple car c’est certainement l’un des plus vieux qu’on puisse trouver ! Il prouve à la fois la fantastique avance de XAML dès sa sortie et sa spécificité technique. Sticky notes pour WPF est un cas d’école (sticky notes listbox). Ce “composant” se présente comme une liste verticale de petites notes de type “post-it” légèrement décalées les unes par rapport aux autres.

L’effet est sympathique et donne un peu de fantaisie et de vie à ce qui ne serait qu’une liste rectangulaire dans une grille en programmation classique. Certes le temps à passé et côté graphisme aujourd’hui la mode est au flat et le skeuomorphisme est un peu dépassé, mais ce n’est pas grave c’est un exemple qui à l’avantage d’être très visuel pour illustrrer mon propos.

Ce composant” n’utilise aucun code “'classique”. Inutile dans les sources de chercher la classe StickyNotes qui hériterait de Control ou même de chercher dans les fichiers de l’application le UserControl créé pour l’occasion.

Rien de tout cela.

Sticky Notes est créé uniquement en jouant sur le templating d’une simple… Listbox ! Une poignée de Xaml et des templates, c’est tout.Le bouton de sélection de langue

Dans une application UWP je devais implémenter un bouton permettant de choisir depuis la page d’accueil la langue à utiliser (français ou anglais). Forcément on pense à un ToggleButton ou quelque chose d’équivalent (ce qui n’existe pas de base). On pourrait aussi associer deux RadioButton dans une grille.

Mais le plus intéressant consiste à se demander “quel control existant se rapproche le plus du comportement que je souhaite implémenter”. En y réfléchissant quelques secondes on s’aperçoit assez vite qu’une CheckBox possède deux états stables (oublions ici l’état indéterminé). Ce composant comporte toute la logique et les états visuels permettant de gérer deux états.

Par défaut une CheckBox c’est une case à cocher avec un bout de texte devant ou derrière.

Le plus dur consiste à se l’imaginer comme deux drapeaux (français et US) côte à côte, celui qui est sélectionné étant à 110% de sa taille et l’autre à 50%. Par convention arbitraire et chauvinisme inconscient certainement j’ai considéré que IsChecked = True était le Français, à False l’anglais (l’exemple est très réducteur quant au support international mais on s’en contentera ici). En partant d’une CheckBox que j’ai totalement vidée de son contenu, et en ajoutant les deux drapeaux (et quelques autres ingrédients et animations via le Visual State Manager) j’ai obtenu un merveilleux “ToggleButton” sans jamais “sous-classer” le moindre contrôle. Juste en écrivant un template.

Le ViewModel de la page d’accueil offre une propriété IsFrench de type booléen qui est simplement bindée à la propriété IsChecked du CheckBox (en mode TwoWay) et l’affaire est jouée !Les règles

On en arrive aux fameuses deux règles illustrées par ces exemples.

Règle 1 : De l’héritage au templating

La programmation Xaml (WPF/UWP) est une programmation visuelle qui s’effectue par templating. Créer des UserControl et encore plus sous-classer des contrôles existants devient quelque chose de rarissime.

Nous sommes passés de l’ère de la programmation objet par héritage à celle de la programmation visuelle par templating. Ce changement n’est pas récent, il remonte à la création de WPF mais c’est le changement de paradigme le moins bien compris aujourd’hui encore. Bien comprendre toute la signification de ce changement est un point primordial et un préliminaire indispensable pour comprendre cette technologie et donc la programmer intelligemment.

Programmation par Properties

Il s’agit du même genre de glissement, une pente douce mais dont la longueur finit par conférer une vitesse tellement grande à celui qui s’y laisse glisser que le décor n’a plus rien à voir avec celui qu’on a tout le temps d’admirer en balade à dos d’âne…

Dans l’exemple précédent on touchait du doigt cette nouvelle approche mais sans la mettre en exergue.

En effet, dans le pattern M-V-VM plutôt que de créer un code incompatible avec le visuel d’un côté pour ensuite créer de l’autre des tripotés de convertisseurs (programmation classique sous WPF historiquement) il semble bien plus simple d’exposer dans les ViewModels des propriétés directement exploitables par l’UI. Si adaptation des données il doit y avoir c’est le ViewModel qui s’en charge (directement si cela est ponctuel, ou via une classe de service si la chose doit être réutilisées ailleurs).

Dans l’exemple précédent du Checkbox transformé en ToggleButton de sélection de langue, aucun événement n’est programmé, aucun gestionnaire n’est écrit pour réagir au clic. Tout se joue dans le ballet automatique de IsChecked de la CheckBox et de IsFrench du ViewModel sous l’égide discrète mais indispensable d’un binding two way…

Quant l’utilisateur clique sur la CheckBox (enfin sur le ToggleButton avec les deux drapeaux) le composant sous-jacent bascule sa propriété IsChecked à vrai ou faux selon le cas. Comme cette dernière est liée à IsFrench du ViewModel, une propriété de type booléen aussi pour assurer la compatibilité des comportements, le ViewModel ne reçoit pas un événement (ou une ICommand) mais voit l’une de ces propriétés (IsFrench) modifiée. Ce qui déclenche le Setter de cette dernière. Ce dernier s’occupant de modifier le fichier de ressource utilisé pour puiser les chaines de caractères. De là et par une série de notification de changement de propriétés (INPC), il avertit en retour la Vue que toutes les propriétés de type texte ont été modifiées. La vue (et ses bindings) y réagit en rafraichissant les affichages… sans programmation ou presque.

Toute cette mécanique s’est déroulée sans aucun gestionnaire d’événement, sans aucune programmation de Commande, sans aucune programmation “classique” (en dehors du ViewModel et de ces propriétés gérant INotifyPropertyChanged, ce qui peut être automatisé ou simplifié en utilisant une toolbox MVVM).Règle 2 :  de l’événementiel au binding

La seconde règle d’or à bien comprendre pour tirer totalement partie de XAML et de ses enfants (WPF et UWP mais aussi les Xamarin.Forms ici) est ainsi d’opter pour un modèle de développement de type Model-View-ViewModel se basant presque exclusivement sur des couples de propriétés mis en relation via binding.

On est passé de l’ère du développement dit “événementiel” des premières versions de Windows à ce qu’on pourrait appeler la “programmation par Binding” ou par “properties”.Conclusion

Pour résumer :

Règle 1 on cherche à templater des contrôles existants sans créer des UserControl ni dériver des contrôles existants.

Règle 2 : on base la dynamique de l’application sur le binding entre propriétés et non plus sur les gestionnaires des événements des contrôles.

Si vous avez déjà fait vôtre ses règles alors vous allez devenir, si ce n’est pas déjà le cas, de très bons développeurs XAML !

Si vous n’aviez pas encore vu les choses sous cet angle là, je serai très heureux si par chance j’ai réussi à lever le voile qui vous empêchait de les voir ainsi. Vous ne ferez qu’entrer plus vite dans la catégorie précédente !

Stay Tuned !

Catégories: Dévelopement

XML, arbres, LINQ et parallélisme

Dot.Blog - lun, 05/03/2018 - 21:06

Traiter des données arborescentes est toujours un peu délicat car cela implique l’usage de code récursif, sorte d’épouvantail à informaticien… Pire si tout cela doit être parallélisé c’est un cauchemar pour certains ! Mais c’est oublier que ces problèmes complexes peuvent être résolus par quelques lignes de C# avec l’aide de LINQ !

Arborescences

Un arbre c’est beau. Il y a un tronc et des ramifications, les branches. Elles-mêmes se ramifient en sous-branches jusqu’à atteindre les feuilles. On oublie souvent que la partie la plus importante d’un arbre ce sont ses racines, ce qu’on en voit n’est là que pour les faire vivre Mais de notre point de vue c’est idem car si on veut on peut tout aussi bien étudier l’arborescence de son système racinaire, c’est tout aussi complexe et ramifié. A l’endroit ou à l’envers, un arbre est ramifié !

De nombreux problèmes font intervenir en informatique des structures de données arborescentes. Les créer, mais surtout les traiter (donc les parcourir) posent néanmoins des problèmes à beaucoup d’informaticiens car cela fait intervenir l’épouvantail que j’évoquais plus haut : la récursivité. Les algorithmes récursifs sont eux aussi très beaux, comme les arbres. En peu de code ils peuvent traiter des ramifications complexes. La clé étant qu’une structure arborescente est une structure “self replicating”, auto récplicative.

Vouloir la traiter et l’aborder comme une structure de données classique est une erreur qui rend le problème très difficile à résoudre. La réponse est dans l’utilisation d’un code lui-même auto réplicatif. Non pas qu’il va se démultiplié en mémoire par enchantement, mais dans le sens où son exécution va pouvoir s’auto répliquer grâce à l’utilisation de la pile qui lui servira de fil d’Ariane pour ne pas se perdre dans le labyrinthe du Minotaure !

Code et arbre

Mais voilà le problème, écrire un code capable de se comporter ainsi, de savoir balayer tous les chemins d’un arbre et de retrouver son chemin vers la sortie n’est pas un exercice mental avec lequel tout le monde est à l’aise.

Créer une arborescence est assez simple, mais la traiter l’est beaucoup moins.

Surtout de nos jours où tout traitement digne de ce nom ce doit en plus d’utiliser tous les cœurs de la machine, donc d’être multitâche, et même encore plus complexe ici : parallélisé.

Création d’arbre parallélisée

Pour faire simple car les données de test ne sont jamais très simples à trouver dans ce genre de cas nous allons choisir comme source l’arborescence d’un disque dur. C’est assez gros et diversifié pour créer un arbre bien costaud.

Nous allons créer cette structure sous la forme d’un fichier XML qui pourra ensuite être exploité soit directement en mémoire soit après été stocké sur disque.

public static XElement BuildTree(string dirName) { var di = new DirectoryInfo(dirName); FileInfo[] files; try { files = di.GetFiles(); } catch { files = null; } var dirsize = 0L; if (files != null) Parallel.ForEach(files, current => dirsize += current.Length); DirectoryInfo[] subdirs; try { subdirs = di.GetDirectories(); } catch { subdirs = null; } // each item is a "directory" having 5 attributes // name is the name of the folder // fullpath is the full path including the name of the folder // size is the size of all files in the folder (in bytes) // files is the number of files in the folder // subdirs is the count of possible sub directories in the folder var elem = new XElement("directory", new XAttribute("name", di.Name), new XAttribute("fullpath", dirName), new XAttribute("size", dirsize), new XAttribute("files", files?.Count()??0), new XAttribute("subdirs", subdirs?.Count()??0)); if (subdirs != null) Parallel.ForEach (subdirs, dinf => { var elemDir = BuildTree(dirName + "\\" + dinf.Name); elem.Add(elemDir); }); return elem; }

La méthode ci-dessus créée un document XML en mémoire en balayant toute l’arborescence d’un disque à partir de l’emplacement spécifié qui peut être autre chose que la racine. Je vous le conseille même pour vos tests ! Sur mon portable dont le disque de 3To est loin d’être plein, le fichier qui est créé en sortie fait plusieurs Go si je pars de la racine, si votre machine n’est pas équipée généreusement de RAM vous allez tout faire sauter !

Mais armé d’un tel fichier votre disque dur (ou toute autre structure de données) n’aura plus de secret pour vous ! Grâce à LINQ il va être possible d’interroger la structure sans trop se perdre dans les méandres de la pensée récursive…

On notera au passage que la méthode de création de l’arbre exploite les extensions parallèles pour traiter l’arborescence le plus efficacement possible. Vous verrez qu’à l’exécution tous les cœurs de votre machine seront à 100%.

Le vidage sur disque de la structure en mémoire peut prendre ainsi bien plus de temps que sa création, car l’écriture du fichier XML par le Save du XDocument n’est pas parallèle…et si le disque n’est pas très rapide ça peut ramer un bon moment ne vous inquiétez pas… D’où l’intérêt de choisir un sous arbre pas trop gros pour vos tests.

La taille du fichier de sortie peut être énorme car XML est verbeux mais en plus ici nous mémorisons pour chaque fichier son path complet ce qui fait une quantité de texte phénoménale.

Voici un exemple de sortie juste pour le sous répertoire NDepend (très bel outil au passage) placé à la racine de C: :

<?xml version="1.0" encoding="UTF-8" standalone="true"?> <!--Structure au 05/03/2018 20:43:36--> <directories> <directory subdirs="4" files="5" size="322725" fullpath="c:\ndepend" name="ndepend"> … (je fais court car on va retrouver ce code plus bas !) </directory> </directory> </directories>

Il n’y a pas beaucoup d’imbrications dans cet exemple mais à vous de choisir quelque chose de plus gros, pour cet article cela suffira largement.

Traiter l’arbre

Un arbre se traite avec délicatesse… Et qu’y a t il de plus doux que LINQ pour ça…

Par exemple imaginons qu’on veuille connaître la liste exacte de tous les répertoires vides…

Voici un petit bout de programme qui va exploiter la séquence de création de l’arbre pour le parcourir :

var d = DirToXml.BuildTreeDocument(@"c:\ndepend"); //d.Save(@"C:\Temp\Test.xml"); Console.WriteLine(d.ToString()); Console.WriteLine(new string('-', 60)); var q = from e in d.Descendants("directory") where (int)e.Attribute("files") == 0 && (int)e.Attribute("subdirs") == 0 orderby (string)e.Attribute("fullpath") select e.Attribute("fullpath"); Console.WriteLine("Répertoires vides"); foreach (var element in q) { Console.WriteLine(element); } Console.WriteLine(string.Format("{0} répertoires vides", q.Count()));

Sur l’exemple d’arbre plus haut la sortie sera :

<!--Structure au 05/03/2018 20:53:07--> <directories> <directory name="ndepend" fullpath="c:\ndepend" size="322725" files="5" subdirs="4"> <directory name="ENaxos" fullpath="c:\ndepend\ENaxos" size="0" files="0" subdirs="0" /> <directory name="BuildProcessResources" fullpath="c:\ndepend\BuildProcessResources" size="1442" files="11" subdirs="3"> <directory name="MSBuild" fullpath="c:\ndepend\BuildProcessResources\MSBuild" size="12166" files="2" subdirs="0" /> <directory name="NAnt" fullpath="c:\ndepend\BuildProcessResources\NAnt" size="11227" files="2" subdirs="0" /> <directory name="ReportXsl" fullpath="c:\ndepend\BuildProcessResources\ReportXsl" size="39285" files="2" subdirs="0" /> </directory> <directory name="Lib" fullpath="c:\ndepend\Lib" size="27203256" files="38" subdirs="0" /> <directory name="NDepend.PowerTools.SourceCode" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode" size="59793" files="7" subdirs="21"> <directory name="Base" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\Base" size="164" files="1" subdirs="0" /> <directory name="AnalyzeAssemblyInFolder" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\AnalyzeAssemblyInFolder" size="4745" files="1" subdirs="0" /> <directory name="APIChanges" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\APIChanges" size="10198" files="2" subdirs="0" /> <directory name="DeadCode" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\DeadCode" size="10221" files="2" subdirs="0" /> <directory name="DotNetFrameworkAnalysis" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\DotNetFrameworkAnalysis" size="2265" files="1" subdirs="0" /> <directory name="CodeQueryConsole" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\CodeQueryConsole" size="75956" files="17" subdirs="0" /> <directory name="AnalyzeCodeOnDisk" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\AnalyzeCodeOnDisk" size="3755" files="1" subdirs="0" /> <directory name="Evolution" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\Evolution" size="4062" files="1" subdirs="0" /> <directory name="AppWords" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\AppWords" size="16503" files="4" subdirs="0" /> <directory name="DetectAssemblyIssues" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\DetectAssemblyIssues" size="13340" files="3" subdirs="0" /> <directory name="bin" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\bin" size="0" files="0" subdirs="1"> <directory name="Release" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\bin\Release" size="0" files="0" subdirs="0" /> </directory> <directory name="DotNetFrameworkCoreAPIChanges" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\DotNetFrameworkCoreAPIChanges" size="4203" files="1" subdirs="0" /> <directory name="CQL2CQLinq" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\CQL2CQLinq" size="4564" files="1" subdirs="0" /> <directory name="Properties" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\Properties" size="1408" files="1" subdirs="0" /> <directory name="Trend" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\Trend" size="2366" files="1" subdirs="0" /> <directory name="SearchForDuplicateCode" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\SearchForDuplicateCode" size="29396" files="6" subdirs="0" /> <directory name="SharedUtils" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\SharedUtils" size="29549" files="5" subdirs="0" /> <directory name="ReviewMethodChanges" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\ReviewMethodChanges" size="2968" files="2" subdirs="0" /> <directory name="SearchTypesByName" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\SearchTypesByName" size="15712" files="5" subdirs="0" /> <directory name="TestDebuggerDisplay" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\TestDebuggerDisplay" size="1881" files="1" subdirs="0" /> <directory name="obj" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\obj" size="0" files="0" subdirs="1"> <directory name="Debug" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\obj\Debug" size="578489" files="5" subdirs="1"> <directory name="TempPE" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\obj\Debug\TempPE" size="0" files="0" subdirs="0" /> </directory> </directory> </directory> </directory> </directories> ------------------------------------------------------------ Répertoires vides fullpath="c:\ndepend\ENaxos" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\bin\Release" fullpath="c:\ndepend\NDepend.PowerTools.SourceCode\obj\Debug\TempPE" 3 répertoires vides

Mais pour aller au bout de la démarche parallèlisée je vous propose de réécrire la fin du code de cette façon :

var q = from e in d.Descendants("directory").AsParallel() where (int)e.Attribute("files") == 0 && (int)e.Attribute("subdirs") == 0 orderby (string)e.Attribute("fullpath") select e.Attribute("fullpath"); Console.WriteLine("Répertoires vides"); Parallel.ForEach (q, element => Console.WriteLine(element) );

Vous noterez ici l’utilisation de Parallel.ForEach déjà utilisé et de PLINQ, l’extension parallèle de LINQ qui se manifeste dans la requête par l’utilisation de AsParalel() sur la source de données…

Conclusion

Il n’y a pas de des grandes nouvelles explosives en informatique, il y a aussi les petites choses bien utiles qu’on doit faire et devant lesquelles on reste parfois sans idées… Créer un arbre, le sauvegarder en XML, le traiter par des requêtes LINQ, le tout parallélisé, ce n’est pas de la bombe, certes, mais c’est ce qui fait le bon code, celui qui va vite, qui est lisible et dont on peut raisonnablement être fier !

Stay Tuned !

Catégories: Dévelopement

Do you speak RegEx ?

Dot.Blog - dim, 04/03/2018 - 19:46

Les expressions régulières sont d’une puissance incroyables, .NET nous offre des outils simples et performants pour les utiliser, mais voilà, quand on s’en sert une fois de temps en temps c’est à chaque fois la galère d’en écrire ! Mais on peut se faire aider..

Régulières mais pas simples

Les expressions régulières semblent être ignorées par bon nombre de développeurs malgré leur puissance. Une raison à cela : c'est aux antipodes de la programmation moderne qui se veut claire et lisible...

Les expressions régulières c'est un peu comme XSLT, balèze mais incompréhensible à moins de ne faire que ça tous les jours. Le problème avec ces "langages" c'est que justement on n'en a pas besoin tous les jours ! Et ce n'est pas en pratiquant une fois de temps en temps qu'on acquiert l'habilité nécessaire. Au final, les plus courageux qui ont essayé plusieurs fois finissent par laisser tomber...

Dommage. D'autant que les expressions régulières sont parfaitement intégrées aux frameworks modernes comme .NET, et que leur bonne utilisation permet des choses quasi magiques en une ou deux lignes d'instructions : tester la conformité d'une donnée complexe et même, ce que beaucoup ignorent, découper une données en groupes ou extraire une information dans un flot (par exemple repérer automatiquement les adresses web dans tout un texte).

Mais j'ai une solution pour vous !

Il existe un petit soft qui n’est pas très récent mais qui permet de saisir des expressions régulières, de les tester, et de les analyser (pour le debug c'est parfait, mais aussi pour.. apprendre!). Ce soft est vraiment bien fait et vaut le coup d'œil dans tous les cas.

Je vous conseille donc de le télécharger et de le tester, vous pourrez insérer des expressions régulières complexes dans votre code et passer pour un héro à la machine à café, c'est pas cool ça ?

Ca s'appelle "Expresso" et ça se trouve là http://www.ultrapico.com/Expresso.htm.Conclusion

L’informatique a été conçue pour simplifier le travail de l’humain, pas pour le complexifier. Ceux qui aiment les trucs tordus à faire de tête ou à la main sont des traitres à la cause informatique ! Un bon informaticien est avant tout une faignasse, il ne se jette jamais sur son clavier avant d’avoir longuement réfléchi, et surtout il ne fait pas de tête ou à la main ce qu’un logiciel peut faire à sa place ! Les expressions régulières aussi puissantes soient-elles sont une torture créant une friction cognitive forte. Mais on peut se faire aider d’un logiciel. Et de fait il serait bête de s’en passer…

Stay Tuned !

Catégories: Dévelopement

Xamarin.Forms, Validation des données et Mvvm–Partie 3

Dot.Blog - sam, 24/02/2018 - 14:16

La voici enfin cette partie 3 ! Enfin pas tout à fait.. mais presque !

Presque…

Oui presque, car si les mécanismes mis en jeu sont finalement assez simples, c’est leur imbrication qui est subtile et longue à expliquer.

Or en ce moment je n’arrive pas à trouver le temps de faire un papier aussi long qu’il devrait l’être pour boucler la partie 3.

Mais en même temps il est dommage de vous priver de la solution complète d’autant que le code est écrit depuis longtemps !

Donc…

Le code source complet

J’ai décidé de vous fournir le code source complet de la solution afin que, armé des 2 premières parties, vous puissiez vous-mêmes découvrir tout ce qu’il y a en plus dans le code pour faire fonctionner le système de validation (avec le retour à l’utilisateur).

Selon vos retours et vos éventuelles questions je pourrai évaluer l’intérêt d’écrire ou non une “vraie” partie 3 pour commenter le code.

Le code à télécharger se trouve ici www.e-naxos.com/download/DataValidationDemo.Zip

Conclusion

C’est un peu court je l’avoue, mais au moins vous disposez maintenant de la solution avec tout son code pour l’étudier.

Stay Tuned !

Catégories: Dévelopement

Prism pour Xamarin.Forms 7.0 releasé

Dot.Blog - sam, 20/01/2018 - 16:09

En ce début d’année (que je vous souhaite excellente bien entendu) voici une release attendue, Prism pour Xamarin.Forms 7.0. Elle apporte de nombreuses améliorations que je vous propose de découvrir…

Prism pour Xamarin.Forms

Inutile de vous faire perdre un temps précieux en présentations que j’ai déjà écrites dans les mois précédents à propos de ce toolkit MVVM indispensable. Je renvoie donc le lecteur intéressé à la liste des articles qui abordent le sujet notamment une série en 7 parties sur le toolkit lui-même.

.NET Standard

Cela était attendu depuis un moment, la bêta permettait déjà de se conformer à cette nouvelle utilisation de .NET mais c’est désormais releasé officiellement : Prism pour Xamarin.Forms supporte .NET Standard.

je ne vais pas là non plus vous en faire des tonnes sur ce que cela signifie et je renverrai là aussi le lecteur à mes articles sur .NET Standard pour en comprendre l’évident intérêt.

C’est en tout cas l’annonce la plus importante techniquement pour cette version 7.0 de Prism puisque comme vous le savez Visual Studio depuis peu de temps oblige à utiliser .NET Standard au lieu des PCL.

Nouveau Namespace pour l’IoC

Il était temps de simplifier et d’unifier tout cela… La version 7.0 offre une nouvelle API pour les conteneurs IoC qui est utilisée partout par le code du toolkit et qui s’offre aussi au développeur. L’un des gros avantages est que désormais la même API permet de traiter les conteneurs IoC dans Prism que cela soit sous UWP, WPF ou les Xamarin.Forms renforçant encore plus la portabilité du code entre les plateformes, associé au support de .NET Standard bientôt un même code identique pourra donc être utilisé sous toutes ces plateformes portant l’idée de l’universalité des Xamarin.Forms bien au-delà de leurs limites !

Les breaking changes

Certaines évolutions ne peuvent se faire sans un minimum de “casse”… Prism 7.0 en adoptant .NET Standard, en reformulant une API globale pour les conteneurs IoC et d’autres changements nécessaires n’échappe pas à la règle.

Bien entendu on retrouve ces “changements cassants” dans la sphère du conteneur IoC, forcément. Mais aussi l’API IModule, dans l’interface IPlatformInitializer, etc.

Autant dire qu’on ne prend pas un code écrit avec une ancienne version de Prism pour juste faire un update du package… On destinera la V7.0 aux nouveaux développements en laissant tranquilles ceux qui fonctionnent déjà avec les releases précédentes ! Toutefois les breaking changes sont localisés et bien documentés et il reste donc possible d’effectuer une mise à jour, mais en connaissance des effets…

Les améliorations

Bien que le but de la version 7 était principalement le support de .NET Standard et la clarification de l’API des conteneurs IoC elle s’accompagne de nouveautés qui ne sont pas sans intérêt.

On en retrouve dans le système de navigation notamment lorsqu’on navigue sur des onglets d’une page tabulée. C’est un cas de navigation un peu délicat à régler et les nouvelles solutions vont simplifier ce type de navigation.

Dans la même veine on trouve la génération automatique des tabulations et des pages tabulées lorsqu’on navigue vers une telle page.

La navigation a été au coeur des nouveautés et la possibilité de retirer une page de la pile de navigation a été ajoutée. Cela permet de répondre à certains scénarios (supprimer une page de login de l’historique une fois la personne identifiée par exemple).

Toujours au sein de la navigation il devient possible d’utiliser une navigation modale dans les liens profonds. S’il s’agit ici de scénarios déjà plus complexes il n’est pas si rare que cela de les rencontrer.

GoBackToRoot est une nouvelle fonction de la navigation, toujours elle !, qui permet comme son nom l’indique d’effacer toute la pile de navigation et de revenir à la page racine. Cela peut s’avérer très pratique.

Et pour terminer, sortons de la navigation pour une autre amélioration tout aussi essentielle : le support des propriétés complexes dans ObservesProperty ! C’est à dire qu’il devient possible d’observer la sous propriété d’une propriété (MaPropriété.SousPropriété.SousSousPropriété…). Cela simplifie encore plus ce merveilleux mécanisme que j’ai détaillé dans la série de 7 articles évoquée au début de cet article.

Conclusion

Comme toute release réellement importante il y a d’inévitables breaking changes à prendre en compte, mais le jeu en vaut la chandelle, Prism devient meilleur et tout le bénéfice va au développeur ! Il est vrai qu’au fil du temps cette mouture de Prism se complexifie un peu, MvvmLight sera certainement encore longtemps le moyen le plus simple de faire du MVVM pour ceux qui n’ont pas investi le temps nécessaire à la compréhension de Prism. Mais ne perdez pas de vue que sur de très nombreux points essentiels Prism va beaucoup plus loin que MvvmLight ou d’autres toolkit et que se passer d’un apprentissage ponctuel se reportera sur chaque instant perdu à chaque ligne de code du projet… Apprenez à vous servir de Prism, vous en tirerez rapidement avantage !

PS : J’en profite pour vous réitérer mes meilleurs voeux pour 2018 ! Comme disait le regretté Spock (Léonard Nimoy) : Live Long and Prosper !

Catégories: Dévelopement

Réduction de 30% jusqu’à ce soir sur le livre Xamarin.Forms !

Dot.Blog - lun, 08/01/2018 - 00:00

Ne ratez pas cette occasion avec le code BLUE3RTYde vous procurer mon livre sur les Xamarin.Forms ! Si vous l’avez déjà faites circuler l’info autour de vous

C’est LE livre sur les Xamarin.Forms en français, le 1er et le seul, tous les aspects sont abordés de la conception des UI bien entendu à la façon d’écrire des Apps qui respectent MVVM. Une référence indispensable pour qui veut se lancer dans le développement Mobile cross-plateforme en C#/XAML.

Son contenu est toujours et encore d’actualité et même plus que jamais et c’est le seul livre dont la mise à jour est permanente et gratuite via Dot.Blog !


Bonne Lecture !

Catégories: Dévelopement
S'abonner à Sleepy SME agrégateur - Dévelopement