"Ecrivez le code d'une nouvelle manière (tm)"





Je n'aime pas C #, mais j'aime rassembler tous les modèles et tout le sucre qu'ils offrent d'une version à l'autre.



Le troisième jour, j'ai regardé le discours de Bill Wagner aux NDC Conferences , où il a montré qu'il fallait écrire du code d'une nouvelle manière (TM).



Il montre de nombreux exemples de bon refactoring, le code devient plus lisible, mais à partir de ce moment j'ai réalisé que le langage avait besoin d'un architecte sensé.



Le sucre n'aidera pas les choses



Prenez un morceau de code mal écrit écrit par un amateur sur ses genoux. Cette méthode vérifie l'état de l'instance de classe et renvoie true si ok et false dans le cas contraire.



internal bool GetAvailability()
{
    if (_runspace.RunspacePoolAvailability == RunspacePoolAvailability.Available) { return true;}
    if (_runspace.RunspacePoolAvailability == RunspacePoolAvailability.Busy) { return true;}
    return false;
}
      
      





Le programmeur a essayé, pas même un seul autre dans la méthode. Mais nous sommes expérimentés, refactorisons-le, supprimons les ifs et transformons-le en ternark:



internal bool GetAvailability()
{
    return _runspace.RunspacePoolAvailability == RunspacePoolAvailability.Available ||
           _runspace.RunspacePoolAvailability == RunspacePoolAvailability.Busy;
}
      
      





C'est devenu beaucoup mieux, 2 lignes de code au lieu de 5, mais le ternark peut être transformé en motif:



internal bool GetAvailability()
{
    return _runspace.RunspacePoolAvailability is RunspacePoolAvailability.Available or RunspacePoolAvailability.Busy;
}
      
      





Au total, nous avons laissé une belle ligne de code. Tout! La refactorisation est terminée! (ne pas)



internal void Invoke()
{
	if (!GetAvailability()) return;
        PowerShell _powershell = PowerShell.Create();
        _powershell.RunspacePool = _runspace;
        _powershell.Invoke()
    
}
      
      





L'appel de _powershell'a dans un _runspace'e inaccessible lèvera une exception.



Toutes les implémentations sont également mauvaises car elles résolvent un problème - elles protègent le code derrière lui-même afin de ne pas lever d'exception, et son apparence est déjà la dixième chose.



Le code n'était pas à jour, mais sa signification n'a pas changé.



Plus d'exceptions!



Lorsqu'un programme rencontre la réalité, bien sûr, des situations exceptionnelles surviennent. Fichier non trouvé, fichier de format incorrect, contenu incorrect, pas de contenu, Streamreader a lu null ou l'a transmis à une chaîne vide. Deux lignes de code sont écrites et les deux sont brisées, mais j'ai regardé la conversation et j'ai vu.



«Mais inquiétez-vous, maintenant, lorsque vous créez votre propre classe ou bibliothèque, vous n'avez pas à penser au code défensif, le compilateur fait la vérification de type pour nous, et la vérification de null n'a jamais été aussi simple!



Il suffit de tout jeter sur l'utilisateur de la bibliothèque et d'écrire le code. Lancer des exceptions et mettre en place un programme est désormais devenu prestigieux! Je lance et tu attrapes! "


Cela, comme j'ai compris le discours de Bill Wagner - NDC Conferences 2020,



j'ai été tellement inspiré par ce concept, et le travail de .net en général, donc je vais vous raconter la véritable histoire du développement des classes RunspacePool et Powershell de System .Management.Automation, que j'ai récemment rencontré:



Smoker # 1 fait Powershell



Tout d'abord, bien sûr, pour garder une trace de l'état, nous créons un champ booléen qui devient vrai lorsque la méthode Dispose est appelée.



Il n'est généralement pas sûr d'afficher le champ IsDisposed, car si le CLR collecte des déchets, vous pouvez intercepter une référence Null.



class PowershellInNutshell() : IDisposable
{
    private static bool IsDisposed = false;
    private static RunspacePoolInTheNuttshell;

    public static void Invoke()
    {
        if (IsDisposed) throw new ObjectDisposedException();
        Console.WriteLine("I was invoked");
    }

    public void Dispose()
    {
        if (IsDisposed) throw new ObjectDisposedException("Invoke","   ,      ,  ");
        IsDisposed = true;
        Console.WriteLine("I was invoked");
        GC.SuppressFinalize(this);
    }
}
      
      





Lorsque nous appelons à nouveau Dispose ou une autre méthode, nous lançons une exception et laissons l'autre programmeur surveiller également l'état de l'instance ou intercepter les exceptions avec son code, mais ce ne sont pas mes problèmes.



Smoker # 2 fait RunspacePooll



Ici, nous créons également le champ IsDisposed, mais cette fois, nous le faisons avec un getter public, afin que la personne utilisant la bibliothèque n'ait pas à écrire plus de code de sécurité.



class RunspacePoolInTheNuttshell() : IDisposable
{
    public static bool IsDisposed = false;
    
    public void Dispose()
    {
        if (IsDisposed) return;
        IsDisposed = true;
        GC.SuppressFinalize(this);
        Console.WriteLine("I was invoked");
    }
}
      
      





Si Dispose a été appelé, revenez et terminez. Bien sûr, lors du nouvel accès au champ, il recevra nullref, car l'objet sera déjà supprimé de la mémoire, mais est-ce mon problème.



Une personne en bonne santé utilise la bibliothèque:



Voici une exception, ici ne fait pas exception, ici nous emballons le poisson. Les deux classes viennent dans le même package et ont un comportement différent. Les classes lancent le même type d'exception pour des raisons différentes.



  • Mauvais mot de passe? InvalidRunspacePoolStateException!
  • Pas de connection? InvalidRunspacePoolStateException!


Il s'avère que dans un endroit, vous devez gérer ObjectDisposedException, dans une autre NullReferenceException dans la troisième InvalidRunspacePoolStateException, et tout est plein de surprises.



L'exception n'est pas une solution





Avant la communion des saintes ordonnances, j'ai lu le dossier à l'ancienne:



public static void Main()
{
    string txt = @"c:\temp\test.txt";
    
    if (File.Exists(txt)) return;
    string readText = File.ReadAllText(txt);
    Console.WriteLine(readText);
}
      
      





Mais après avoir regardé la vidéo, j'ai commencé à faire d'une nouvelle manière:



public static void Main()
{
    string txt = @"c:\temp\test.txt";
    try
    {
        string readText = File.ReadAllText(txt);
        Console.WriteLine(readText);
    }
    catch (System.IO.FileNotFoundException)
    {
        Console.WriteLine("File was not found");
    }
}
      
      





Ou est-ce une nouvelle façon?



public static void Main()
{
    string txt = @"c:\temp\test.txt";

    if (!File.Exists(txt))
    {
        throw new NullReferenceException();
    }
    string readText = File.ReadAllText(txt);
    Console.WriteLine(readText);
}
      
      





Comment cela se passe-t-il exactement d'une manière nouvelle? Où finit la responsabilité du développeur et où commence la responsabilité de l'utilisateur?





En général, l'idée est claire, si vous êtes l'auteur de la bibliothèque, vous pouvez immédiatement appeler la méthode et lui soumettre des données incorrectes, vous connaissez parfaitement le domaine et décrit tous les cas de comportement incorrect, jetez des exceptions qui font sens et les a manipulés vous-même.



internal class NewWay
{
    public static string _a;
    public static string _b;
    public static string _c;

    public static void NewWay(string a, string b, string c)
    {
        string _a = a ?? throw new NullReferenceException("a is null");
        string _b = b ?? throw new NullReferenceException("b is null");
        string _c = c ?? throw new NullReferenceException("c is null");
    }
    public void Print()
    {
        if (String.Compare(_a, _b) != 0)
        {
            throw new DataException("Some Other Ex");
        }

        Console.WriteLine($"{_a + _b + _c}");// 
    }
}
      
      





try
{
    NewWay newway = new(stringThatCanBeNull, stringThatCanBeNull, stringThatCanBeNull);
    newway.Print();
}
catch (NullReferenceException ex)
{
    Console.WriteLine(" ");
}
catch (DataException ex)
{
    Console.WriteLine(" ");
}

      
      





Les plus ingénieux ont déjà compris où je mène. L'organisation de la correction d'erreurs basée sur des blocs try catch ne mènera qu'à une imbrication de code plus profonde.



En utilisant ce modèle, dans tous les cas, nous refusons d'exécuter le code, mais plus poliment.



En général, rien de nouveau, il y a 10 ans, les gens ont commencé à soupçonner que C # était surchargé de modèles et d'année en année, ils ne diminuaient pas. Rencontrez-en un autre, lancer des exceptions est devenu plus facile.



Et enfin - les opérateurs





Les opérateurs ne doivent rien lancer nulle part.



Un exemple de JS que vous connaissez probablement:



console.log('2'+'2'-'2');
// 20
      
      



 

Les concepteurs JS ont considéré qu'un opérateur d'addition séparé et un opérateur de concaténation séparé ne sont pas nécessaires, donc faire des maths dans JS n'est pas sûr.



La source de ce bogue dans JS est la conversion implicite du type chaîne en type int à l'aide de l'opérateur. C'est ainsi que l'excès de sucre devient un bug.



C # souffre également du cast de type implicite, bien que beaucoup moins souvent. Prenons, par exemple, l'entrée utilisateur, qui, après la mise à jour de la bibliothèque, a commencé à être mappée à string au lieu de int, comme auparavant, et l'opérateur (+) et l'opérateur mathématique et l'opérateur de concaténation. 



Changer le type de int en string n'a pas cassé le code, mais a rompu la logique métier.



Je vais donc le laisser ici, et vous essayez de deviner le résultat de l'exécution sans courir. 



class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"{'2' + '2' - '2' }");
    }
}
      
      








All Articles