要与该程序一起使用,您必须阅读代码,并且执行起来越容易,它看起来就越像自然语言-然后您就可以快速深入并专注于主要内容。
在最后两篇文章中,我展示了精心选择的单词有助于更好地理解所写内容的本质,但仅思考它们是不够的,因为每个单词都以两种形式存在:本身和作为句子的一部分。 直到我们在Thread.CurrentThread的上下文中读取 CurrentThread之前,它都不会重复。
因此,在音符和简单旋律的指导下,我们现在将了解什么是音乐。
循环目录
- 对象
- 动作和属性
- 文字编码
文字编码
大多数流畅的界面在设计时都侧重于外部而不是内部,因此它们非常易于阅读。 当然,这不是免费的:从某种意义上说,内容正在减弱。 假设,在FluentAssertions包中, FluentAssertions可以编写: (2 + 2).Should().Be(4, because: "2 + 2 is 4!")并且,相对于阅读, because看起来很优雅,但是在Be() , errorMessage使用error或errorMessage参数。
我认为,这种豁免并不重要。 当我们同意代码是文本时,其组成部分不再属于自己:它们现在是某种通用的“以太”的一部分 。
我将通过示例展示如何将这些考虑变为经验。
Interlocked
让我想起您Interlocked.CompareExchange(ref x, newX, oldX)的Interlocked情况,我们将Interlocked.CompareExchange(ref x, newX, oldX)转换为Interlocked.CompareExchange(ref x, newX, oldX) Atomically.Change(ref x, from: oldX, to: newX) ,使用明确的方法和参数名称。
ExceptWith
ISet<>类型具有一个名为ExceptWith的方法。 如果您查看类似于items.ExceptWith(other)的呼叫,您将不会立即意识到发生了什么。 但是,您只需编写: items.Exclude(other) ,一切都准备就绪。
GetValueOrDefault
使用Nullable<T>如果x为null Nullable<T>调用x.Value将引发异常。 如果仍然需要获取Value ,请使用x.GetValueOrDefault :它是Value或默认值。 大块头
表达式“或x或默认值”与简短的x.OrDefault匹配。
int? x = null; var a = x.GetValueOrDefault();
使用OrDefault和Or ,需要记住一件事:与运算符一起使用时.? 您不能编写类似x?.IsEnabled.Or(false) ,只能编写(x?.IsEnabled).Or(false) (换句话说,如果左侧为null ,则.?运算符将取消整个右侧)。
使用IEnumerable<T>时可以应用模板:
IEnumerable<int> numbers = null;
Math.Min和Math.Max
带有Or的想法可以发展为数字类型。 假设您要从a和b取最大数。 然后我们写: Math.Max(a, b)或a > b ? a : b a > b ? a : b 。 这两个选项看起来都很熟悉,但是看起来却不是自然语言。
您可以将其替换为: a 或 a.Or(b).IfLess() - 如果 a 小于 则 取 a 或 b 。 适用于以下情况:
Creature creature = ...; int damage = ...;
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 };
Regex
但是,与有时不正确地使用Regex以及将其用于其他目的相比, string.Join完全没有害处。 可以通过简单易读的文本获得帮助的地方,出于某种原因,首选过于复杂的条目。
让我们从一个简单的开始-确定一个字符串代表一组数字:
string id = ...;
另一种情况是从序列中找出字符串中是否至少有一个字符:
string text = ...;
任务越复杂,解决方案“模式”就越困难:将"HelloWorld"的记录拆分为几个词"Hello World" ,则有人会用一种简单的算法来代替怪物:
string text = ...;
毫无疑问,正则表达式是有效且通用的,但是我想一眼就知道发生了什么。
Substring并Remove
碰巧您需要从行的开头或结尾删除部分内容,例如,从path -扩展名.txt (如果有)中删除。
string path = ...;
同样, 操作和算法也消失了, 只剩下了一行,结尾没有.exe扩展名 。
由于Without方法应该返回特定的WithoutExpression ,因此它们会请求另一个: path.Without("_").AtStart和path.Without("Something").Anywhere 。 同样有趣的是,可以使用相同的单词构造另一个表达式: name.Without(charAt: 1) -删除索引1处的字符并返回新行(在计算排列中很有用)。 而且可读性强!
Type.GetMethods
要使用反射获取某种类型的方法,请使用:
Type type = ...;
(对于GetFields和GetProperties 。)
Directory.Copy
文件夹和文件的所有操作通常被概括为DirectoryUtils , FileSystemHelper 。 它们实现文件系统旁路,清理,复制等。 但是在这里您可以提出更好的建议!
我们将文本“将所有文件从'D:\ Source'复制到'D:\ Target'”显示为代码"D:\\Source".AsDirectory().Copy().Files.To("D:\\Target") AsDirectory() -从string返回DirectoryInfo ,而Copy() -创建一个CopyExpression实例,该实例描述用于构造表达式的唯一API(例如,您不能调用Copy().Files.Files )。 然后机会打开了,不是复制所有文件,而是Copy().Files.Where(x => x.IsNotEmpty)一些文件: Copy().Files.Where(x => x.IsNotEmpty) 。
GetOrderById
在第二篇文章中,我写道IUsersRepository.GetUser(int id)是多余的,更好的是IUsersRepository.User(int id) 。 因此,在类似的IOrdersRepository我们没有IOrdersRepository GetOrderById(int id) ,而是Order(int id) 。 但是,在另一个示例中,建议将此类存储库的变量称为_ordersRepository而不是_orders 。
两种更改本身都很好,但是它们在阅读上下文中并没有加在一起:调用_orders.Order(id)看起来很冗长。 _orders.Get(id)是可能的,但是订单失败,我们只想指定一个具有这样的标识符的订单。 因此, 一个为One :
IOrdersRepository orders = ...; int id = ...;
GetOrders
在诸如IOrdersRepository对象中,经常发现其他方法: AddOrder , RemoveOrder 。 前两个重复消失,并获得Add和Remove (具有相应的条目_orders.Add(order)和_orders.Remove(order) )。 使用GetOrders更难以重命名Orders 。 让我们看看:
IOrdersRepository orders = ...;
应该注意的是,使用旧的_ordersRepository或GetOrderById调用中的重复并不是那么明显,因为我们正在使用存储库!
诸如One , All类的名称适用于表示许多接口的许多接口。 说,在GitHub API的著名实现中octokit获取所有用户存储库看起来像gitHub.Repository.GetAllForUser("John") ,尽管更合乎逻辑gitHub.Users.One("John").Repositories.All 。 在这种情况下,获得一个存储库分别是gitHub.Repository.Get("John", "Repo")而不是明显的gitHub.Users.One("John").Repositories.One("Repo") 。 第二种情况看起来更长,但是在内部是一致的,反映了平台。 另外,使用扩展方法,可以将其缩短为gitHub.User("John").Repository("Repo") 。
Dictionary.TryGetValue
从字典中获取值分为几种情况,仅在找不到密钥的情况下需要做的事情不同:
- 抛出错误(
dictionary[key] ); - 返回默认值(未实现,但通常写
GetValueOrDefault或TryGetValue ); - 返回其他内容(未实现,但我希望
GetValueOrOther ); - 将指定的值写入字典并返回(未实现,但
GetOrAdd )。
表达式收敛于“ 取一些X,如果不取X则取Y ”。 另外,与_ordersRepository ,我们将字典变量itemsDictionary而不是itemsDictionary 。
然后对于“带一些X”部分 ,调用表items.One(withKey: X)理想的,返回一个带有四个结尾的结构:
Dictionary<int, Item> items = ...; int id = ...;
Assembly.GetTypes
让我们看一下如何在程序集中创建所有现有的类型T实例:
因此,有时,静态类的名称是表达式的开头 。
在NUnit中可以找到类似的内容: Assert.That(2 + 2, Is.EqualTo(4)) - Is并且不被认为是自给自足的类型。
Argument.ThrowIfNull
现在让我们看一下前提条件检查:
Ensure.NotNull(argument) -不错,但英语不太好。 另一件事是上面编写的Ensure(that: x).NotNull() 。 如果只有...
顺便说一句,你可以! 我们编写Contract.Ensure(that: argument).IsNotNull()并using static导入Contract的类型。 这样就获得了Ensure(that: type).Implements<T>()的Ensure(that: type).Implements<T>() , Ensure(that: number).InRange(from: 5, to: 10)等。
静态导入的想法打开了许多门。 出于以下原因的一个美丽示例:代替items.Remove(x)编写Remove(x, from: items) 。 但是令人好奇的是减少了返回函数的enum和属性。
IItems items = ...;
异国Find
在C#7.1和更高版本中,您不能写Find(1, @in: items) ,而不能写Find(1, in items) ,其中Find定义为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() , x.Or(b).IfLess() orders.One(with: id) , orders.All 。 - 作为表达式一部分的方法是
path.Without(".exe").AtEnd 。 - 作为表达式一部分的类型是
Instances.Of , Is.EqualTo 。 - 作为表达式一部分的方法(
using static )是Ensure(that: x) , items.All(Weapons) 。
因此,外部的和预期的成为最重要的。 首先考虑一下,然后考虑其特定的形式,只要代码被读为文本,就没有那么重要了。 由此可见,法官与其说是语言,不如说是他的品味-他确定item.GetValueOrDefault和item.OrDefault之间的区别。
结语
哪个更好,更清楚,但不是可行的方法,或者是可行的,却难以理解? 没有家具和房间的雪白城堡,还是路易十四风格的带沙发棚? 没有引擎或or吟的驳船以及没有人能使用的量子计算机的豪华游艇?
极地答案不适合,但也“在中间的某个地方” 。
我认为,这两个概念是密不可分的:仔细选择书籍的封面,我们会怀疑文本中的错误,反之亦然。 我不希望甲壳虫乐队播放低质量的音乐,但也应将它们称为MusicHelper 。
另一件事是,在开发过程中处理单词是一件被低估的,不寻常的事情,因此仍然需要某种极端的判断力。 这个周期是形式和图片的极限。
谢谢大家的关注!
参考文献
任何有兴趣查看更多示例的人都可以在我的GitHub上找到,例如,在Pocket.Common库中。 (不适用于全球和通用)