web-dev-qa-db-ja.com

ローカルスコープ変数の不変性を考慮する必要があります

私は次のコードを持っています:

const string endPoint = @"foo{0}?pageNum={1}&itemsPerPage={2}";
const int itemsPerPage = xxx;
InvoiceCollection response = await _apiClient
    .GetAsync<InvoiceCollection>(string.Format(endPoint, _apiClient.OrgId, 1, itemsPerPage));

if (response?.TotalCount > itemsPerPage)
{
    var allInvoices = (await DoPagination(endPoint,response.TotalCount, itemsPerPage))
        .SelectMany(i => i.Invoices);
    response.Invoices = (response.Invoices ?? Enumerable.Empty<Invoice>()).Concat(allInvoices);
}

return response;

ここでのロジックはAPIを呼び出すことです。結果の総数が事前定義されたitemsPerPageを超えた場合、他のすべてのページを呼び出してallInvoicesで結果を取得し、最初の応答と組み合わせて返します。すべての結果を一緒に。 IEnumerable.Concatを使用すると、新しいオブジェクトが作成され、responseに割り当てられます。

「回線からHttpClientに送信されるすべての応答は不変である必要がある」と明記したコードレビューコメントを受け取りました。コメントは特にresponse変数に関するものですが、ローカルスコープの変数であり、可変または不変であっても実行に影響を与えないため、同意しません。そのため、コードが壊れるシナリオを予測できません。

私の議論は有効ですか?

更新:

以下は私のInvoiceCollectionクラスです:

 public class InvoiceCollection
 {
     [JsonProperty("results")]
     public IEnumerable<Invoice> Invoices { get; set; }
     public int TotalCount { get; set; }
 }

responseは、混乱を招く悪い名前です。ここで、応答はfisrtCollectionまたはそれに類似した名前に変更でき、APIクライアントに関する情報は保持されません。

1
Anjo

査読者は正しい:

  1. 変数を実際の方法で変更すると、コードの説明が難しくなります。実際、コードは非常に不可解です。

    あなたの質問の方法をほとんど使用していないと想像してください。問題が発生したため、ログを実際の請求書と比較します。コードを見ると、最初はonly HTTPクエリが3行目と4行目にあるようです。ただし、応答isが実際の応答である場合とそうでない場合があります。

    さらに、responseには、URIなどのリクエストに関する情報が含まれる場合があります。この場合、全体が特に誤解を招くようになります。リソースへのURIと完全に異なるリソースの表現を含む応答オブジェクトがあります。これは決して大丈夫ではありません。

  2. responseの背後にあるクラスはあなたに属していません。それは特定のインターフェースを持つ別のクラスです。そもそもInvoiceプロパティを変更できる理由は明らかではありませんが、このクラスの作成者がこの間違いを見つけて後で修正する可能性があります。彼がそうするとき、それはあなたのコードを壊します。

    これは、応答クラスの作成者がそれをリファクタリングすることをより困難にしたことを意味します。作成者がコードを修正するのに十分な時間がない場合(または修正を依頼する場合)、リファクタリングをキャンセルするだけのリスクがあります。したがって、Invoiceを変更することで、技術的負債の削減を困難にしました。

4

コメントがどこから来たのか理解できたと思います。あなたの応答オブジェクトはどちらもapiからの応答ですand返される応答です。

そのため、その応答の一部を直接変更してから返すのは奇妙に思えます。おそらく、何らかの形で応答が無効になる可能性があります。あなたがresponse.TotalNumberプロパティを更新するのを忘れたか、または何かしたとしましょう。

より一般的なアプローチは、単純にAPI応答を転送するのではなく、API応答から異なるタイプの戻り変数を設定することだと思います。

あなたがAPIクラスをラップすることと同等のことをしているなら、私は同意します、私は同意します、応答変数を不変にするのはやり過ぎのようです問題を引き起こしています。

しかしながら。技術的に言えば、このようにプロパティを直接変更する、不変にする、または安全な方法でプロパティを変更するメソッドを提供することによってオブジェクトを破壊できる場合は、優れています。

全体として、可変の「データ型」、つまりメソッドのないクラスを、C#の不変の構造体ではなくAPIから返すのが一般的だと思います。おそらく完璧ではありませんが、それはより簡単で受け入れられています。

このような単純なクラスは、壊れやすいものを持たないだけでなく、変更可能であるため、受信するAPIデータを簡単に逆シリアル化できます。

それ以外の場合は、カスタムコンストラクタまたはファクトリを作成する必要があります。それは苦痛でしょう。

2
Ewan

昨日とまったく同じコードを実行しました。ここにあります

private async Task<IEnumerable<File>> ListRemoteFilesAsync(string category)
{
    FilesResponse response;
    int offset = -1;
    var files = new List<File>();

    do
    {
        offset++;
        response = await _client.ListFilesByAsync(category, offset*PageSize, PageSize);
        files.AddRange(response.Files);
    } while (response.Pagination.Limit * (offset + 1) < response.Pagination.Total);

    return files;
}

ここでの違いは、API呼び出しからの戻り値を決して公開しないことです。そして、私は私が返す新しいコレクションを作成します。

これはプライベートメソッドであり、APIに依存するFileタイプはプライベートクラススコープの外部に公開されないことに注意してください。

2
Anders