フッターコンテンツにスキップ
Iron Academy Logo
C# よくある問題

DRY原則をマスターする:C#でデザインパターンを適用してコードをクリーンにする

Tim Corey
53分20秒

C#のデザインパターンは、効率的で再利用可能、かつ保守性の高いコードを書くために不可欠なツールです。 これらのパターンは、一般的なソフトウェア設計の問題に対する標準的な解決策を提供し、ベストプラクティスを促進し、開発者が冗長なコードを回避できるようにします。 デザインパターンを適用する際の基本原則の1つはDRY(Don't Repeat Yourself)原則で、可読性と保守性を高めるためにコード内の繰り返しを最小限に抑えることを重視しています。

この記事は、ティム・コーリーの洞察に満ちたビデオ"デザイン・パターン"に触発されています:DRYの原則と、よりクリーンで整理されたコードを作成するための実用的なアプリケーションについて深く掘り下げています。 この記事では、Tim のビデオで説明されている主要な概念と戦略を探ることで、C# プロジェクトで DRY デザインパターン原則を効果的に実装するための包括的なガイドを提供することを目的としています。

Introduction to the DRY Principle in C

冒頭でティム・コリーは、"Don't Repeat Yourself "の略であるDRY原則について説明する。この原則はプログラミングの基本的な概念で、知識やロジックのすべての部分がコード内の1つの場所に表現されるようにすることで、冗長性を避けることを強調している。 Timは、ダッシュボード・フォームを持つWinFormsアプリケーションの簡単な例を使って原理を説明しています。 フォームには、姓と名を入力するフィールドと、これらのフィールドに基づいて社員IDを生成するボタンがあります。

コードの繰り返しを特定し、予測する

(0:53)で、ティムはコード内の繰り返しの特定と予測に移ります。 WinFormsアプリケーションを例に、メソッドが1回しか呼び出されない場合でも繰り返しが発生することを示す。 このアプリケーションでは、従業員IDの生成ロジックとして、姓と名のテキストフィールドから部分文字列を抽出し、最後に3桁のコードを付加します。

Applying Design Patterns In Csharp For Cleaner Code 1 related to コードの繰り返しを特定し、予測する

上のスクリーンショット(1:31)では、ティムがアプリケーションの機能を実演し、姓と名の最初の4文字と3桁のコードを組み合わせて従業員IDを生成する方法を示しています。 彼は、コードは同じロジックを明示的に繰り返さないためDRY原則に従っているように見えるが、繰り返しのパターンには対処すべき根本的な問題があることを強調している。

(1:51)では、コードは単純に見えるが、従業員IDを生成するロジックがボタンのクリックイベントと緊密に結合しているため、DRY原則に完全に準拠していないと指摘している。 つまり、このロジックが、例えば新入社員のリスト(3:58)を処理するときなど、クライアントコードの他の場所で必要になった場合、コードを繰り返したり、適応させたりする必要があり、冗長になってしまう。

独立した再利用可能なメソッドを作成する

このセグメントでは、ティム・コーリーがDRY原則を遵守するために、独立した再利用可能なメソッドを作成する方法を示します。 彼は、イベント・ハンドラから従業員IDを生成するロジックを別のメソッドに抽出することから始めます。 このリファクタリングでは、GenerateEmployeeID というプライベートメソッドを作成し、既存のコードをこのメソッドに移動します (5:15)。 イベントハンドラ内の修正されたコードは、単にこのメソッドを呼び出します。

ステップと例:

1.初期コード:従業員IDを生成するためのロジックは、ボタンのクリックイベントハンドラに直接ありました。

Applying Design Patterns In Csharp For Cleaner Code 2 related to ステップと例:

2.リファクタリングされたコード: Timは、より柔軟にすることでメソッドを改善します。 特定のUI要素に依存する代わりに、このメソッドはパラメーターとして firstNamelastName を受け取り、生成されたIDを返します。 この変更により、このメソッドをさまざまなコンテキストやUI要素で使用できるようになります:

   private string GenerateEmployeeID(string firstName, string lastName)
   {
      string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString();
      return employeeID;
   }
   private string GenerateEmployeeID(string firstName, string lastName)
   {
      string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString();
      return employeeID;
   }

Timは次に、このメソッドがクリックイベントからどのように呼び出されるかを示します:

   employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);
   employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);

また、この方法は、コードを繰り返すことなく、複数の従業員レコードを含むCSVファイルの処理など、アプリケーションの他の部分でも使用できるようになったことも指摘している。

クラスライブラリの構築と使用

ティム・コリーは次に、コードの再利用と保守性をさらに高めるためのクラス・ライブラリの概念について説明します。 彼は、GenerateEmployeeID メソッドを複数のプロジェクトで使用可能なクラスライブラリオブジェクトにカプセル化する方法を示しています。

(8:00)でティムは、ユーザーの要求や会社の方針に基づいて、グラフィックやアニメーションを使ってよりインタラクティブにするために、デザインは変化し続けると説明する。 そこで、彼は、正確なフィールドと社員IDを生成するボタンを持つソリューション内のWPFプロジェクトを紹介します。

(9:15)でティムは、もし私たちが同じことを繰り返さないようにするのであれば、新しいWPFプロジェクトではコードがコピーペーストされているはずだと言って、クラスライブラリを使うことを強く主張している。 そこで、DRYを保つために、クラス・ライブラリでクラスを作成する必要があります。

ステップと例:

1.クラスライブラリの作成:

  • (9:47)で、Timは.NET Frameworkで新しいクラスライブラリプロジェクトを作成し、DRYDemoLibraryと名付けています。

  • このライブラリ内で、彼はパブリッククラス EmployeeProcessor を定義し、GenerateEmployeeID メソッドをこのクラスに移動します:

      public class EmployeeProcessor
      {
         public string GenerateEmployeeID(string firstName, string lastName)
         {
            string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString();
            return employeeID;
         }
      }
      public class EmployeeProcessor
      {
         public string GenerateEmployeeID(string firstName, string lastName)
         {
            string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString();
            return employeeID;
         }
      }

2.プロジェクトでクラスライブラリを使用する:

  • WinFormsプロジェクト(13:18)とWPFプロジェクト(14:00)では、TimはDRYDemoLibraryクラス・ライブラリへの参照を追加しています。

  • 次に、古いコードをクラスライブラリの GenerateEmployeeID メソッドの呼び出しで置き換えます:

      EmployeeProcessor processor = new EmployeeProcessor();
      employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text);
      EmployeeProcessor processor = new EmployeeProcessor();
      employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text);
  • このアプローチでは、メソッドが一箇所で管理されるため、冗長性がなくなります。 Timは、コードを繰り返すことなく、同じクラスライブラリを異なるUIフレームワーク(WinFormsとWPF)で使用できることを示します。

3.利点:

  • 一貫性:ロジックをクラスライブラリに一元化することで、Timはロジックへの変更(バグ修正など)がすべてのプロジェクトで一律に適用されるようにします。

  • メンテナンスの軽減:メソッドの変更はクラスライブラリで行うだけでよいため、不整合を回避し、メンテナンスのオーバーヘッドを削減します。

クラスライブラリを複数のプロジェクトに統合する

ティム・コリーは、さまざまなタイプのプロジェクトでDRYDemoLibraryクラスライブラリを使用する方法を探求し続け、特に新しいコンソールアプリケーションにライブラリを統合することに焦点を当てます。 これは、ライブラリの機能が、単一のインスタンスや同じソリューション内のものだけでなく、さまざまなアプリケーションで再利用できることを示すものです。

ステップと例:

1.新しいソリューションとプロジェクトの作成:

  • (17:29)のTimは、Windowsサービスやコンソールアプリのような異なるタイプのプロジェクトでDRYDemoLibraryを使用する必要があるかもしれないシナリオをシミュレートしながら、コンソールアプリケーション用の新しいソリューションを作成することから始めます。

  • 彼は新しいプロジェクトをConsoleUIと名付け、基本的なコンソール・アプリケーションのセットアップ方法を示している。

      class Program
      {
         static void Main(string[] args)
         {
            Console.ReadLine();
         }
      }
      class Program
      {
         static void Main(string[] args)
         {
            Console.ReadLine();
         }
      }

2.クラスライブラリに参照を追加する:

  • Timは、新しいプロジェクトにDRYDemoLibrary DLLへの参照を追加する方法を説明します。 これには、クラス・ライブラリ・プロジェクトのbinフォルダ内のDLLファイルを参照し、コンソール・アプリケーションに追加することが含まれます。

      using DRYDemoLibrary;
      using DRYDemoLibrary;
  • リファレンスが追加されると、Tim (19:24) はライブラリから EmployeeProcessor クラスを使用して、ユーザー入力に基づいて従業員 ID を生成する。

      Console.WriteLine("What is your first name?");
      string firstName = Console.ReadLine();
    
      Console.WriteLine("What is your last name?");
      string lastName = Console.ReadLine();
    
      EmployeeProcessor processor = new EmployeeProcessor();
      string employeeID = processor.GenerateEmployeeID(firstName, lastName);
    
      Console.WriteLine($"Your employee ID is {employeeID}");
      Console.WriteLine("What is your first name?");
      string firstName = Console.ReadLine();
    
      Console.WriteLine("What is your last name?");
      string lastName = Console.ReadLine();
    
      EmployeeProcessor processor = new EmployeeProcessor();
      string employeeID = processor.GenerateEmployeeID(firstName, lastName);
    
      Console.WriteLine($"Your employee ID is {employeeID}");

3.コンソールアプリケーションの実行:

  • Timは、コンソール・アプリケーションを実行して、ライブラリを使用して従業員IDが正常に生成されることを示します。 これにより、クラスライブラリの同じコードを異なるプロジェクトで再利用できることが確認されます。

    Applying Design Patterns In Csharp For Cleaner Code 3 related to ステップと例:

4.DLLの更新:

  • Timは、DLLが変更された場合、それを参照するプロジェクトでそれを更新できることに簡単に触れています。 このビデオでは詳しく取り上げていないが、NuGet パッケージの使用は、複数のプロジェクトにまたがる DLL の管理と更新に推奨されるアプローチである、と彼は指摘する。

DLLの更新とNuGetパッケージの管理

Tim Corey氏は、クラス・ライブラリの管理と更新にNuGetパッケージを使用する概念を簡単に紹介しています。 このアプローチは、特に大規模なプロジェクトや組織において、依存関係や更新を処理するための、よりスケーラブルなソリューションを提供します。

主なポイント:

1.NuGetパッケージの作成:

  • DLLファイルを手動で管理する代わりに、Tim氏はクラスライブラリのNuGetパッケージを作成することを提案しています。 これには、DLLをNuGetパッケージにパッケージ化し、NuGetサーバー(プライベートまたはパブリック)にアップロードすることが含まれます。

2.パッケージの更新:

  • NuGetパッケージを使用することで、パッケージのバージョンを更新するだけで、ライブラリを参照するすべてのプロジェクトでライブラリを更新できます。 これにより、一貫性が確保され、バージョンの不一致や更新漏れのリスクが軽減されます。

3.メリット:

  • 一元管理: NuGetパッケージは、ライブラリのバージョンと依存関係を一元管理する方法を提供します。

  • アップデートの容易さ:複数のプロジェクトにまたがるライブラリのアップデートが容易になり、信頼性が高まります。

  • 統合: NuGetはさまざまな開発ツールや環境と統合し、ライブラリの依存関係を管理するプロセスを合理化します。

ユニットテストでDRYを実装する:クラッシュコース

このセグメントでは、Tim Coreyが、DRY(Don't Repeat Yourself)原則を適用することで、単体テストをどのように強化できるかを示します。 特にユニットテストに焦点を当て、開発作業にDRY原則を導入する方法を示す。

初期テストのセットアップ

Timは、DLLのバグが原因で現在失敗しているユニットテストを実行することから始めます。 彼は、コードが主要なソリューションの外にある場合でも、問題を特定するためのユニットテストの重要性を強調しています。 コードは4文字の入力を想定していましたが、代わりにTimは3文字のファーストネームを渡し、それがソリューションに直接含まれていなくてもDLLファイルでクラッシュしました。

Applying Design Patterns In Csharp For Cleaner Code 4 related to 初期テストのセットアップ

バグに対処するためのコードのリファクタリング

ファーストネームの取り扱いに関する問題に対処するため、ティムはコードをリファクタリングします。 新しいクラス・ライブラリ・プロジェクトを作成することで、DRYが開発にどのように適用できるかを説明する(23:50)。 このアプローチにより、複数のオブジェクトに対する変更を一度だけ行い、修正を繰り返すことなく効果的にテストすることができます。

Applying Design Patterns In Csharp For Cleaner Code 5 related to バグに対処するためのコードのリファク�...

ユニットテストの追加

Tim は、クラス・ライブラリ・プロジェクトに EmployeeProcessorTest という新しいテスト・クラスを導入し、XUnit を使用してユニット・テストを設定します。 従業員IDを生成するテスト・メソッドの作成方法を示し、実際の値に依存するのではなく、依存関係をモックすることの重要性について議論する。

Applying Design Patterns In Csharp For Cleaner Code 6 related to ユニットテストの追加

テストメソッドの記述

Tim は、GenerateEmployeeID_ShouldCalculate というユニットテストメソッドを書いています。 彼は、異なるシナリオをテストするためにインラインデータで理論を設定し、メソッドが期待される結果を返すようにします。 また、出力を検証するためにAssert.Equalを使用する方法についても説明しています。

public class EmployeeProcessorTest
{
   [Theory]
   [InlineData("Timothy", "Corey", "TimoCore")]
   public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
   {
      // Arrange
      var processor = new EmployeeProcessor();

      // Act
      var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);

      // Assert
      Assert.Equal(expectedStart, actualStart);
   }
}
public class EmployeeProcessorTest
{
   [Theory]
   [InlineData("Timothy", "Corey", "TimoCore")]
   public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
   {
      // Arrange
      var processor = new EmployeeProcessor();

      // Act
      var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);

      // Assert
      Assert.Equal(expectedStart, actualStart);
   }
}

ユニットテストの実行

Timは、テスト条件と結果を制御するために、日付時刻値のような動的データをモックすることの重要性を強調しています。 動的な文字列を扱うことの難しさや、制御された値を使用してさまざまなシナリオをテストする方法について説明します。 次に彼はユニットテストを実行しますが、その前にテストを実行するために必要な二つのNuGetパッケージ xunit.runner.consolexunit.runner.visualstudio を追加します。

Applying Design Patterns In Csharp For Cleaner Code 7 related to ユニットテストの実行

1つのインラインデータに対してすべてのテストを正常に実行した後、出力は次のようになります:

Applying Design Patterns In Csharp For Cleaner Code 8 related to ユニットテストの実行

現在 (31:30) で、Timはもう一つのインラインデータを追加し、サブストリングの第二引数を expectedStart.Length に変更しました:

public class EmployeeProcessorTest
{
   [Theory]
   [InlineData("Timothy", "Corey", "TimoCore")]
   [InlineData("Tim", "Corey", "TimCore")]
   public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
   {
      var processor = new EmployeeProcessor();
      var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
      Assert.Equal(expectedStart, actualStart);
   }
}
public class EmployeeProcessorTest
{
   [Theory]
   [InlineData("Timothy", "Corey", "TimoCore")]
   [InlineData("Tim", "Corey", "TimCore")]
   public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
   {
      var processor = new EmployeeProcessor();
      var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
      Assert.Equal(expectedStart, actualStart);
   }
}

(32:05)でユニットテストを再度実行した後、2番目の理論でテストが壊れました:

Applying Design Patterns In Csharp For Cleaner Code 9 related to ユニットテストの実行

プライベート メソッドでコードを改善する

DRY原則に従うため、Timはコードをさらにリファクタリングし、DRYDemoLibraryの実際の EmployeeProcessor クラス内にプライベートメソッド GetPartOfName を作成します。 このメソッドは、名前の一部を抽出し、コードの再利用性と可読性を向上させます。 ティムは以下の変更を行いました:

public string GenerateEmployeeID(string firstName, string lastName)
{
   string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
   return employeeID;
}

private string GetPartOfName(string name, int numberOfCharacters)
{
   string output = name;

   if (name.Length > numberOfCharacters)
   {
      output = name.Substring(0, numberOfCharacters);
   }

   return output;
}
public string GenerateEmployeeID(string firstName, string lastName)
{
   string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
   return employeeID;
}

private string GetPartOfName(string name, int numberOfCharacters)
{
   string output = name;

   if (name.Length > numberOfCharacters)
   {
      output = name.Substring(0, numberOfCharacters);
   }

   return output;
}

ユニットテストの更新

Timは、部分文字列の予想される長さを修正するなど、コードの変更を反映するためにユニットテストを更新します。 また、これらのテストを実行することで、問題を迅速に特定し、コードが新しい要件を満たしていることを検証することができることを説明しています。 ティムは新しい理論を追加し、ユニットテストを実行して、出力が期待通りかどうかを検証します:

Applying Design Patterns In Csharp For Cleaner Code 10 related to ユニットテストの更新

.NET標準ライブラリで汎用性を拡大する

.NET標準ライブラリを作成する

クラスライブラリの汎用性を高めるために、ティム・コーリーは、.NET Frameworkクラスライブラリから.NET Standardクラスライブラリへの移行を推奨しています。 この変更により、以下のようなさまざまなプラットフォームでライブラリの互換性を保つことができます:

  • Windowsプラットフォーム:WinForms、WPF、コンソール・アプリケーション
  • クロスプラットフォーム: .NET Core、Xamarin(iOSおよびAndroid用)、Linux、macOS

.NET標準ライブラリの作成手順:

1.新しいプロジェクトの追加:ソリューションを右クリックし、新規プロジェクトの追加を選択します。 2.Select .NET Standard: .NET Frameworkクラスライブラリを選択する代わりに、.NET Standardを選択してください。 このライブラリタイプは、幅広いプラットフォームに対応しています。

Applying Design Patterns In Csharp For Cleaner Code 11 related to .NET標準ライブラリの作成手順:

3.コードの移行:既存のコード(EmployeeProcessorクラスなど)をコピーして、新しい.NET Standardライブラリに貼り付けます。 このプロセスでは微調整が必要になることがありますが、コアロジックは一貫しています。

.NET Standardに変換することで、さまざまなプラットフォームからライブラリにアクセスできるようになり、異なるアプリケーションタイプでのコードの繰り返しが減り、開発工数が削減されます。

コードとテストでの繰り返しを避ける

開発における繰り返しを減らす

ティム・コリーは、.NET Standardライブラリを採用することで、コードベースだけでなく、開発プロセスにおいてもコードの繰り返しを最小限に抑えることができると強調しています。 異なるプラットフォーム固有のプロジェクトでコードが重複する代わりに、複数の環境で動作する単一のライブラリにコードを集中させます。

メリット:

  • 統一されたコードベース:さまざまなプラットフォーム用の1つのコードベースは、コードの保守と更新に必要な労力を削減します。
  • 簡素化されたテスト:.NET Standardライブラリを使えば、単体テストを一度書くだけで、サポートされているすべてのプラットフォームに適用できるようになります。

テストとデバッグ:Timは、労力と繰り返しをさらに減らす方法として、ユニットテストを紹介します。 自動テストは、アプリケーションの各反復を手動でテストする必要なく、コードの正しさを検証します。

DRYを適用するためのヒント: 停止するタイミングを知る

ティム・コリーは、DRY(Don't Repeat Yourself)の原則に従うことが保守性の高いコードを書くために重要である一方、いつ、どこでそれを適用するかを知ることが重要であると強調しています。 すべてのシナリオで同じアプローチが必要なわけではありません。そこで、ティムの洞察にヒントを得た実践的なヒントをいくつかご紹介します:

1.コードビハインドやUIにコードを書かない:Timは、コードビハインドファイルやユーザーインターフェイスに直接ロジックを書かないようにアドバイスしています。 例えば、ビジネスロジックをフォームやボタンのクリックイベントに埋め込んではいけません。 そのようなロジックは、別のクラスやライブラリにまとめてください。 このように分離することで、きれいなアーキテクチャを維持し、異なるユーザーインターフェイス間でコードの再利用性を高めることができます。

2..NET標準ライブラリの活用:ライブラリを作成する際、Timは可能な限り.NET Frameworkライブラリの代わりに.NET Standardライブラリを使用することを提案します。 .NET Standardライブラリは汎用性が高く、.NET Core、Xamarinなど、さまざまなプラットフォームでコードを使用できます。 このアプローチは、コードの重複を減らし、コードの移植性を高めます。

3.プラットフォーム固有のコードを分離する: ファイル処理や構成管理など、プラットフォーム固有の要件により、.NET Standardライブラリに収まらないコードもあります。 ティムは、このような場合、.NET Standardコード用とプラットフォーム固有のコード用の2つのライブラリを作成することを推奨しています。 こうすることで、プラットフォーム固有のニーズに対応しながら、コアロジックを再利用することができます。

4.ユニットテストを重視する: Timは、コードにユニットテストを書くことを強く推奨しています。 ユニットテストは、バグを早期に特定し、コードが期待通りに動作することを保証するのに役立ちます。 アプリケーション全体を手動でテストすることなく、変更をすばやく検証できるため、デバッグプロセスを大幅にスピードアップできます。

5.プロジェクトの規模を考慮する:非常に小規模なプロジェクトや実験的なプロジェクトでは、別個のライブラリや広範なユニットテストを作成するオーバーヘッドは必要ないかもしれないとTimは認めています。 しかし、小規模なプロジェクトは、時間とともに成長し、進化することが多いため、本番アプリケーションの場合は、クリーンなアーキテクチャとユニットテストから始めることが望ましいです。

これらのヒントに従うことで、DRY原則を効果的に適用しながら、コードの再利用と保守性の必要性と実用的な考慮事項のバランスをとることができます。

結論

デザインパターンによるDRY原則の習得は、クリーンで保守性の高いC#コードを書くために不可欠です。 ティム・コリーが示したように、DRYを効果的に適用するには、再利用可能なメソッドを作成し、クラスライブラリを活用し、より幅広い互換性のために.NET Standardを採用する必要があります。 これらのプラクティスをいつ、どのように適用するかを理解することで、コードの品質と柔軟性を大幅に向上させることができます。

より深い洞察については、このトピックに関するティム・コーリーのビデオこちらをご覧ください。 ティムの最新コンテンツについては、YouTubeチャンネルをご覧ください。

Hero Worlddot related to DRY原則をマスターする:C#でデザインパターンを適用してコードをクリ...
Hero Affiliate related to DRY原則をマスターする:C#でデザインパターンを適用してコードをク�...

好きなことを共有することで収入を増やす

.NET、C#、Java、Python、またはNode.jsを使用する開発者向けのコンテンツを作成しますか?あなたの専門知識を副収入に変えましょう!

アイアンサポートチーム

私たちは週5日、24時間オンラインで対応しています。
チャット
メール
電話してね