コードは生きていて死んでいます。 パート3。 テキストとしてコード

プログラムに付随するには、コードを読む必要があります。コードを読むのが簡単であればあるほど、自然言語のように見えます。それから、すぐに主なものを掘り下げて集中します。







最後の2つの記事では、慎重に選択された単語が書かれている内容の本質をよりよく理解するのに役立つことを示しましたが、すべての単語はそれ自体と文の一部の2つの形式で存在するため、それらについてのみ考えるだけでは不十分です。 Thread.CurrentThread



のコンテキストで読み取るまで、 CurrentThread



繰り返しはまだ繰り返されていません。







したがって、音符とシンプルなメロディーに導かれ、音楽とは何かを見ることになります。







サイクルの目次



  1. オブジェクト
  2. アクションとプロパティ
  3. テキストとしてコード


テキストとしてコード



最も流fluentなインターフェイスは、内部よりも外部に重点を置いて設計されているため、読みやすくなっています。 もちろん、無料ではありません。コンテンツはある意味で弱くなっています。 だから、 FluentAssertions



パッケージにFluentAssertions



ように書くFluentAssertions



ができるとしFluentAssertions



う: (2 + 2).Should().Be(4, because: "2 + 2 is 4!")



、そして、読書に比べて、見た目はエレガントですが、 Be()



むしろ、 error



またはerrorMessage



パラメーターがerrorMessage



です。







私の意見では、そのような免除は重要ではありません。 コードがテキストであることに同意すると、そのコンポーネントはそれ自体に属しなくなります。それらは現在、一種の普遍的な「エーテル」の一部です。







このような考慮事項がどのように経験になるかを例で示します。







Interlocked





メソッドとパラメーターの明確な名前を使用して、 Interlocked.CompareExchange(ref x, newX, oldX)



Atomically.Change(ref x, from: oldX, to: newX)



に変更したInterlocked



のケースを思い出させてください。







ExceptWith





タイプISet<>



ISet<>



というメソッドがExceptWith



ます。 items.ExceptWith(other)



ような呼び出しを見ると、何が起きているかすぐにはitems.ExceptWith(other)



ません。 ただし、すべて記述する必要があるため、 items.Exclude(other)



を記述する必要があります。







GetValueOrDefault





Nullable<T>



x.Value



する場合、 x



null



場合、 x.Value



を呼び出すと例外がスローされnull



。 それでもValue



を取得する必要がある場合x.GetValueOrDefault



x.GetValueOrDefault



使用されます。これはValue



またはデフォルト値のいずれかです。 かさばる。







「またはx、またはデフォルト値」という表現は、短くエレガントなx.OrDefault



ます。







 int? x = null; var a = x.GetValueOrDefault(); // ,  .  . var b = x.OrDefault(); //  —  ,   . var c = x.Or(10); //     .
      
      





OrDefault



Or



覚えておく価値のあることが1つあり.?



演算子を使用するときは.?



x?.IsEnabled.Or(false)



(x?.IsEnabled).Or(false)



ようなものを書くことはできません(言い換えると、 null



左側にある場合、 .?



演算子は右側全体をキャンセルしnull



)。







IEnumerable<T>



使用する場合、テンプレートを適用できます。







 IEnumerable<int> numbers = null; // . var x = numbers ?? Enumerable.Empty<int>(); //   . var x = numbers.OrEmpty();
      
      





Math.Min



およびMath.Min





Or



使用したアイデアは、数値型に発展させることができます。 a



b



から最大数を取得するとします。 それからMath.Max(a, b)



またはa > b ? a : b



a > b ? a : b



どちらのオプションも非常によく似ていますが、それでも、自然言語のようには見えません。







次のように置き換えることができます: a.Or(b).IfLess()



- b



より小さい 場合 a



b



または b



取ります。 そのような状況に適しています:







 Creature creature = ...; int damage = ...; //   . creature.Health = Math.Max(creature.Health - damage, 0); // Fluent. creature.Health = (creature.Health - damage).Or(0).IfGreater(); //   : creature.Health = (creature.Health - damage).ButNotLess(than: 0);
      
      





string.Join





場合によっては、要素をスペースまたはコンマで区切って、シーケンスを文字列に組み立てる必要があります。 これを行うには、たとえば次のようにstring.Join



使用しますstring.Join



string.Join(", ", new [] { 1, 2, 3 }); // "1, 2, 3".



string.Join(", ", new [] { 1, 2, 3 }); // "1, 2, 3".









単純な「コンマの数を分割する」は、突然「リストの各数字にコンマを付ける」になります -これは確かにテキストとしてのコードではありません。







 var numbers = new [] { 1, 2, 3 }; // ""    —  . var x = string.Join(", ", numbers); //    — ! var x = numbers.Separated(with: ", ");
      
      





Regex





ただし、 string.Join



は、 Regex



誤って他の目的で使用されることRegex



あるのに比べて、まったく無害です。 シンプルで読みやすいテキストでうまくいく場合は、何らかの理由で、複雑すぎるエントリが推奨されます。







簡単なものから始めましょう-文字列が数字のセットを表すことを決定します:







 string id = ...; // ,  . var x = Regex.IsMatch(id, "^[0-9]*$"); // . var x = id.All(x => x.IsDigit()); // ! var x = id.IsNumer();
      
      





別のケースは、シーケンスの文字列に少なくとも1つの文字があるかどうかを調べることです。







 string text = ...; //   . var x = Regex.IsMatch(text, @"["<>[]'"); //   . ( .) var x = text.ContainsAnyOf('"', '<', '>', '[', ']', '\''); //  . var x = text.ContainsAny(charOf: @"["<>[]'");
      
      





タスクが複雑になるほど、ソリューションの「パターン」は難しくなります。 "HelloWorld"



レコードをいくつかの単語"Hello World"



に分割するには、単純なアルゴリズムではなくモンスターが必要です。







 string text = ...; //   -   . var x = Regex.Replace(text, "([az](?=[AZ])|[AZ](?=[AZ][az]))", "$1 "); //  . var x = text.PascalCaseWords().Separated(with: " "); //   . var x = text.AsWords(eachStartsWith: x => x.IsUpper()).Separated(with: " ");
      
      





間違いなく、正規表現は効果的で普遍的ですが、一目で何が起こっているのかを理解したいと思います。







Substring



Remove





行の先頭または末尾から一部を削除する必要がある場合があります。たとえば、 .txt



拡張子.txt



(ある場合)などです。







 string path = ...; //    . var x = path.EndsWith(".txt") ? path.Remove(path.Length - "txt".Length) : path; //   . var x = path.Without(".exe").AtEnd;
      
      





繰り返しになりますがアクションアルゴリズムはなくなり、 最後に.exe拡張子のない単純な行が残されました







Without



メソッドは特定のWithoutExpression



を返す必要があるWithoutExpression



path.Without("_").AtStart



およびpath.Without("Something").Anywhere



さらにpath.Without("Something").Anywhere



ます。 同じ単語で別の式を構築できることも興味深いです: name.Without(charAt: 1)



-インデックス1の文字を削除し、新しい行を返します(順列の計算に役立ちます)。 また読みやすい!







Type.GetMethods





リフレクションを使用して特定のタイプのメソッドを取得するには、次を使用します。







 Type type = ...; //   `Get` ,   `|`.     . var x = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // ,  . `Or`   , . var x = type.Methods(_ => _.Instance.Public.Or.NonPublic);
      
      





GetFields



GetProperties



についても同じことがGetProperties



ます。)







Directory.Copy





多くの場合、フォルダーとファイルの操作はすべてDirectoryUtils



FileSystemHelper



一般化されFileSystemHelper



。 ファイルシステムのバイパス、クリーニング、コピーなどを実装します。 しかし、ここでもっと良いものを思いつくことができます!







「すべてのファイルを「D:\ Source」から「D:\ Target」にコピー」というテキストをコード"D:\\Source".AsDirectory().Copy().Files.To("D:\\Target")



AsDirectory()



- string



からDirectoryInfo



を返し、 Copy()



-式を作成するための一意のAPIを記述するCopyExpression



インスタンスを作成します( Copy().Files.Files



呼び出すことはできませんCopy().Files.Files



など)。 次に、すべてのファイルではなく、いくつかのファイルをコピーする機会が開きます: Copy().Files.Where(x => x.IsNotEmpty)









GetOrderById





2番目の記事では、 IUsersRepository.GetUser(int id)



は冗長であり、より良いのはIUsersRepository.User(int id)



であるとIUsersRepository.User(int id)



。 したがって、同様のIOrdersRepository



IOrdersRepository



GetOrderById(int id)



ではなくOrder(int id)



ます。 ただし、別の例では、そのようなリポジトリの変数は_ordersRepository



ではなく、単に_orders



と呼ばれることが提案され_orders









両方の変更はそれ自体で有効ですが、読み取りコンテキストでは_orders.Order(id)



されません_orders.Order(id)



呼び出しは冗長に見えます。 _orders.Get(id)



は可能ですが、注文は失敗します。 そのような識別子を持つもののみを指定します。 したがって、 OneOne



です。







 IOrdersRepository orders = ...; int id = ...; //   . var x = orders.GetOrderById(id); //      : var x = orders.Order(id); //     ,    . var x = orders.One(id); //    : var x = orders.One(with: id);
      
      





GetOrders





IOrdersRepository



などのオブジェクトでは、他のメソッドAddOrder



RemoveOrder



GetOrders



がよく見られます。 最初の2回の繰り返しはなくなり、 Add



およびRemove



が取得されます_orders.Add(order)



対応するエントリ_orders.Add(order)



および_orders.Remove(order)



)。 GetOrders



を使用すると、 Orders



名前を少し変更するGetOrders



が難しくなります。 見てみましょう:







 IOrdersRepository orders = ...; //   . var x = orders.GetOrders(); //  `Get`,  . var x = orders.Orders(); // ! var x = orders.All();
      
      





古い_ordersRepository



、リポジトリで作業しているため、 GetOrders



またはGetOrderById



呼び出しの繰り返しはそれほど目立たないことに注意してください。







One



All



などの名前は、多くを表す多くのインターフェースに適しています。 よく知られているGitHub APIの実装gitHub.Repository.GetAllForUser("John")



では、すべてのユーザーリポジトリの取得はgitHub.Repository.GetAllForUser("John")



ように見えますが、より論理的ですgitHub.Users.One("John").Repositories.All



この場合、 1つのリポジトリを取得すると、それぞれ、明らかなgitHub.Users.One("John").Repositories.One("Repo")



ではなく、 gitHub.Repository.Get("John", "Repo")



になりgitHub.Users.One("John").Repositories.One("Repo")



。 2番目のケースは長く見えますが、内部的に一貫性があり、プラットフォームを反映しています。 さらに、拡張メソッドを使用して、 gitHub.User("John").Repository("Repo")



短縮できます。







Dictionary.TryGetValue





辞書から値を取得することは、キーが見つからない場合に必要ことだけが異なるいくつかのシナリオに分けられます。









XがXでない場合、XまたはYを取ります」という点で収束します。 さらに、 _ordersRepository



の場合のように、ディクショナリ変数をitemsDictionary



ではなくitems



と呼びitems









次に、 「Xを取る」部分では、 items.One(withKey: X)



形式の呼び出しitems.One(withKey: X)



理想的で、4つのitems.One(withKey: X)



持つ構造体を返します







 Dictionary<int, Item> items = ...; int id = ...; //  ,   : var x = items.GetValueOrDefault(id); var x = items[id]; var x = items.GetOrAdd(id, () => new Item()); //    : var x = items.One(with: id).OrDefault(); var x = items.One(with: id).Or(Item.Empty); var x = items.One(with: id).OrThrow(withMessage: $"Couldn't find item with '{id}' id."); var x = items.One(with: id).OrNew(() => new Item());
      
      





Assembly.GetTypes





アセンブリにT



型の既存のインスタンスをすべて作成してみましょう。







 // . var x = Assembly .GetAssembly(typeof(T)) .GetTypes() .Where(...) .Select(Activator.CreateInstance); // "" . var x = TypesHelper.GetAllInstancesOf<T>(); // . var x = Instances.Of<T>();
      
      





したがって、静的クラスの名前が式の始まりである場合があります。







NUnitで類似したものを見つけることができます: Assert.That(2 + 2, Is.EqualTo(4))



-自給自足型として考えられていました。







Argument.ThrowIfNull





次に、前提条件チェックを見てみましょう。







 //  . Argument.ThrowIfNull(x); Guard.CheckAgainstNull(x); // . x.Should().BeNotNull(); // ,  ...  ? Ensure(that: x).NotNull();
      
      





Ensure.NotNull(argument)



-素晴らしいですが、完全に英語ではありません。 もう1つは、上記のEnsure(that: x).NotNull()



です。 そこにしかできなかったら...







ところで、できます! Contract.Ensure(that: argument).IsNotNull()



を記述using static



Contract



タイプusing static



インポートusing static



。 したがって、すべての種類のEnsure(that: type).Implements<T>()



Ensure(that: number).InRange(from: 5, to: 10)



などがEnsure(that: number).InRange(from: 5, to: 10)



ます。







静的インポートのアイデアは多くの扉を開きます。 ための美しい例: items.Remove(x)



代わりにRemove(x, from: items)



書き込みます。 しかし、興味深いのは、関数を返すenum



とプロパティの削減です。







 IItems items = ...; // . var x = items.All(where: x => x.IsWeapon); //  . // `ItemsThatAre.Weapons`  `Predicate<bool>`. var x = items.All(ItemsThatAre.Weapons); // `using static`  !  . var x = items.All(Weapons);
      
      





エキゾチックなFind





C#7.1以降では、 Find(1, @in: items)



ではなくFind(1, in items)



を記述できFind(1, @in: items)



Find<T>(T item, in IEnumerable<T> items)



として定義されていFind<T>(T item, in IEnumerable<T> items)



。 この例は実用的ではありませんが、すべての手段が読みやすさのために苦労していることを示しています。







合計



このパートでは、コードを読みやすくするためのいくつかの方法を検討しました。 それらはすべて一般化できます:









したがって、外部および想定されるものが前面に表示されます。 最初に考えられ、コードがテキストとして読み取られる限り、その具体的な化身が考えられますが、それほど重要ではありません。 裁判官は言語ほど味ではないということになる-彼はitem.GetValueOrDefault



item.OrDefault



違いを決定します。







エピローグ



どちらが良い、明確であるが、動作する方法ではないか、動作しているが理解できないか? 家具や部屋のない雪のように白い城、またはルイ14世のスタイルのソファのある小屋? エンジンのない贅沢なヨット、または誰も使用できない量子コンピューターを搭載したうめき台船?







Polarの回答は適合しませんが、 「中間のどこかに」もあります。







私の意見では、両方の概念は密接に結びついています。本の表紙を慎重に選択し、テキストの誤りを疑いで調べ、その逆も同様です。 ビートルズに低品質の音楽再生させたくありませんが、 MusicHelperと呼ばれるべきです。







別のことは、開発プロセスの一部として単語に取り組むことは過小評価された異常なことであり、したがって、何らかの極端な判断が依然として必要であるということです。 このサイクルは、フォームと画像の極端です。







ご清聴ありがとうございました!







参照資料



もっと多くの例を見ることに興味がある人は、私のGitHubで、例えばPocket.Common



ライブラリで見つけることができます。 (世界的および普遍的な使用ではありません)








All Articles