Sommaire
- Les sources
- Pourquoi ?
- Ce qui a été porté
- Ce qui est propre à ce port
- Ce que j'ai loupé
- Note à moi-même
Bonjour 'nal,
Y'a plein de poussière ici dis-moi. Faut dire que ça fait longtemps que je ne t'ai plus écrit.
Ce WE, j'ai voulu faire honneur à Pierre Tramo avec un langage qui (à ses débuts) était une copie de Java avec quelques améliorations pour pas que le prof voit qu'on a copié sur le voisin.
Je veux bien sûr parler de C#.
Les sources
Pourquoi ?
Parce que j'en avais envie. Pour le fun. Parce que ça manquait (ou pas), surtout face au port Java.
Ce qui a été porté
Tout. Les traductions, les tests unitaires, le mode jeu, l'aspect orienté-objet…
Ce qui est propre à ce port
J'ai voulu utiliser au plus les possibilités modernes de C# 9 et précédents.
Bon y'a pas de pattern matching ni d'async/await ni de stackalloc, ni plein d'autres choses, mais c'est pour très une bonne raison : on n'en a pas besoin.
Le point d'entrée utilise des top level statements (C# 9) (et une lambda) :
using CmdTempo;
using CommandLine;
using CommandLine.Text;
using LibTempo;
// Set sentence builder to localizable
SentenceBuilder.Factory = () => new LocalizableSentenceBuilder();
Parser.Default.ParseArguments<Options>(args).WithParsed((options) => Runner.RunOptions(options));
Comme pour async dans un point d'entrée de programme console, ce n'est que du sucre syntaxique.
Le compilateur s'occupe de rajouter tout la sauce (namespace, classe statique, static void Main (string[] args)).
Mais ça fait du bien à mes petits doigts potelés de ne pas avoir à l'écrire, et à mes yeux de ne pas avoir à les lire. Et ça, c'est bon !
On utilise parfois des ranges sur des chaînes de caractère (C# 8) :
return
String.Format(Resource.SentenceMutuallyExclusiveSetErrors, names[0..^2], incompat[0..^2]);
C'est beaucoup plus rapide à écrire qu'avec SubString, et ça me rappelle mes années Ruby.
Les options sont une classe essayant de forcer l'immutabilité (un record) (C# 9) :
namespace LibTempo
{
using CommandLine;
public record Options
{
public const uint DefaultSampleSize = 5;
public const uint DefaultResetTime = 5;
public const uint DefaultPrecision = 0;
public const uint MaxPrecision = 5;
[Option('g', "game", Required = false, Default = false, HelpText = nameof(IsGamingMode), ResourceType = typeof(Resource))]
public bool IsGamingMode { get; }
[Option('s', "sample-size", Required = true, Default = DefaultSampleSize, HelpText = nameof(SampleSize), ResourceType = typeof(Resource))]
public uint SampleSize { get; }
[Option('r', "reset-time", Required = true, Default = DefaultResetTime, HelpText = nameof(ResetTime), ResourceType = typeof(Resource))]
public uint ResetTime { get; }
[Option('p', "precision", Required = true, Default = DefaultPrecision, HelpText = nameof(Precision), ResourceType = typeof(Resource))]
public uint Precision { get; }
public Options(bool isGamingMode, uint sampleSize, uint resetTime, uint precision)
{
IsGamingMode = isGamingMode;
SampleSize = sampleSize == 0 ? DefaultSampleSize : sampleSize;
ResetTime = resetTime == 0 ? DefaultResetTime : resetTime;
Precision = precision > MaxPrecision ? MaxPrecision : precision == 0 ? DefaultPrecision : precision;
}
}
}
On utilise des extensions pour faire croire que nous aussi on a Back, Front, et IsEmpty dans la classe générique Queue (C# 2 pour les Generics et 3 pour LINQ, ça nous rajeunit pas) :
using System.Collections.Generic;
using System.Linq;
namespace LibTempo
{
internal static class QueueExtensions
{
public static bool IsEmpty<T>(this Queue<T> queue) => queue.Count == 0;
public static T Back<T>(this Queue<T> queue) => queue.Last();
public static T Front<T>(this Queue<T> queue) => queue.First();
}
}
On utilise partout des expression body pour des méthodes car les {} et return c'est has-been (C# 7) :
private double ComputeNewSecretBPM() => _betterRng.Next(50, 200);
On écrit le BPM avec la précision demandée en utilisant beaucoup moins le clavier et avec de l'interpolation (C# 7) :
protected string BPMToStringWithPrecision(double bpm) => bpm.ToString($"G{_precision}", CultureInfo.CurrentCulture);
Et le pattern Fluent pour des tests plus rapidement écrits (mais ça c'est un package Nuget) :
[Fact]
public void InvalidArsShouldReturnDefaultOptions()
{
var result = Parser.Default.ParseArguments<Options>(args: new string[] { "0", "0" });
result.Tag.Should().Be(ParserResultType.NotParsed);
}
On a activé les Nullable Reference Types partout, parce que c'est la guerre contre les NullReferenceExceptions depuis C# 8 (comme beaucoup de choses, C# a piqué ça à F# où NULL n'existe pas) :
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
Ainsi, un accès à null (qui provoque un crash) est détecté à la compilation et traité comme une erreur.
Mais je n'ai pas eu l’occasion d'utiliser les annotations, donc pas de code à montrer. Le compilateur n'a rien dit.
Ce que j'ai loupé
J'aurais pu convertir le projet en VB.NET en un tour de main grâce au compilateur Roslyn, afficher la console dans le navigateur avec Blazor.Console… Peut-être plus tard ?
J'aurais pu aussi utiliser NGetText pour éviter les fichiers RESX et leur intrusion avec leurs clés en Pascal Case dans le code, ce qui le rend moins lisible. Et ainsi garder le fichier .PO d'origine.
Console.WriteLine(Resource.HitEnterForEachTempoOrQToQuit);
Enfin, j'ai loupé mon week-end, et j'aurais pu me coucher plus tôt.
Sacré Pierre Tramo !
Note à moi-même
Ce n'est pas parce que c'est plus facile de compiler et déboguer TapTempo quand on est sous Windows avec WSL qu'il fallait forcément t'y intéresser !
# Liens manquants...
Posté par xcomcmdr . Évalué à 5. Dernière modification le 11 janvier 2021 à 00:07.
Extension methods:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
Top Level Statements :
https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/exploration/top-level-statements
Un modérateur pourrait le rajouter, svp ?
Et un typo:
guuere/guerre
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Liens manquants...
Posté par Benoît Sibaud (site web personnel) . Évalué à 4.
Corrigé, merci (et d'autres typos au passage).
[^] # Re: Liens manquants...
Posté par Snark . Évalué à 2.
Il y a aussi un "on N'en a pas besoin" qui m'a tiré l'œil.
[^] # Re: Liens manquants...
Posté par Benoît Sibaud (site web personnel) . Évalué à 3.
Corrigé, merci.
# Bravo
Posté par mzf (site web personnel) . Évalué à 3.
Il me semble que c'est le premier portage complet connu, donc félicitation !
Comme tu as dû lire tout le code C++ original, n'hésite pas à remonter des soucis si tu as trouvé des coquilles :-)
# classe BPM
Posté par TImaniac (site web personnel) . Évalué à 2.
Si je puis me permettre une suggestion, tu pourrais faire une classe BPM :)
[^] # Re: classe BPM
Posté par xcomcmdr . Évalué à 2.
Pierre Tramo approuve ! :)
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.