プログラムに付随するには、コードを読む必要があります。コードを読むのが簡単であればあるほど、自然言語のように見えます。それから、すぐに主なものを掘り下げて集中します。
最後の2つの記事では、慎重に選択された単語が書かれている内容の本質をよりよく理解するのに役立つことを示しましたが、すべての単語はそれ自体と文の一部の2つの形式で存在するため、それらについてのみ考えるだけでは不十分です。 Thread.CurrentThread
のコンテキストで読み取るまで、 CurrentThread
繰り返しはまだ繰り返されていません。
したがって、音符とシンプルなメロディーに導かれ、音楽とは何かを見ることになります。
サイクルの目次
- オブジェクト
- アクションとプロパティ
- テキストとしてコード
テキストとしてコード
最も流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)
は可能ですが、注文は失敗します。 そのような識別子を持つもののみを指定します。 したがって、 OneはOne
です。
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
辞書から値を取得することは、キーが見つからない場合に必要なことだけが異なるいくつかのシナリオに分けられます。
- エラーをスローします(
dictionary[key]
); - デフォルト値を返します(実装されていませんが、多くの場合
TryGetValue
またはTryGetValue
)。 - 他の何かを返します(実装されていませんが、
GetValueOrOther
を期待します)。 - 指定された値をディクショナリに書き込み、それを返します(実装されていませんが、
GetOrAdd
がGetOrAdd
されます)。
式は 「 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)
。 この例は実用的ではありませんが、すべての手段が読みやすさのために苦労していることを示しています。
合計
このパートでは、コードを読みやすくするためのいくつかの方法を検討しました。 それらはすべて一般化できます:
- 式の一部としての名前付きパラメーターは、
Should().Be(4, because: "")
、Atomically.Change(ref x, from: oldX, to: newX)
です。 - 技術的な詳細の代わりに単純な名前は、
Separated(with: ", ")
、Exclude
です。 - 変数の一部としてのメソッドは、
x.OrDefault()
、x.Or(b).IfLess()
、orders.One(with: id)
、orders.All
です。 - 式の一部としてのメソッドは
path.Without(".exe").AtEnd
です。 - 式の一部としての型は
Instances.Of
、Is.EqualTo
です。 - 式の一部としてのメソッド(
using static
)は、items.All(Weapons)
Ensure(that: x)
、items.All(Weapons)
です。
したがって、外部および想定されるものが前面に表示されます。 最初に考えられ、コードがテキストとして読み取られる限り、その具体的な化身が考えられますが、それほど重要ではありません。 裁判官は言語ほど味ではないということになる-彼はitem.GetValueOrDefault
とitem.OrDefault
違いを決定します。
エピローグ
どちらが良い、明確であるが、動作する方法ではないか、動作しているが理解できないか? 家具や部屋のない雪のように白い城、またはルイ14世のスタイルのソファのある小屋? エンジンのない贅沢なヨット、または誰も使用できない量子コンピューターを搭載したうめき台船?
Polarの回答は適合しませんが、 「中間のどこかに」もあります。
私の意見では、両方の概念は密接に結びついています。本の表紙を慎重に選択し、テキストの誤りを疑いで調べ、その逆も同様です。 ビートルズに低品質の音楽を再生させたくありませんが、 MusicHelperと呼ばれるべきです。
別のことは、開発プロセスの一部として単語に取り組むことは過小評価された異常なことであり、したがって、何らかの極端な判断が依然として必要であるということです。 このサイクルは、フォームと画像の極端です。
ご清聴ありがとうございました!
参照資料
もっと多くの例を見ることに興味がある人は、私のGitHubで、例えばPocket.Common
ライブラリで見つけることができます。 (世界的および普遍的な使用ではありません)