型制御を強化します。典型的なC#プロジェクトでは、弱い型指定の未承諾要素がありますか?

問題



私たちは、C#などの言語について、静的かつ静的に型付けされたものについて話すことに慣れています。 もちろん、これは真実であり、多くの場合、ある言語エンティティに対して示すタイプは、そのタイプのアイデアをうまく表現しています 。 しかし、習慣から外れて(「誰もがこれを行う」)、「宣言された型」の「望ましい型」の完全に正しくない表現に我慢する場合の広範な例があります。 最も顕著なのは、代替手段がないヌル値を備えた参照型です。

アクティブな開発の年の現在のプロジェクトでは、NullReferenceExceptionはありませんでした。 これは、以下に説明する手法を適用した結果であると合理的に信じることができます。


コードを考えてみましょう:



public interface IUserRepo { User Get(int id); User Find(int id); }
      
      





このインターフェイスには追加のコメントが必要です。「Getは常にnullではないが、オブジェクトが見つからない場合は例外をスローします。 また、検索ではなく検索はnull "を返します。 これらのメソッドの作成者が暗示している「望ましい」戻り値の型は異なります。「必須ユーザー」と「未定ユーザー」です。 そして、「宣言された」型はまったく同じです。 言語がこの違いを明確に表現することを強制しない場合、これは私たちが自分のイニシアチブでそれを行うことができない、すべきではないことを意味しません。



解決策



関数型言語、たとえばF#には、標準型FSharpOption <T>があります。これは、任意の型のコンテナーを表すだけで、Tの値が1つあるか、存在しない場合があります。 関数型言語に精通しているさまざまなコーディングスタイルの支持者を含め、使いやすいようにこのタイプからどのような可能性を持ちたいか考えてみましょう。

この架空のタイプを考えると、次の形式でリポジトリを書き換えることができます。



 public interface IUserRepo { User Get(int id); Maybe<User> Find(int id); }
      
      





最初のメソッドがまだnullを返すことができるように、すぐに予約してください。 これを言語レベルで禁止する簡単な方法はありません。 ただし、これは少なくとも開発チームの合意レベルで行うことができます。 そのような事業の成功は人々にかかっています。 私のプロジェクトでは、このような合意が受け入れられ、順調に監視されています。

もちろん、さらに進んで、ソースコードにnullキーワードが存在するかどうかをアセンブリプロセスでチェックすることもできます(このルールに指定された例外があります)。 しかし、これはまだ必要ありませんでした。内部の規律で十分です。

一般に、たとえば、PostSharpなどのAOPソリューションを介してContract.Ensure(Contract.Result <T>()!= Null)をすべての適切なメソッドに強制するなど、さらに先へ進むことができます。規律が低いと、不運なヌルを返すことはできません。



インターフェイスの新しいバージョンは、Findがオブジェクトを見つけられない可能性があることを明示的に宣言します。その場合、Maybe <User> .Nothingを返します。 この場合、結果のnullをチェックすることを忘れることはできません。 このようなリポジトリの使用についてさらに考えます。



 //      null var user = repo.Find(userId); //    User,  Maybe<User> var userName = user.Name; //  ,  Maybe  Name var maybeUser = repo.Find(userId); //    , string userName; if (maybeUser.HasValue) //           { var user = maybeUser.Value; userName = user.Name; } else userName = "unknown";
      
      





このコードは、nullチェックを使用して記述する場合と似ていますが、ifの条件が少し異なっているように見えます。 ただし、このようなチェックを絶えず繰り返すと、最初にコードが乱雑になり、操作の本質がわかりにくくなり、次に、開発者が退屈になります。 したがって、ほとんどの標準操作に既製のメソッドを使用すると非常に便利です。 以前の流なスタイルのコードは次のとおりです。



 string userName = repo.Find(userId).Select(u => u.Name).OrElse("unknown");
      
      





関数型言語と表記法に近い人のために、完全に「関数型」スタイルをサポートできます。



 string userName = (from user in repo.Find(userId) select user.Name).OrElse("unknown");
      
      





または、例はより複雑です:



 ( from roleAProfile in provider.FindProfile(userId, type: "A") from roleBProfile in provider.FindProfile(userId, type: "B") from roleCProfile in provider.FindProfile(userId, type: "C") where roleAProfile.IsActive() && roleCProfile.IsPremium() let user = repo.GetUser(userId) select user ).Do(HonorAsActiveUser);
      
      





その命令的な同等物で:



 var maybeProfileA = provider.FindProfile(userId, type: "A"); if (maybeProfileA.HasValue) { var profileA = maybeProfileA.Value; var maybeProfileB = provider.FindProfile(userId, type: "B"); if (maybeProfileB.HasValue) { var profileB = maybeProfileB.Value; var maybeProfileC = provider.FindProfile(userId, type: "C"); if (maybeProfileC.HasValue) { var profileC = maybeProfileC.Value; if (profileA.IsActive() && profileC.IsPremium()) { var user = repo.GetUser(userId); HonorAsActiveUser(user); } } } }
      
      





また、少なくとも次の形式では、Maybe <T>とそのかなり近い相対物であるIEnumerable <T>との統合が必要です。



 var admin = users.MaybeFirst(u => u.IsAdmin); //  FirstOrDefault(u => u.IsAdmin); Console.WriteLine("Admin is {0}", admin.Select(a => a.Name).OrElse("not found"));
      
      





上記の「夢」から、Maybeタイプに何が欲しいのかは明らかです





プロジェクトにすばやく含めるためにNugetが提供できるソリューションを検討し、上記の基準に従って比較します。



Nugetパッケージ名とタイプタイプ ハスバリュー 価値 フルエンタピ LINQサポート IEnumerableとの統合 ノートとソースコード
オプション、クラス そこにある いいえ、パターンマッチングのみ 最小 いや いや github.com/tejacques/Option
Strilanc.Value.May、構造体 そこにある いいえ、パターンマッチングのみ 金持ち そこにある そこにある 5月にnullを有効な値として受け入れます

github.com/Strilanc/May
オプション、構造体 そこにある そこにある 平均的 そこにある そこにある どちらのタイプも利用可能です。

github.com/davidsidlinger/options
Nevernullクラス そこにある そこにある 平均的 いや いや github.com/Bomret/NeverNull
機能的、たぶん、構造体 そこにある そこにある 金持ち そこにある そこにある github.com/AndreyTsvetkov/Functional.Maybe
たぶんタイプなし - - 最小 いや - 拡張メソッドはプレーンなnullで動作します

github.com/hazzik/Maybe

upd:同様のアプローチと詳細な説明を含むmezastelのスクリーンキャストです: www.techdays.ru/videos/4448.html
WeeGems.Options、構造体 そこにある そこにある 最小 いや いや 他の機能ユーティリティもあります:メモ化、機能の部分使用

bitbucket.org/MattDavey/weegems


私のプロジェクトがそのパッケージを成長させたのはたまたま起こった、それは上記の一つです。



この表から、最も「軽量」で低侵襲なソリューションはhazzikからである可能性があり、APIを変更する必要はありませんが、同じifsを取り除くためにいくつかの拡張メソッドを追加するだけです。 しかし、悲しいかな、それは忘れっぽいプログラマがNullReferenceExceptionを受け取ることを保護しません。



最も豊富なパッケージは、Strilanc.Value.Maybe(特にここでは、著者が(null).ToMaybe()がMaybe.Nothingと同じではないことを決定した理由)、Functional.Maybe、Optionsです。



味を選択してください。 一般に、私はもちろん、Microsoftの標準ソリューションと、C#、タプルなどの機能タイプが必要です:) 。 待って、見て。



UPD:同志aikixd反対の記事を書きました。



All Articles