C#リーダブルコード #15_比較演算子はできるだけクラスにさせる

当サイトではアフィリエイト広告を利用しています。

リーダブルコード
Subway Station in Munich, Germany. Train coming in. Strong Motion Blur on Train and People, no recognizable Persons.

今回は「比較演算子はできるだけクラスにさせる」というお話をしていきます。少しオブジェクト指向的内容にもなってしまいますが,「比較」というテーマでもあるため,解説させていただきます。

GOOD:比較演算子はできるだけクラスにさせる

まず,次のようなメソッドがあったとします。引数がProductと,intのstockUseCountです。stockUseCountは「どのくらい在庫が必要か?」という意味とします。

//GOOD:比較演算子はできるだけクラスにさせる
private void 比較演算子はできるだけクラスにさせる(
    Product product,
    int stockUseCount)
{
           
}

NOTBAD:クライアントコードで比較する

それではクライアントコードで比較する例を見ていきます。クライアントコードとは,使う側のコードという意味です。今回の例でいえばProductクラスのStock等の値を,Productクラス自身で使うのではなく,Productを保持している側が使っているような状況を,Productクラスのクライアントコードという言い方をします。Productクラスを使っている側ということです。

例えば次のようなコードを見てください。

private void 比較演算子はできるだけクラスにさせる(
    Product product,
    int stockUseCount)
{
    //NOTBAD:クライアントコードで比較する
    if (product.Stock >= stockUseCount) //①
    {
        Console.WriteLine($"処理...{product.Stock}");
    }
}

①の部分でproduct.Stockの値とstockUseCountを比較しています。

ポイントは比較演算子「>=」の部分です。

比較演算子は「>=」でなくても,「>」でも「==」でもなんでも同じなのですが,要するに比較しているということが大事です。これをクライアントコード側で比較する場合,要するに,Productクラスではなく,Productクラスを使っている側でproduct.Stock>= stockUseCountのように比較するコードを書いてしまうと,こういった比較がいろいろなクライアントコードに散らばってしまいます。つまり,Productクラスを使うクラスというのは,商品マスター画面や受注画面,発注画面など,いたるところで使われる可能性があり,それぞれのクライアントコードで,在庫チェックの方法を「product.Stock>= stockUseCount」のように直接記述してしまうと,とあるクライアントコードでは「product.Stock> stockUseCount」のように「>=」の部分を「>」と間違えて書いてしまうクライアントコードも発生する可能性があります。ですので,こういった比較をクライアントコード側にさせるということは,非常にバグを生む可能性が高くなります。こういった比較演算子などを各クライアントコードに散らばって書かないようにするために,1か所にまとめてしまうというのが良いでしょう。

GOOD:比較演算子はクラスに任せる

比較演算子をクライアントコードに散らばって記述しないようにするために,その値を保持しているクラスに判断させてしまうのが一番良いやり方です。オブジェクト指向でいうところのカプセル化という発想がこれですが,ここではあまり難しく考えずに,値を持っているクラスが比較を担当すると考えてください。

ProductクラスにCheckStockを追加

今回の場合,ProductのPriceの値を比較したいわけなので,Priceの持ち主はProductクラスということになります。ですので,Productクラスの中で,Priceの比較をします。今回は在庫チェックがしたいわけですから,CheckStockなどという名前のメソッドを作成し,その中で①のように比較します。

public sealed class Product
{
    public Product(int productId,
                    string productName,
                    int price,
                    int stock)
    {
        ProductId = productId;
        ProductName = productName;
        Price = price;
        Stock = stock;
    }

    public int ProductId { get; }
    public string ProductName { get; }
    public int Price { get; }
    public int Stock { get; set; }

    internal bool CheckStock(int stockUseCount)
    {
        return Stock >= stockUseCount; //①
    }
}

このようにPriceを保持しているクラスで在庫チェックを行うことで,クライアントコードからすれば,処理を呼び出すだけで,在庫チェックができるので,クライアントコード側で「>=」で比較するのか「>」で比較するのかを悩むことなく,CheckStockを呼び出すだけで解決します。

クライアントコードからCheckStockを呼び出す

クライアントコード側は,次の①のようにCheckStockメソッドを呼び出します。

//GOOD:比較演算子はできるだけクラスにさせる
private void 比較演算子はできるだけクラスにさせる(
    Product product,
    int stockUseCount)
{
    //NOTBAD:クライアントコードで比較する
    if (product.Stock >= stockUseCount)
    {
        Console.WriteLine($"処理...{product.Stock}");
    }

    //GOOD:比較演算子はクラスに任せる
    if (product.CheckStock(stockUseCount)) //①
    {
        Console.WriteLine($"処理...{product.Stock}");
    }
}

このようにすれば,どのクライアントコードが呼び出してもStockとの比較は「>=」で行われるので,アプリケーション全体が統一したロジックで安定します。少しオブジェクト指向的お話になってしまいましたが,こういったチェック処理等はよく出てくると思うので,比較やチェックは値を保持しているクラスにやらせるということを覚えておいてください。