「新しい方法でコードを書く(tm)」





私はC#が好きではありませんが、バージョンごとに提供されるすべてのパターンとすべての砂糖を収集するのが好きです。



3日目はNDCConferencesでBillWagnerのスピーチを見ました 。そこで彼は、新しい方法(TM)でコードを書く必要があることを示しました。



彼は優れたリファクタリングの例を数多く示しており、コードはより読みやすくなっていますが、その瞬間から、言語には正気のアーキテクトが必要であることに気付きました。



砂糖は問題を助けません



アマチュアがひざまずいて書いた、下手に書かれたコードを取り上げてください。このメソッドは、クラスインスタンスの状態をチェックし、okの場合はtrueを返し、そうでない場合はfalseを返します。



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





プログラマーは、メソッド内の他の1つでも試してみませんでした。しかし、私たちは経験豊富です。リファクタリングし、ifを削除して、ターナークに変えましょう。



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





5行ではなく2行のコードで、はるかに良くなりましたが、ternarkはパターンに変えることができます。



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





合計で、1行の美しいコードを残しました。すべて!リファクタリングが完了しました!(ではない)



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





アクセスできない_runspace'eで_powershell'aを呼び出すと、例外がスローされます。



すべての実装は、1つの問題を解決するため、同じように悪いです。例外をスローしないようにコードを保護し、見た目はすでに10番目です。



コードは最新ではありませんでしたが、その意味は変わっていません。



その他の例外!



もちろん、プログラムが現実に遭遇すると、例外的な状況が発生します。ファイルが見つかりません、間違った形式のファイル、間違ったコンテンツ、コンテンツがありません、Streamreaderがnullを読み取るか、空の文字列に渡しました。2行のコードが書かれていて、両方とも壊れていますが、私はその話を見て、視力を得ました。



「しかし、心配してください。独自のクラスやライブラリを作成する場合、防御コードについて考える必要はありません。コンパイラがタイプチェックを行い、nullのチェックがこれまでになく簡単になりました。



ライブラリのユーザーにすべてを投げて、コードを書くだけです。例外を投げてプログラムを置くことは今や名声になっています!私が投げると、あなたは捕まえます!」


つまり、Bill Wagner-NDC Conferences 2020の講演を理解したとき、



私はこの概念と.netの作業全般に非常に触発されたので、SystemからのRunspacePoolクラスとPowershellクラスの開発の実話をお話しします。私が最近遭遇した.Management.Automation:



喫煙者#1がPowershellを作成



まず、もちろん、状態を追跡するために、Disposeメソッドが呼び出されたときにtrueに変化するブールフィールドを作成します。



CLRがガベージを収集すると、Null参照をキャッチできるため、IsDisposedフィールドを表示することは一般的に安全ではありません。



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);
    }
}
      
      





Disposeまたは別のメソッドを再度呼び出すと、例外がスローされ、他のプログラマーもインスタンスの状態を監視したり、コードで例外をキャッチしたりできますが、これらは私の問題ではありません。



喫煙者#2はRunspacePoollを作ります



ここでもIsDisposedフィールドを作成しますが、今回はパブリックゲッターを使用して作成するため、ライブラリを使用する人はこれ以上セキュリティコードを記述する必要がありません。



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





Disposeが呼び出された場合は、戻って実行します。もちろん、フィールドに再度アクセスすると、オブジェクトはすでにメモリから削除されているため、nullrefを受け取りますが、これが私の問題です。



健康な人はライブラリを使用します:



ここに例外があります、ここに例外はありません、ここで私たちは魚を包みます。両方のクラスは同じパッケージで提供され、動作が異なります。クラスは、さまざまな理由で同じタイプの例外をスローします。



  • 間違ったパスワード?InvalidRunspacePoolStateException!
  • 接続されていませんか?InvalidRunspacePoolStateException!


ある場所ではObjectDisposedExceptionを処理する必要があり、別の場所では3番目のInvalidRunspacePoolStateExceptionでNullReferenceExceptionを処理する必要があり、すべてが驚きに満ちています。



例外は解決策ではありません





聖体拝領の前に、私は古い方法でファイルを読みました:



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





しかし、ビデオを見た後、私は新しい方法でやり始めました:



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");
    }
}
      
      





それとも新しい方法ですか?



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);
}
      
      





それは新しい方法でどのくらい正確ですか?開発者の責任はどこで終わり、ユーザーの責任はどこから始まりますか?





一般に、アイデアは明確です。ライブラリの作成者であれば、すぐにメソッドを呼び出して誤ったデータを送信できます。サブジェクト領域を完全に理解し、誤った動作のすべてのケースを説明し、例外を破棄します。それらを自分で感知して処理しました。



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(" ");
}

      
      





最も独創的な人は、私がどこをリードしているのかをすでに理解しています。try catchブロックに基づくエラー訂正の編成は、より深いコードのネストにつながるだけです。



このパターンを使用すると、いずれの場合も、コードの実行を拒否しますが、より丁寧に実行します。



一般に、新しいことは何もありません。10年前、人々はC#がパターンで過負荷になり、年々減少しなかったと疑うようになりました。別のものに会って、例外を投げるのが簡単になりました。



そして最後に-演算子





オペレーターはどこにも何もキャストしないでください。



あなたがおそらく知っているJSからの例:



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



 

JSの設計者は、個別の加算演算子と個別の連結演算子は必要ないと考えていたため、JSで数学を行うことは安全ではありません。



JSでのこのバグの原因は、演算子を使用した文字列型からint型への暗黙的な変換です。これが過剰な砂糖がバグになる方法です。



C#は、それほど頻繁ではありませんが、暗黙的な型キャストにも悩まされています。たとえば、ライブラリを更新した後、以前のようにintではなくstringにマップされ始めたユーザー入力、および演算子(+)と数学演算子および連結演算子を考えてみます。 



型をintからstringに変更してもコードは壊れませんでしたが、ビジネスロジックは壊れました。



それで、ここに残しておきます。実行せずに実行の結果を推測しようとします。 



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








All Articles