xiyuan技术圈

溪源的技术博客,技术的不断进取之路


  • Home

  • 技术

  • 随笔

  • 读书

  • 管理

  • 归档

C#提升性能的几点提示和技巧

Posted on 2020-05-28 | Edited on 2021-04-25 | In 技术

C#性能提示和技巧

在Raygun,我们是一群非常懂多种语言的开发人员。Raygun的各个部分使用不同的语言和框架编写-最好的工作方式。

鉴于大量的C#和我们正在处理的数据的爆炸性增长,在不同的时间需要进行一些优化工作。大部分重大的收获往往来自于真正地重新思考问题并从全新的角度解决问题。

今天我想分享一些C#性能技巧,这些技巧对我的最新工作有所帮助。其中一些功能在你看来也许相当微不足道,因此请不要在这里充电并使用所有功能。就这样,提示1是…

1.每个开发人员都应使用分析器

有一些很棒的.NET分析器。我个人使用了Jet Brains团队的dotTrace分析器。我知道我们团队中的Jason 也从Red Gate分析器中获得了很多价值。每个开发人员都应安装并使用探查器。

我无法数出我认为应用程序的最慢部分在一个区域中的次数,而实际上却完全在其他地方。探查器对此提供了帮助。此外,有时候,它可以帮助我发现错误-缓慢的部分之所以缓慢,只是因为它做错了什么(单元测试没有正确地拾取它)。

这是您要执行的所有优化工作的第一步,也是有效的第一步。

冲刺开始

2.级别越高,速度越慢(通常)

这只是我闻到的气味。您使用的抽象级别越高,通常越慢。我在这里发现的一个常见示例是在代码繁忙的部分(也许在循环中被称为数百万次)中使用LINQ。LINQ非常适合快速表达某些内容,而这些内容可能要花一堆代码,但是您通常会将性能留在桌面上。

不要误会我的意思-LINQ非常适合让您开发出可运行的应用程序。但是在代码库中以性能为中心的部分中,您可能会付出太多。特别是因为将这么多操作链接在一起非常容易。

我所使用的特定示例是我使用的地方.SelectMany().Distinct().Count()。鉴于这被称为数千万次(由我的探查器发现的关键热点),它正在累积大量的运行时间。我采用了另一种方法,并将执行时间减少了几个数量级。

3.不要低估发行版和调试版

我一直在努力工作,对获得的性能感到非常满意。然后,我意识到自己已经在Visual Studio中进行了所有测试(我经常将性能测试编写为也可以作为单元测试运行,因此我可以更轻松地运行自己关心的部分)。我们都知道发行版本已启用优化。

因此,我做了一个发布版本,称为从控制台应用程序测试的方法。

我对此有了很大的转变。我的代码已经疯狂地进行了优化,因此确实是时候对.NET JIT编译器进行一些微优化了。启用优化后,我的性能提高了约30%!这使我想起了我不久前在网上阅读的一个故事。

软盘的屏幕截图

这是上世纪90年代的一个古老游戏编程故事,当时内存限制非常严格。在开发周期的后期,团队最终将耗尽内存,并开始考虑必须删除或降级哪些内容以适合可用的微小内存空间。资深开发人员根据他的经验就曾期望这样做,并在项目一开始就分配了1MB的内存和垃圾数据。然后,他节省了一天的时间,并删除了他在项目开始时立即分配的1MB内存,从而解决了问题!

知道团队总是没有足够的空间,因为那里有可用的内存,就可以为团队提供他们所需要的东西,并按时发货。

我为什么要分享这个?在性能方面类似–在调试模式下获得足够好的运行,并且您将在发行版本中获得一些“免费”性能。美好时光。

4.看大局

有一些很棒的算法。您多数不需要每天甚至每月都不用。但是,值得知道它们的存在。我经常进行研究后,就会发现一种更好的解决问题的方法。在编码之前进行研究的开发人员与在编写代码之前进行适当分析的开发人员的可能性差不多。我们喜欢代码,并且总是想直接进入IDE。

此外,通常在查看性能问题时,我们过于专注于单个生产线或方法。这可能是一个错误–放眼全局,可以通过减少需要完成的工作来帮助您显着提高性能。

5.内存位置很重要

假设我们有一个数组数组。实际上是一张桌子,尺寸为3000×3000。我们要计算有多少个插槽的值大于零。

问题–这两个中哪个更快?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (int i = 0; i < _map.Length; i++)
{
for (int n = 0; n < _map.Length; n++)
{
if (_map[i][n] > 0)
{
result++;
}
}
}
for (int i = 0; i < _map.Length; i++)
{
for (int n = 0; n < _map.Length; n++)
{
if (_map[n][i] > 0)
{
result++;
}
}
}

回答?第一个。 在我的测试中,此循环使性能提高了8倍!

注意区别吗?这是我们遍历此数组数组的顺序([i] [n]与[n] [i])。即使我们从自己管理内存中抽象出来,内存局部性在.NET中的确很重要。

就我而言,这种方法被称为数百万次(准确地说是数亿次),因此我可以从中获得的任何性能都获得了可观的胜利。再次感谢我经常使用的分析器,以确保我专注于正确的地方!

6.减轻垃圾收集器的压力

C#/.NET具有垃圾回收功能。垃圾收集是确定哪些对象当前已过时并删除它们以释放内存中空间的过程。这意味着在C#中,与C ++之类的语言不同,您不必手动维护不再有用的对象的删除,即可声明其在内存中的空间。相反,垃圾收集器(GC)处理所有这些,因此您不必这样做。

问题是没有免费的午餐

问题是没有免费的午餐。收集过程本身会导致性能下降,因此您实际上并不希望GC一直收集。那么如何避免这种情况呢?

有许多有用的技术可以避免对GC施加太大压力。在这里,我将只关注一个技巧:避免不必要的分配。这意味着要避免这样的事情:

1
2
List<Product> products = new List<Product>();
products = productRepo.All();

第一行创建了一个完全无用的列表实例,因为下一行返回另一个实例并将其引用分配给变量。现在想象一下上面的两行是否在一个执行数千次的循环中?

上面的代码可能看起来像一个愚蠢的示例,但是我已经在生产中看到了这样的代码,而不仅仅是一次。不要只关注示例本身,而要关注一般建议。除非确实需要,否则不要创建对象。

由于GC在.NET中的工作方式(这是一个世代的GC流程),因此较旧的对象更有可能收集较新的对象。这意味着创建许多新的,短暂的对象可能会触发GC运行。

7.不要使用空的析构函数

标题说明了一切-请勿在类中添加空的析构函数。Finalize每个具有析构函数的类的条目都会添加到队列中。然后在调用析构函数时调用我们的老朋友GC来处理队列。空的析构函数意味着这一切都是徒劳的。

请记住,就性能而言,GC执行并不便宜,正如我们已经提到的。不要不必要地导致GC工作。

盒子的屏幕截图

8.避免不必要的装箱和拆箱

装箱和拆箱就像垃圾回收一样,在性能方面很昂贵。因此,我们希望避免不必要地进行操作。但是他们在实践中会做什么?

装箱就像创建引用类型框并将值类型的值放入其中一样。换句话说,它包括将值类型转换为“对象”或该值类型实现的接口类型。取消装箱相反,它会打开包装盒并从其中提取值类型。为什么会有问题呢?

好吧,正如我们已经提到的,装箱和拆箱本身就是昂贵的过程。除此之外,当您装箱一个值时,您会在堆上创建另一个对象,这给GC带来了额外的压力(您已经猜到了!)。

那么,如何避免装箱和拆箱呢?

通常,您可以通过避免.NET(版本1.0)中早于泛型的API来做到这一点,因此,它们必须依赖于使用对象类型。例如,更喜欢通用集合,例如System.Collections.Generic.List,而不是System.Collections.ArrayList。

9.当心字符串连接

在C#/。NET中,字符串是不可变的。因此,每次执行一些看起来好像在更改字符串的操作时,它们都会创建一个新的字符串。这些操作包括类似的方法Replace和Substring,同时也串联。

提防串联大量字符串,尤其是在循环内部

因此,这里的技巧很简单-注意不要串联大量字符串,尤其是在循环内部。在这种情况下,请使用System.Text.StringBuilder类,而不要使用“ +”运算符。这样可以确保不会为连接的每个部分创建新实例。

10.随时关注C#的发展

最后,我们以非常笼统的建议作为结尾-请密切关注C#语言的更改和发展方式。C#团队不断提供可以对性能产生积极影响的新功能。

我们可以提到的一个最新示例是C#7中引入的ref return 和ref locals。这些新功能允许开发人员按引用返回并将引用存储在局部变量中。C#7.2引入了Span 类型,从而可以对内存的连续区域进行类型安全的访问。

诸如此类的新功能和类型不太可能被大多数C#开发人员使用,但是它们无疑会对性能至关重要的应用程序产生影响,值得进一步了解。

C#性能很重要!

这只是我发现对提高.NET代码性能有用的几件事的集合-但是值得花时间检查代码以确保其性能。您的团队和客户将感谢您!

我是如何完成一个短期项目的?

Posted on 2020-05-26 | Edited on 2021-04-25 | In 随笔
  1. 项目启动

4月中旬,接到指示,需要于5月10日完成一个紧急任务,这个项目是为一位核心客户打造一套具备公众号,后台和大屏显示系统相关的完整系统,而此时,我们的团队构建还并没有完成,意味着我们需要实现的将是一个超出现有团队实际能力的项目。

当然,团队能力本身衡量就是一件并不容易的事情。如同从海绵中挤水分,也许从表面上看没什么内容,但挤一挤总能挤出许多精华来,尤其是在职场中成长,总必须经历一番考验,把自己的潜力逼出来,才能真正成长。

当然,我依然根据实际情况进行了一番风险评估,主要包括一下几个方面:

1,团队资源匮乏,尤其是缺乏有经验的开发者

2,大华摄像头以前没对接过,存在技术的不确定性。

3,团队没搞过公众号开发。

4,时间太紧,质量可能是个问题,有可能影响公司商誉。等会,

显然,风险评估是项目启动前必须做好的关键。尤其是这个项目,承载着相关方的许多目标,包括是我们团队的第一个正式项目,无论是老板也好,公司同事也好,甚至加入团队的成员们也好,其实都想知道我们这个团队到底行不行,能不能承受足够的压力。

显然,团队成员们都需要通过这个项目证明自己的能力,进而证明团队的能力。

二 技术选型

在完成项目启动会后,我们跟客户进行了一番沟通,明确了项目团队中大家关心的问题,并开始着手技术选型,框架,业务分析,代码实现。

在进行技术选型中,我基于公司长期积累的角度,选择了.netcore+vue.js的组合型技术栈,框架使用了abp作为我们的开发框架。

虽然在实际过程中,有人提出了abp开发框架提供了一系列重量级的组件,对我们这样的团队来说可能会扩大技术风险,平白增加项目的时间。但实际过程中,该选型并没有造成想象中那么严重的问题。主要原因大概有以下几点:

1,abp无论如何依然是.net技术栈中最受欢迎,公认设计最优雅的面向领域驱动设计的框架。哪怕我们不用领域驱动的思想,也不影响我们使用abp的轮子。它的轮子确实太灵活方便了,运用妥当定然能给我们带来不少好处。

2,即使我已经从事了十年开发,却依然觉得要设计一套权限认证系统远比想象中的复杂。

事实上这是一个普遍认可的观点,看起来权限认证几个表就能搞定这个问题,其实并非如此。例如abp的权限认证包括了如下复杂的逻辑结构。

图片
在介绍领域驱动设计的一本书《Microsoft.Net企业级应用架构设计》这本书中,作者也表达了同样的观点:

即使已经写了多本asp.net的书,创建基于角色的UI基础设施以及相关的成员系统耗费比预期更多的功夫。

事实上,对于我们这样的项目,关键不是选什么框架,而是选一种能够满足当前需求,又能在未来给公司带来积累的框架和技术体系,而abp和abpvnext恰好如此。小项目可以用abp,大项目则可以考虑后者。或者都可以采用后者。

三,项目过程控制

当这个项目拿到时,我们面临短期看似无法完成的较多量需求,难免会存在选择困难证。究竟是做A,还是选B,还是做一个成年人应该选的,两者都选?

显然,我们的精力有限,只能有所取舍。

在现代软件工程中,瀑布模型,快速原型开发模型,增量/迭代型模型都是我们在项目选型过程最基础的选择。如果采用瀑布模型,显然不符合我们的实际情况,因为没有足够的资源,不太可能花时间写文档;快速原型也同样如此,花时间做一个完整的demo,那也不太可行,尤其是甲方已经确定了原型之后,已经具备开发实例功能的前提。

在这个模块优先级选择之中,我个人认为,虽然B端系统这个部分需求很多,优先把它搞定,能够让我们集中精力解决其他问题,但对用户来说,是否真的如此?

业务价值的评估是我们进行功能优先级评定的第一要素。如同一个价值滑条,同时摆在一个刻度上,永远只有一个功能。

于是,我们将公众号,大屏作为第一迭代。并安排足够的资源优先完成此模块。当然,资源其实依然并不足够,但大家都知道,资源不足才是项目最普遍的现象,在现有资源范围充分利用,才是成立项目组的目的。

我们引入了持续集成的概念,要求把更早的交付当作核心目的。为了实现这个目标,我们采取了如下措施:

  1. 收集需求,以低保真原型图指导软件开发,除个别页面要求美观外,其他页面以满足可用性为准

  2. 前后端设计模拟数据,前端就可以开始联调了。

  3. 划分迭代周期,确定发布节奏。

  4. UI先行开发,同步后端开始设计接口和模拟数据的格式,用后端不用直接与前端联调,而是使用测试驱动先行的方式开发后台逻辑。

四,总结

当然在实际操作过程中,我们并没有采用测试驱动开发的红绿重构开发实践,这使得我们没有足够的保障措施来确保代码质量,而且由于项目团队普遍经验不足,也意味着我们在未来的演进过程中,将面临许多严重问题。

在这个项目中,我们采用了合理的迭代划分;让售前,设计驻场确保需求的澄清;采用了开发分支和功能分支分离的模式是我个人认为值得复用的经验。

由于时间缘故,在下一篇文章中,再讨论一下这个问题。

共同创业五年,技术总监却突然就这么离职了

Posted on 2020-05-18 | Edited on 2021-04-25 | In 随笔

一、 引子

有一天,跟一位原同事老A聊起职场的一些情况,期间无意中提到了原公司的技术总监。我说这位技术总监带领公司从一家十几人的小团队,做到四百人的公司,作为同样都是开发者,而且以前也都从事过相同行业的开发,我非常钦佩他。

在内地城市长沙,虽然也有一些优秀的互联网公司,但像这样短短五年,使公司从十几个人的公司发展为几百人的公司,组建起拥有200人的技术团队,其实已经算是非常不错了。

在公司这五年的发展过程中,这位技术总监应该是出力非常多的一位,从引入变革,到关键产品的发布,到团队的逐渐发展,到研发流程的规范化,已经算是一位适时刷新思想,实现组织变革的典型人物了。

当然,有时候公司发展得好,既是“时势造英雄”,又是“英雄造时势”。老板不能只说自己的功劳,把公司做的好,全归功于自己决策做得好、党和国家政策好,技术总监也不会自吹自擂,将不属于自己的功劳,也吹成自己的功劳。虽然这两位之间看起来似乎有点不太明显的矛盾,但总归是维持在一个平衡的程度,使得公司依然能够保持快速的发展速度,长此以往一定会成就一段佳话。

这时,老A却告诉我,这位技术总监过完年就已经从公司离职了。

这让我非常惊讶,干得好好的,怎么就离职了?这位技术总监和老板之间配合得还算默契,公司发展也处于“如日中天”的状态,怎么说走就走?

老板换人了?怎么可能,技术总监往往是公司最关键的技术岗位,怎么可能像普通开发岗位一样想换就换的。是不是有什么特殊的原因?

他说:这有什么,想走了呗。也许有哪些原因,我们也看不出来啊。

好吧,其实我也并非一位喜欢职场八卦的人,无意对深层次的原因进一步挖掘,尤其是这家公司纵使在长沙看起来有一点规模,但是在《架构师社区》的读者眼里,依然并非“大厂”,不提也罢。

总结来说,每个人职场都有不同的选择,在恰到好处的时候离去,无论是归于平淡,还是寻找另外一番人生际遇,或是快意恩仇,或是潇洒自如,或是相忘于江湖,都是对自己职场人生的一段总结。

二、 技术总监,“高”处不胜寒

1)

在我刚刚参加工作时,曾经以为技术高管算是一位开发者职场奋斗的顶峰,能够成为技术高管,一定已经非常”稳定“了。

当然,这种观点显然已经过时了。读者们都非常清楚,在一家成熟的公司,理论上讲,没有一个人是不可替换的,唯一的区别是替换成本的高低。尤其是那些成熟的互联网公司,连企业法人也会根据公司发展形势的变化而变化,那技术总监就更不用说了。

技术总监甚至不一定属于“高管”,例如小公司,由于天花板很低,也许一位技术总监,已经算是“高管”了;但在大公司,其实很少会轻易将高管Title授予给他人,有时看上去那些所谓“总监”,其实只是头衔而已,并不能说明他的职位和话语权究竟有多高;也许得VP(技术合伙人)或CTO才能称为“高管”。

不过总体来说,从白手起家的普通程序员,到成为“技术总监”,这已经算是对一位辛勤工作的开发者付出的极大肯定了。

2)

但有朝一日成为了技术总监,就真的职场无忧了么?

并非如此,有一位朋友曾经分享了一个图片,那是一位40+的技术总监求职10k+技术职位的简历截图。(由于某些原因,不便分享。)

这位大佬在一家国企的IT技术部门担任技术总监,在公司期间曾经主导了公司从传统媒体到新媒体的转型,为公司寻找新的利益增长点做出了不可磨灭的贡献,但是由于新媒体市场的激烈竞争,即便是依托国企和政府的良好关系和资源,这家公司依然很难在这个市场获得不错的业绩。

业绩不好,技术部门自然而然也不会有多好过,事实上压力最大、首当其冲的就是技术老大,一方面是公司在新方向上的投入和产出不对等,首先就会追究技术总监的责任,另外一方面,研发团队长期承受巨大的压力,每天都加班加点,却被公司业务部门屡屡出言不逊,“花了那么多钱,结果养出来一支这样的卵蛋”?(毕竟虽然是国企吃大锅饭,但技术部门的工资,其实比业务部门要高出一倍有余),好不容易培养出来的团队,面临着严重的人员流失问题,又会让新项目的开展面临难以维系的局面。

曾几何时,这位技术总监试图在团队内营造一股生机盎然的学习氛围,让大家在轻松而又愉快的氛围中共同进步,但是来自于业务部门的压力,显然让一切试图制造象牙塔的努力都变成徒劳。

最终那些琳琅满目的书架、咖啡机、每日点心,被那些“大干一百天,奋斗就是生存”“年轻时不奋斗,还何时奋斗”“我的生命只有一次,我们要用他来改变世界”这样的标语替代。

(这也让许多心心念念去国企、大厂寻求稳定的人提个醒,也许你还真寻不到你梦寐以求的稳定。)

3)

他曾经跟我讲过刚去公司时,老板给他描绘的一幅幅宏伟蓝图,在刚开始业务转型时,在公司的大力支持之下,一个个他主导的项目都比他自己预想的还要轻易的完成,在同事们眼中,俨然一幅救世主一般。

但是由于竞争对手过于强大,那些他带领几十号技术人员付出了许多个昼夜、熬夜加班干出来的新产品,却由于没办法获得足够的流量,最终如同一拳打到了棉花上,毫无产出。

然后两年过去,他已经不再看业务部门同事的眼光了,也许莫名其妙间,感受到的是一股“嫌隙”的表情。许多业务部门的同事甚至会当着他的面说,如果把投入到互联网产品上的大几百万拿给自己做业务,肯定能带来更多客户。

于是最终,这位技术总监也选择了放弃,他想回归本行,从一位纯粹的开发者干起,但大概市场已经没有他的位置了吧。

毕竟,在如今的长沙,企业老板们对40岁左右的开发者,不管你履历多么华丽,所能提供的薪酬待遇,往往不超过10k。

也许,他能选择的方向,是“教职”。

三 职业发展方向之谜

也许这篇文章又一次输出了“焦虑”,但并非笔者本意。”焦虑”大概是开发者们最普遍的情绪,尤其是最近疫情的影响,不少公司开始裁员,更是给开发者群体造成了不小的影响,职业发展再度成为大家普遍困扰的话题。

在互联网上流传了三种不同的程序员发展路线图,一种是从程序员出发,然后干到专业工程师,高级工程师,架构师,技术负责人,技术总监,CTO;

还有一种是从程序员出发,干到技术总监,然后开始送外卖或者送快递,或者自己开个彩票店,或者便利店,然后闲庭细步,泡上一杯茶、去钓钓鱼,细细品味这人生的慢慢悠长。

当然,还有一种主流的发展路线,大概是最终走向创业,成为“万众创业”国家大计政策下的一颗螺丝钉。

我们该如何选择适合自己的职场方向呢?

在这篇文章中介绍了两个优秀的技术管理者,他们在自己的职场赛道上都遇到了一些困难,但也都一如既往的努力,那我们呢?

其实,无论你做什么,其实都难以一帆风顺,但只要踏实坚持,总能看到希望。焦虑无助于改变现状,倒不如沉下心来,干好当下。

​

是小厂全栈好,还是大厂专业工程师好?

Posted on 2020-05-17 | Edited on 2021-04-25 | In 随笔

一

在博客园中使用小公司大公司进行搜索,列入的搜索记录长达50页。虽然完全命中关键词的文章也许并不多,但这或许也能体现出这个话题的热门程度。

今天我的公众号好友中也有人问了我这个问题:

在小公司里面做全栈好,还是大公司里面做专业的前端或者后台好?

对于这个问题,我大概有一点点发言权。在我过去若干年的职业生涯中,各种类型的公司也算都经历过,小一点的公司,大概有四五十人,大一点的公司有大几百人。

当然,与读者们的大厂比起来,都是小公司,着实不算大公司。但总体来说,也算是了解“专业工程师”和“全栈工程师”这两个名词背后的水深水浅。

二

有时,当我们去跟一些人交流,会发现一个奇怪的现象,往往小公司的更喜欢称自己为“全栈工程师”,而大厂出来的,则反而不敢自称自己为“全栈工程师”。这究竟是为何呢?

我们可以继续引述经典理论“达克效应”。

1、不知道自己不知道。

2、知道自己不知道。

3、知道自己知道。

4、不知道自己知道。

这四个阶段其实无论在技术层面,还是职场发展过程中,都无处不在。

图片

在我们每个人说起漫长,说起短暂的职业生涯中,总是会历经无穷次技术的发展,甚至变革,这些技术其实在给我们创造价值的同时,也一点点在我们的灵魂深处留下投影。

有的技术或理论,会对我们的职业发展产生非常深刻的影响;有的则如雨后彩虹一般,突然出现,却有遽然消失。

每一种技术或理论的产生总会有一套成体系的脉络,也许入门很容易,但要成为专家其实非常困难。达克效应表现的也是这样一种效应。

那些看起来很容易就学会的东西,往往要深入或许更加困难。而许多大厂开发者深刻体会其中的不容易,所以若非经过最少几百小时的学习,其实不敢自称为专家,更遑论自称“全栈工程师”了。

其实,有的人自称为“全栈工程师”,倒不如说是“全能工程师”—每种技术都或多或少懂一点,能够在很短的时间内完成任务,但一旦要有所深入,就略显不足,无法再进一步了。

三

但,无法在技术层面有所深入,是一件难以启齿的事情么?

也许并非如此。我深深的感觉,在IT行业,看起来风起云涌,浪潮迭起,但依然充满前途和光明,其主要原因在于:人们对基于互联网场景下的应用,需求从来就没有因为互联网技术的发展而有所降低,反而越来越细致,越来越具体,产生着越来越深远的影响。

例如,很多年前就说美国互联网泡沫破灭,但今天互联网经济反而越来越重要了;移动互联网也有人唱衰,“说BAT才掌握船票,已经垄断中国经济,其他公司几乎毫无机会”。却莫名其妙间,又多出了头条、美团、小米、滴滴、京东、拼多多等数不尽的优秀互联网公司。

再过十年IT产业会逐渐退潮么,IT人才将会毫无施展才华的土壤么。不得而知,而且也不重要。重要的是,即便在互联网技术飞速发展的今天,中国依然对优秀的IT工程师非常稀缺。

四

图片

时至今日,软件实现过程并没有因为软件技术人才的增加而逐渐简化,反而依然非常复杂。我们其实都能看到,在IT行业,五年经验以下的开发者始终居于大多数,不管是十年前,还是今天,行业都几乎没有太大的变化。我们能指望现在的年轻开发者能够提前规避我们之前遇到过的那些问题么?

历史告诉我们:人们走过的弯路,后人其实还是会再走一遍。看似大爆炸的互联网,知识满地都是。其实,知识过载和知识过乏没有任何区别。所以,我们写过的那些垃圾代码,我们以前遇到过的那些bug,依然有许多年轻人在沿着我们的步伐再走一遍。

那些隐藏在软件界面的冰山之下,难道bug突然减少了?究竟会不会在哪天突然爆发?客户提出的需求,程序员们究竟是如何实现的?

五

我始终认为,专业工程师依然非常匮乏,无论过去、今天,或未来。

专业工程师或许不一定是某个领域的专家,也许是某些具备优秀跨职能能力的开发者。

他们首先能够基于某些行业场景出发,以独特的视角发掘问题的本质,并快速的将业务问题转换为技术实现,还能抽丝剥茧,发现不同事物之间的关联关系,从而更好的将业务问题以软件的形式进行呈现,

他们也能灵活的发现不同技术之间的优缺点,并使用合适的技术问题来进行适配,使得问题能够以最快的速度进行解决。

他们还能从多个角度出发,而不仅仅是从【软件代码实现】这个维度出发来解决问题,他们所具备的良好的沟通能力和专业素养,使得客户/用户能够愿意倾听提出的建议,从而以最少的代价来解决问题。

六

成为专业工程师,与选择“大厂”或选择“小厂”有非常明显的区别么?

大公司和小公司都有不同的发展轨迹,不同的人适合不同的发展方向。无论怎么选,其实都是“小样本”。

个体选择走【跨职能型人才路线】或走【专业人才】路线,对于偌大的中国来说,都其实不会对历史的车轮产生多大的影响,但我们的选择其实是在慢慢的改变我们的生活。

有时,小厂在能够填补我们对于经济上的匮乏,又有时,选择了大厂会让我们以为未来的发展无忧。

确实如此,有时一些小厂反而能够比大厂提供短期内更加诱人的薪资,这对一些经济条件不太好的人来说,犹如“久旱甘霖”;而大厂看似平滑的发展曲线,能够让我们只要沿着设定的方向走下去,肯定不会走错。

但真的小厂就意味着“朝不保夕”,大厂就一定是“高枕无忧”么?谁也说不准。

最重要的,也许依然是认清自己的定位,无论在大厂,还是小厂,使自己成为出色的“专业工程师”,更加全面的成长,或许更能让我们的职场利于不败之地。

那些与EF有关的错误认知

Posted on 2020-05-15 | Edited on 2021-04-25 | In 技术

前言

这是一个对话性的讨论,它讨论了一个严重的问题趋势,我发现在由初级团队到架构师团队的各种规模的组织中,EntityFramework的利用率都很高。这不是一个如何做的问题,这也不适合新手。如果有什么能激发您的想法,或者您对我提到的事情感到好奇,那么Google是您的朋友。这也是我的第一篇博客文章。欢迎批评。

历史和功能介绍(按版本)

首先,让我们简单回顾一下EF随时间推移推出的功能。这绝不是详尽无遗的,当然也不会通过对主要版本的更新列出所有内容。它只是提醒了迄今为止EF的故事。

EF / EF 3.5

· DB First

EF 4.0

· Lazy Loading

· Migrations

· POCOs

EF 5

· Enums

· Spatial

EF 6

· Async

· Interception

· Logging

· NuGet Installation

· Recovery

EF 7 / Core

· Code First Only

· In-Memory Support

· Limited Batching

· Nonrelational Support

看到这种零散的发布以及Microsoft在开发领域的普遍声誉,Entity Framework有了一个不好的惊喜,并不是因为它为我将要解决的问题找借口而已。根据您正在使用的次要版本,功能似乎随机出现。因此,即使安装了相同的主要版本,您仍会习惯某些东西,转到另一个环境,提出声明并在其现有框架上进行尝试,即使它没有安装,您也仍然会“告诉您”的样子有助于进一步加深已经确立的地位。

基本上,EF的典型故事如下:

高级人士:“让我们使用EF和仓储模式!”

其他开发人员:“ Idk,还没听说过好消息。”

高级:“不,太好了!看到这个例子吗?”

开发人员:“嗯,好的。”

起初,它的工作原理在可接受的范围内。然而,随着它的增长,迟缓开始出现,人们开始抱怨。由于我们行业中偿还技术债务的状况非常糟糕,或者由于完全拒绝首先查看仓储模式中的技术债务,因此,据称聪明的人的整个部门都袖手旁观,只是得出结论,”EF是垃圾",并不是说他们的使用是垃圾。

我在这里要说明的是后者,并向您展示如何避免该陷阱。

EF使Sql查询过程得以抽象

在我职业生涯的早期,我开始直接通过ADO使用经典的ASP和SQL Server。我在一个非常小的网络部门工作,因此我经常不得不亲自进入数据库来创建表和执行任务。在一系列复制/粘贴部署,生产测试等过程中,我很快熟悉了SQL Server的所有技巧。“那这个呢?不会,该产品页面仍然无法加载。那个怎么样?!不,仍然无法加载。来吧…这个?成功!通过几乎在黑暗中绊倒,我对索引,视图,复制,安全权限等等非常了解,当时甚至还没读完高中。

使用结构化的环境输入我的前几个地方,然后使用Entity Framework时让我非常脸红。它完全没有我习惯的任何选择。因此,我跳上船,但是没多久就开始抱怨。如果您有金鱼的记忆,让我重申我习惯于随意调整所有杠杆。遇到问题时,我会进行调查。通常,我发现诸如索引利用率之类的关键组件被完全忽略了。当我提出这些问题时,我被告知EF因不知道如何利用它们而感到过失,而我们是在这里处理业务问题,而不是由Microsoft来为他们做。老实说,我基本上还是一个初级开发人员。我有什么理由不同意呢?

仓储模式存在的问题

点击访问仓储模式。仓储模式的问题有两个方面。存储库模式的问题有两个方面。首先,它要求您预先声明如何绑定应用程序与数据库进行交互。即使您构建了这些超级复杂的方法,这些方法允许您传入表达式、字典或异常动态,并且您很有创造力,但是您所做的只是制造了一个维护噩梦。

“但是调用者可以定义他们所需要的!” 不,他们不能。当然,他们可以指向实体并通常定义要选择的数据的形状,但是他们无法确定字段选择之类的内容。他们没有办法说他们需要以一种友好的方式预先加载数据或延迟数据。他们不能在一次实例化中说他们也需要来自这里或那里的数据,但在下一次实例化中,他们只需要找到目标实体,除非神圣的存储库允许他们这样做。相反,您会得到这些全有或全无的决策,这些决策将您的应用程序链接在一起,我们想知道为什么它会很快降级。我真的希望你的水晶球比我的好。

其次,即使是微软自己的例子也没有使用某些实习生可能编写的适当接口。因此,我说,每个人都做错了。关于EF的常识是使用存储库模式,由于存储库模式本身的文档和示例不正确,所以没有人会让EF做它被设计用来做的事情,因为知识的来源受到了毒害。面对这种情况,我听到很多人抱怨说MVC教程的例子直接使用了DbContext,抱怨说它不够稳定,也不是说几乎没有人做得很稳定,但这是另一篇博客文章。(大多数软件直接跳转到ID,忽略了其他的。)

让数据库成为数据库

由于SQL Server是EF中最常用的支持数据存储,因此它不是一个干净的软件。太乱了 ,它具有大量场景的功能。如果您想让您的应用程序实际使用您所支付的巨额许可费用的一小部分,请停止将SQL约束到EF驱动的地狱荒原,而这些荒地比SELECT *还要好。然后,我们喜欢抱怨事情进展缓慢。

如果您不让EF在正确的情况下利用功能,则可能无法意识到平台的潜力。必须浪费数十亿美元的许可和开发成本,即使在应用程序以截然不同的方式增长时,使用的独特功能也只会使SQL Server的单位利用率下降。这是一种直觉,但是看到我在大型和小型公司中看到的愚蠢的朴素仓储实现,很难在这里看到我是完全错误的。这对我们自己,我们的雇主和彼此都是有害的。

实体框架仍然逐步锁定在基础数据存储的工作方式上。在SQL Server中,这意味着联接性能,视图和索引利用率,存储过程调用等。这就像将乳胶手套称为手的抽象。它不是,EF也不是它所依赖的存储机制的抽象。相反,它是一组通用的API,它们使我们能够以统一的方式访问数据。由于我刚才所说的原因(我们不能以任何方式否认或减轻基础实现的行为),这不是一个抽象。因此,我们必须在代码中考虑显式或隐式破坏抽象的那些行为。如果要假装它是抽象,我们唯一能做的就是把头埋在沙子里,然后在事情变得笨拙时继续continue吟。

最近,我遇到了—架构师—几乎对让数据库定义视图和将EF指向视图而不是表的建议感到敬畏,您知道,这让dba能够真正完成他们的工作,并使数据库能够在不破坏应用程序代码的情况下进行更改。这并不是什么难事,但问题是普遍存在的,所以大多数人在他们太熟悉的环境中都看不到过去。那么,我们该怎么做呢?

使用IQueryable而不是IEnumerable

正确使用Entity Framework的第一步是打破与IEnumerable的依赖关系。当谈论断开连接的商店时,这是很糟糕的。IEnumerable唯一给出的就是延迟执行。如果这是您想要脱离ORM的唯一功能,那么您就不需要ORM。IEnumerable隐瞒使用数据存储的原因在于,它们一劳永逸地固定在它们的表示中。即使应用程序增长,即使仓储中添加了新方法,返回IEnumerable的旧实现也对它们所处的新世界都是盲目,聋哑和愚蠢的。您实际上是在强迫代码与数据布局和期望一起使用。就像几年前首次实施时一样。这是开发人员的错,但应归咎于EF。

但是,IQueryable可以变形并更改为其给定的上下文。即使传递和添加了子句,它也可以评估实例,例如各个调用的需求。如果DbContext已经获取了数据,但它仍可以从高速缓存中检索实体,然后才能以非常快的速度进行重复调用,从而使热路径更加凉爽。更重要的是,它还提供了一些功能,例如,如果基础提供程序支持的话,让我们流数据;无需实例化List对象以使其对堆更友好地加载数据;检查基础类型,以便我们可以在复杂的工作流程中做出明智的决定;访问基础上下文, 等等。

这些都是使您的代码真正了解正在发生的事情而不会破坏抽象障碍的所有功能,因为EF不是抽象。顺便说一句,抽象应该是使用EF的组件,而不是EF本身。我在程序员进行的许多讨论中表达了自己的见解,这些讨论表达了一些需求,但是我们以“抽象”的名义对许多解决方案solutions之以鼻,随之而来的是我们高兴地扭曲自己的箍,这样我们就可以继续存在固体。

习惯匿名类型

我听说过的关于EF的最大的抱怨也许就是它检索了多少该死的数据。谁定义实体?EF?没有!你做到了 从本质上讲,您不必多怪,因为每个表的实体似乎是所有人都可以看到的。尽管如此,无论实体有多大,我们都无需受到阻碍。将匿名类型传递给EF查询将导致EF仅选择您定义的字段。可以将数十列的“无法重构”的怪物表分解为实际需要的3或4个字段。一次选择整个实体并假装无能为力的迷恋只能描述为一种大众歇斯底里的形式,我们大声疾呼,“我看不到你!”

使用正确的工具完成工作

您知道所有带有封面上各种工具的Microsoft Press书籍吗?您知道,除了某些人只是选择随机图像之外,还有一个原因。大多数工具不仅是螺丝起子或刨刀。有一些真正的奇怪应用没有明显的应用,但是可以肯定的是,它们有自己的目标,并且擅长于此。“正确的工具”的口号经常重复出现,但是我们并没有真正停下来思考工作,更不用说工具了。以下是EF的一些功能。

自.Net 2.0以来出现的SqlBulkCopy

另一个与EF数据量密切相关的大型抱怨是,EF检索到它据称无法处理大量数据的方式。我喜欢开发人员的双重性。我想让您知道,结合下面讨论的AsStreaming,反应性扩展和SqlBulkCopy,我可以在一分钟内检索,转换和推送数百万条记录,而不会费力地创建一个完全基于任何工作负载的完全基于代码的ETL解决方案从较小的记录到中等大小的记录(例如5–100亿条记录),并且仍然具有良好的性能。如果您需要更多,则有更多专用工具。但是,不要说Entity Framework无法处理大量数据。 您的代码无法处理大量数据。EF很好。

可悲的是,自2005年以来我们就拥有SqlBulkCopy,但我们却假装工具箱中有这个大漏洞。问题已经解决。重新发明轮子的理由为零。你猜怎么了?它也支持流!

AsTracking与AsNoTracking

我觉得自己的成绩很差。关于EF的另一个大抱怨是它的数据缓存。您几乎总是可以告诉DbContext摆脱缓存的实体。不过,最近,我们获得了将其设置为Entity Framework Core中默认策略的能力。相反,我们可以选择要跟踪的内容,而不是不需要的内容。我很高兴地承认一个烦恼,即您仍然需要分离实体。

流式传输

实体框架中的查询通常在返回之前缓冲所有结果。流技术解决了这一问题,并立即让您开始处理数据进入应用程序的过程。您既可以更快地开始工作,又可以使服务器对内存更友好。

特殊雪花

在开发人员中,我看到了一个令人不安的趋势。缺乏探索和发明的欲望。我们想要开箱即用的解决方案,在不了解细节的情况下“可行”。即使代码不是魔术,我们仍然相信看不见的魔术。

我采用的一般方法不是构建这些固定的仓储,而是构建扩展,使我们的应用程序以我们需要它们的独特方式运行。是否希望在运行时间较长的过程中缓存数据的好处,但又不能在给定操作之外继续存在呢?对于我来说,这听起来像是DbContext的完美扩展方法,该方法可以获取一些实体,对其进行处理以获得缓存的好处,然后在返回之前清除缓存。另一种扩展方法是在操作完成后分离所有那些实体的方法。

不要害怕

我在这里谈论DbContext是因为有很多人对待它。它被视为一件大,笨重,笨拙的事情,如果您不小心的话,它们会偷走您的孩子。我们花了很长的时间才能使DbContext的存在只为少数几个组件所知。这将我们的实现进一步扼杀到仓储中。由于我们必须遍历仓储以获取任何类型的数据,因此我们需要在发生更改时定期违反“开放/关闭”原则,或者被迫接受仓储指示的决策膨胀的折衷,并且在使用时要格外小心我们打电话给它。

释放DbContext。如果模块需要数据,请不要自欺欺人,说DbContext还不是依赖项。我可以向您保证,如果您对它的可访问性感到满意,并消除“人们犯错了怎么办?!”的神秘主义。它实际上将使我们整体上变得更好。如果某人可以提交顽皮的代码,并且至少使它经过一次未经测试的生产,则您实际上就没有发布控制或质量检查。诸如隐藏DbContext之类的策略是您组织中已经流血的伤口上的权宜之计,无助于真正缓解实际问题。

别再找借口

我们程序员必须停止像解决我们所遇到的问题的解决方案那样行动,或者必须使用node.js和dapper的正确组合来区分它们,这并不是说它们没有合法用途,而是经常被他们当作替罪羊实体框架是一种很好的工具,可以用来做某事。我们十年来拥有的工具已经足以满足我们的大多数需求。一次又一次的错误决定最终导致错误的决定,使我们陷入困境。使您的工具适应自如。尝试新事物。可以肯定的是,我们只能怪自己。

使用REST API 最佳实践简述

Posted on 2020-05-12 | Edited on 2021-04-25 | In 技术

使用REST API 最佳实践简述

Facebook,Google,Github,Netflix,Amazon和Twitter等许多巨头都拥有自己的REST(ful)API,您可以访问它们来获取甚至写入数据。

但是,为什么所有都需要REST?

那样好吗,为什么如此盛行?

当然,这不是传达消息的唯一方法吗?

REST和HTTP有什么区别?

好吧,事实证明REST非常灵活,并且与 Internet所基于的主要协议HTTP兼容。由于它是一种架构风格而不是标准,因此它提供了实现各种设计最佳实践的大量自由。以及听说它与语言无关?你一定觉得它很棒吧。

在此博客文章中,我们的目标是尽可能清楚地解释REST,以便您可以清楚地了解何时以及如何使用REST,以及它的本质。

我们将介绍一些基础知识和定义,并展示一些REST API最佳实践。这应该为您提供了以您喜欢的任何编码语言实现REST API所需的全部知识。

如果您对HTTP不太熟悉,建议您阅读我们的HTTP系列文章,或者至少阅读其中的第1部分,这样您可以更轻松地理解这些资料。

因此,在这篇文章中,我们将讨论:

关于REST:

  • 什么是REST?
  • REST是否绑定到HTTP?
  • REST和HATEOAS支持
  • RESTful API是什么意思?
  • REST过多又称为RESTafarian综合征

REST API最佳做法:

  • 抽象与具体API
  • URI格式(名词,不是动词)。正确网址与错误网址示例
  • 错误处理
  • 状态码
  • 安全
  • REST API版本控制
  • 文件的重要性

那么REST本质上是什么?

REST(代表性状态转移)是Roy Fielding在其博士学位中创立的一种建筑风格。UC Irvine的论文“ 体系结构样式和基于网络的软件体系结构设计 ”。 他与HTTP 1.1同步提出了这种观点。

我们主要将REST用作在万维网上的计算机系统之间进行通信的一种方式。

REST是否绑定到HTTP?

根据定义,似乎与Http强制绑定?其实并非如此。尽管您可以将其他一些应用程序协议与REST一起使用,但是在实现REST时,HTTP仍然是应用程序协议中无可争议的冠军。

REST和HATEOAS支持

作为应用程序状态引擎的 HATEOAS或超媒体 是每个可扩展且灵活的REST API的重要功能。

该HATEOAS约束建议,客户端和服务器通信完全采用了超媒体。

使用超媒体有几个优点:

  • 使API设计人员能够在每个响应中包括他们所能提供的一切,以正确地提供一件事以及与相关端点的超媒体链接,从而使设计脱钩
  • 帮助API更优雅地发展和成熟
  • 为用户提供更深入地探索API的方法

因此很明显,HATEOAS在设计时考虑了耐用性。

GitHub的工作方式如下:

GET https://api.github.com/users/codemazeblog

响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"login": "CodeMazeBlog",
"id": 29179238,
"avatar_url": "https://avatars0.githubusercontent.com/u/29179238?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/CodeMazeBlog",
"html_url": "https://github.com/CodeMazeBlog",
"followers_url": "https://api.github.com/users/CodeMazeBlog/followers",
"following_url": "https://api.github.com/users/CodeMazeBlog/following{/other_user}",
"gists_url": "https://api.github.com/users/CodeMazeBlog/gists{/gist_id}",
"starred_url": "https://api.github.com/users/CodeMazeBlog/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/CodeMazeBlog/subscriptions",
"organizations_url": "https://api.github.com/users/CodeMazeBlog/orgs",
"repos_url": "https://api.github.com/users/CodeMazeBlog/repos",
"events_url": "https://api.github.com/users/CodeMazeBlog/events{/privacy}",
"received_events_url": "https://api.github.com/users/CodeMazeBlog/received_events",
"type": "User",
"site_admin": false,
"name": "Code Maze",
"company": "Code Maze",
"blog": "https://code-maze.com",
"bio": "A practical programmers' resource.",
...
}

如您所见,除了客户端请求的关键信息之外,您还可以在响应中找到一堆相关的超媒体链接,这些链接将您带到您可以自由浏览的API的其他部分。

RESTful API是什么意思?

“ RESTful”意味着一些功能:

  • 客户端-服务器体系结构:完整的服务由作为整个系统前端的“客户端”和作为后端的“服务器”组成
  • 无状态:服务器不应在不同请求之间保存任何状态。会话状态完全由客户负责。按照REST定义: 所有REST交互都是无状态的。也就是说,每个请求都包含连接器理解该请求所需的所有信息,而与之前的任何请求无关。(Roy的论文ch.5.2.2)\
  • 可缓存的:客户端应该能够将响应存储在缓存中以提高性能

因此,RESTful API是一项遵循这些规则的服务(希望如此),并使用HTTP方法来操纵资源集。

但是为什么我们需要或使用RESTful API?

因为它们为我们提供了一种简单,灵活和可扩展的方式来制作可通过Internet进行通信的分布式应用程序。

我们可以拥有更多的REST方法吗?

是的,你猜对了。是的,我们可以🙂

正如Mike Schinkel定义的那样,对于狂热地遵循REST的人们来说甚至还有一个短语 。

RESTifarian是Roy T. Fielding在他的博士论文第五章中定义的REST软件架构风格的狂热支持者。论文在UCIrvine。你可以在rest - discussion邮件列表中找到野外的RESTifarians。但是要小心,RESTifarians在讨论休息的细节时可能是极其细致的,正如我最近在参与列表时所了解到的。🙂

太多的事情都是不好的。

我们需要一点实用主义才能做出好的应用程序和服务。了解和理解一种理论很重要,但是该理论的实现是区分不良与良好与卓越应用的区别。所以要聪明,要牢记最终用户。

因此,让我们走一些使API变得“光彩”的重要点,使用户的生活变得更加轻松。

抽象与具体API

在开发软件时,我们经常使用抽象和多态来获取大多数应用程序。我们想重用尽可能多的代码。

那么我们也应该这样写我们的API吗?

好吧,API并非完全如此。对于REST API,具体要比abstract好。你能猜出为什么吗?

让我向您展示一些示例:

让我们看两个API版本。它是最好有有一个的API /entities,或者有一个API /owners,/blogs并 /blogposts 分别?

作为开发人员,哪一个对您更具描述性?您想使用哪个API?

我总是会选择第二个。

URI格式(名词,不是动词)。正确网址与错误网址示例

这是另一种REST API最佳实践。您应该如何格式化端点?

如果使用软件开发方法,您将得到如下所示的结果:

1
2
3
4
5
6
/getAllBlogPosts
/updateBlogPost/12
/deleteBlogPost/12
/getAuthorById/3
/deleteAuthor/3
/updateAuthor/3

您明白了……会有很多端点,每个端点都在做其他事情。有一个更好的系统可以解决这些问题。

将资源视为名词,将HTTP方法视为动词。如果这样做,最终将得到如下结果:

GET /blogposts –获取所有博客文章

GET /blogposts/12 –获取ID为12的博客文章

POST /blogposts –添加新的博客文章并返回详细信息

DELETE /blogposts/12 –删除ID为12的博客文章

GET /authors/3/blogposts –获取ID为3的作者的所有博客文章

这是创建API的更简洁,更精确的方法。对于最终用户而言,这是显而易见的,并且有一种解决方法。

通过使用单数而不是复数来表示资源名称,可以使其更加简洁。那取决于你。

错误处理

API构建的另一个重要方面。有几种处理错误的好方法。

让我们看看顶级狗如何做到这一点:

推特:

  • 请求: GET https://api.twitter.com/1.1/account/settings.json

  • 响应:状态码400

    Twitter response

1
{"errors":[{"code":215,"message":"Bad Authentication data."}]}

Twitter为您提供状态代码和错误代码,并简要描述了所发生错误的性质。他们让您在“ 响应代码”页面上查找代码。

脸书:

  • 请求: GET https://graph.facebook.com/me/photos

  • 响应:状态码400

    Facebook Response

    1
    {  "error": {   "message": "An active access token must be used to query information about the current user.",   "type": "OAuthException",   "code": 2500,   "fbtrace_id": "DzkTMkgIA7V"  }}

Facebook为您提供了更具描述性的错误消息。

特威里奥:

  • 请求: GET https://api.twilio.com/2010-04-01/Accounts/1234/IncomingPhoneNumbers/1234

  • 响应:状态码404

    1
    <?xml version='1.0' encoding='UTF-8'?><TwilioResponse>  <RestException>    <Code>20404</Code>    <Message>The requested resource /2010-04-01/Accounts/1234/IncomingPhoneNumbers/1234 was not found</Message>    <MoreInfo>https://www.twilio.com/docs/errors/20404</MoreInfo>    <Status>404</Status>  </RestException></TwilioResponse>

Twilio默认为您提供XML响应,并提供指向文档的链接,您可以在其中找到错误的详细信息。

如您所见,错误处理的方法因实现而异。

重要的是 不要让REST API的用户“挂断”,不知道发生了什么,或者漫无目的地在StackOverflow的浪费中徘徊,寻找解释。

状态码

在设计REST API时,我们通过使用HTTP状态代码与API用户进行通信 。状态代码很多,描述了多种可能的响应。

但是,我们应该使用多少? 我们在每种情况下都应该有严格的状态码吗?

就像生活中的许多事情一样, KISS原则 也适用于这里。那里有70多个状态代码。你内心了解他们吗?潜在的API用户会全部了解它们,还是会再次使用Google搜索?

大多数开发人员都熟悉最常见的状态代码:

  • **200 OK**
  • **400 Bad Request**
  • **500 Internal Server Error**

从这三个开始,您可以涵盖REST API的大多数功能。

其他常见的代码包括:

  • **201 Created**
  • **204 No Content**
  • **401 Unauthorized**
  • **403 Forbidden**
  • **404 Not Found**

我们可以使用它们来帮助用户快速找出结果。如果您感觉到状态代码的描述性不如我们在“错误处理”部分中讨论的那样,则可能应该包含某种消息。再一次,我们需要务实,通过使用 数量有限的代码 和描述性消息来帮助用户。

您可以在此处找到完整的HTTP状态代码列表,以及在CodeMaze上总结的其他有用的HTTP内容 。

安全

关于REST API安全的说法不多,因为 REST不处理安全问题。它依赖于诸如基本身份验证或摘要身份验证之类的标准HTTP机制 。

每个请求都应 通过HTTPS进行。

有很多技巧可以提高REST API的安全性,但是由于REST的无状态性,因此在实施它们时必须谨慎。记住最后一个请求的状态超出了窗口,应该在 客户端存储和验证状态。

时间戳记和日志记录 请求也可以有所帮助。

关于这个话题还有很多要说的,但这超出了本文的范围。我们有一个不错的职位HTTP安全 在这里CodeMaze如果您想了解更多关于这一点。

REST API版本控制

您已经编写了REST API,它已经非常成功,许多人已经使用它并对此感到满意。但是,您拥有的多汁的新功能会破坏系统的其他部分。重大变化。

不用担心,有解决方案!

在开始制作您的API之前,我们可以通过在端点之前加上API版本来对其进行版本控制:
https://api.example.com/v1/authors/2/blogposts/13

这样,只要API发生重大更改,我们就可以始终增加API版本号(例如v2,v3…)。这也向用户发出信号,表明已发生了翻天覆地的变化,在使用新版本时,请务必小心。

文件的重要性

这是不言而喻的。您可能是世界上最好的API设计人员,但是 如果没有文档,您的API就像死了一样。

正确的文档 对于每个软件产品和Web服务都是必不可少的。

我们可以通过保持一致并使用清晰和描述性的语法来帮助用户。但是,好的文档页面并没有真正的替代品。

一些很好的例子:

https://www.twilio.com/docs/api/rest/

https://developers.facebook.com/docs/

https://developers.google.com/maps/documentation/

还有很多其他…

有许多工具可以帮助您记录您的API,但是不要忘记让人参与其中,只有一个人可以正确地理解另一个人。至少现在是这样(看着你)。

结论

我们讨论了REST API构建的许多概念,并介绍了一些顶级REST API最佳实践。在一次提供这些API时,您可能会觉得有些奇怪或难以接受,但是请尝试自己创建REST API。并尝试实现一些您在这里学到的REST API最佳实践。

甲方爸爸,大概你要的是代码生成器吧?

Posted on 2020-05-08 | Edited on 2021-04-25 | In 技术

甲方爸爸,大概你要的是代码生成器吧?

一

1)

有一天,我的朋友Y童鞋分享了他正在做的一个内部开源项目,这个开源项目从外表上看,跟目前市场上那些代码生成器本没有特别大的区别,所以我兴趣并不大。

在他给我介绍了一下具体需求之后,我才体会了他的意思,并提起了那么一丢丢兴趣。。

毕竟,听起来有点“鬼扯”,为啥?因为,他居然试图依靠这个项目来生成”单元测试“。。。。

他:定义好数据库表和结构,然后就生成逻辑方法和代码、以及界面,还同时把“单元测试”代码给生成了,免得程序员要花时间去思考代码逻辑之余,还要想怎么写出可测试代码。

我:这样生成的代码还有灵魂么。。

他:有啊,编写高可测试代码,不就是我辈中人,这些有追求的码农应该实现的目标么?

我:这种模式怎么越看越像埃里克埃文斯大佬说的“Smart UI”模式啊。。

他:你倒这么说,也有那么一点点像。

2)

我:当然,能够生成单元测试倒也可以。毕竟大部分单元测试看起来似乎是一模一样的。无外乎就是“ Arrange\Act\Assert”,AAA操作猛如虎,测试代码一把梭。

他:我这个东西,生成的代码,除了看起来提高了单元测试覆盖率之外,其实,并不能提高代码的质量。

我:是什么逼得你要花时间去开发这样的代码生成器呢?

他:还不是被这班菜鸡开发者们产出的劣质产品闹腾的。我不是想着省测试的钱,又能提高产品的质量么?就自然而然只能靠压榨“程序员”来实现了。但是让我来对这么多人的代码进行审查,还是太难了。这不,用单元测试来操作,不就可以了?

3)

我: 你们太难了。为啥这么赶啊?

他:这不是甲方爸爸要加需求,他说得倒是好:加需求也就几行代码,多简单。但是我们这边,就得忙翻天,太特么累了。

我:那能不能多招几个测试?

他:端到端测试,只是看起来将缺陷扼杀在摇篮而已,实际上。。隐藏在冰山下的缺陷呢。。客户就是小白鼠啊。再说,我们现在家业太小,测试有两个了,再招就请不起了。。功能是不能少的,bug是不能多的,我只能想想单元测试这种办法了。

我:好吧。。我们也一样。。

二

1)

之前有个朋友老张介绍了一个故事,仿佛跟这个有点类似。他有幸参与了一个交通信息化的项目,这个项目的业主是国企单位,属于“体制内”的企业。

在过去一波有一波的信息化发展过程中,这些体制内的企业仿佛成为了许多IT企业竞相薅羊毛的对象。为啥,国企项目多、钱也不少,关键是国企对软件质量要求不高啊。

许多企业借助国企项目,他们依托所谓“快速上线”的神器,将中华民族艰苦奋斗的精神发挥到了极致,公司能够在最短的时间内,将原本停留在脑海里的软件,快速的转化成为实现,并部署到甲方爸爸的现场环境中。

至于软件的质量、软件的工程化水平,对不起,不重要?用户体验?性能?功能可用性?重要么,不重要。先快速上线回款再说。于是,这些企业获得了业绩的腾飞,老板们赚得盆满簸赢,好不自在。

而且老板们还会吹:我们公司最大的优点,就是在逆境下生存的能力,能够在最短的时间消化需求,做出最符合客户需求的软件。

好吧,仿佛这也是软件工程的一种方向?快速开发。。。。

2)

然后,有那么几年,市场突然间就“做烂“了。一方面,国家将投资方向重点放在了房地产领域,对信息化的投入也逐渐收紧了许多;另外一方面,企业过去匆忙上线了太多的软件系统,不同软件系统之间的对接沟通困难,操作过程缺乏连贯性,使得基层员工开始抗拒这些”看似“能够带来效率提升,却容易出现各种质量问题导致自己过去几天工作量返工的所谓”信息化“系统;另外,大家也都很清楚,效率提升其实带来的是”裁员“,首先被裁的…

总之,有那么一段时间,国企对信息化是“弃若敝屣”的。

三

1)

但,随着“互联网”和“共享出行”的兴起,又让这个概念重新热炒了起来。

老张他们公司也有幸接到了一个这样的项目,公司还是一家非常大的出租车公司,拥有二十多家子公司,员工超过2万人。这个项目的目的是打通出租车和旅客的关系,借助于手机实现快速出行,同时打通企业内部信息孤岛,让总公司领导能够第一时间看到各种数据的流转情况,为建立科学决策提供依据。

老张被选为这个项目的负责人。在项目启动会上,他意气风发,向业主和公司老板们保证,将带领公司团队与甲方团队一起,以饱满的姿态打响这场战役,为业主的业绩腾飞贡献自己的一份力量。

然而,但项目启动后,他才深刻的明白这究竟是一个怎样的坑。

2)

首先是业主关系,由于业主是一家涉及大几万员工和二十几家子公司的大型集团公司,需要梳理的业务表单非常复杂,业务流程和体系,远比甲方爸爸预想的要复杂得多。

其次是开发周期短,不知从何时起,国企对于软件系统的印象就是“简单、容易、很快就能实现”,仿佛一个需求只要说出来,这般不要命的程序员们就能很快的实现功能。当老张跟他们提到需求太多,根本做不完时,甲方爸爸甚至说:怎么可能有做不出来的软件系统,是不是你能力不行?

再次是外部系统多,由于不同的子公司往往采购了不同的系统,要统一对接到一个系统 。

还有就是涉及的技术点多,需要在许多领域进行专门的技术攻关,由于公司暂时缺乏相关资源,使得开发过程屡屡收到阻塞。

2)

经过长达几个月的需求调研,老张编写了一份超过一千页纸的需求规格说明书,并获得了业主的批准,但项目正式开始时,他却只获得了短短半年的开发周期。此时,他手上能够调动的开发者资源,才不过10余人。

为了干完这个项目,他和项目团队的成员不得不牺牲周末和假期,辛苦坚持了半年,才把项目功能都开发完,但在项目实施环节时,由于子公司与总公司的意见不统一,根本用不起来。

最终,项目崩盘,公司倒闭,甲方将近一年的投入近乎白费。老张和项目团队也白白辛苦了大半年,还得去劳动仲裁,找老板讨薪。

回顾这段时光时,老张说了一段话:

“企业信息服务化的项目,看起来合同工价很高,其实都是坑啊。有的业主,根本不懂什么叫“合适的软件”。

在互联网如此发展的今天,这些业主,要的还是“快速开发”。但凡想到什么就往里面加,程序员不猝死太难了。许多需求今天提,明天就要,但是用了一次呢这些功能就不用了,我根本不知道这些软件,干出来有什么意义。

千万别跟业主提敏捷,他们会在你的每个迭代中塞根本干不完的工作量;也不要提拥抱变化,他们变起脸来,自己都怕。”

四

仔细想想,许多传统企业领导想转型到互联网,不就是这样么,恨不能一天就把项目干完,干完项目就“升官发财”,至于项目能不能用,谁知道呢。。

也许,他们要的并不是软件,而是一种“代码生成器”。嗯,输入“甲方爸爸的一万种需求”,输出“一个功能齐全、包容万物、自由变化的软件”。。

作为有追求的码农们,我们能像Robert大叔在《代码整洁之道-程序员的自我修养》一书中写的方法:选择“拒绝”么。

额。。生存要紧。。偶尔吐吐槽,饭还是得恰啊。

提升WebAPI性能的几个小建议

Posted on 2020-05-03 | Edited on 2021-04-25 | In 技术

typora-root-url: ........\image.techq.xyz\images\improve-webapi

提升WebAPI性能的几个小建议

本文作者:德本德拉·达什(Debendra Dash)

原文来自: https://www.c-sharpcorner.com/article/Tips-And-Tricks-To-Improve-WEB-API-Performance/

Web API是Microsoft作为.NET框架的一部分而开发的一项技术,它使用户能够与异构平台进行通信,包括网站,移动设备和桌面应用程序等。在编写Web API时,我们应该关注其性能和响应时间。在这里,我列出了在提高Web API性能时需要考虑的几点。

1

在Web API中使用并行编程

Web API逻辑主要处理2个重要功能-将数据发布到服务器以进行插入或更新,以及从服务器获取数据。当我们有成千上万的记录要从服务器获取时,响应时间非常高。这是因为有时我们将不得不遍历数据源,进行几次更改,然后将数据发送到客户端。而且,一个简单的foreach循环是一个单线程循环,该循环逐个顺序处理数据以给出结果集。

因此,在这种情况下,建议在从服务器获取数据时使用并行foreach循环。由于并行的foreach循环在多线程环境中工作,因此执行速度将比foreach循环更快。

并行Foreach循环的执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14


```c#
List<Employee> li = new List<Employee>();
li.Add(new Employee { Id = 1001, Name = "Sambid", Company = "DELL", Location = "Bangalore" });
li.Add(new Employee { Id = 1002, Name = "Sumit", Company = "DELL", Location = "Bangalore" });
li.Add(new Employee { Id = 1003, Name = "Koushal", Company = "Infosys", Location = "New Delhi" });
li.Add(new Employee { Id = 1004, Name = "Kumar", Company = "TCS", Location = "Bangalore" });
li.Add(new Employee { Id = 1005, Name = "Mohan", Company = "Microsoft", Location = "Hyderabad" });
li.Add(new Employee { Id = 1006, Name = "Tushar", Company = "Samsung", Location = "Hyderabad" });
li.Add(new Employee { Id = 1007, Name = "Jia", Company = "TCS", Location = "Pune" });
li.Add(new Employee { Id = 1008, Name = "KIRAN", Company = "CTS", Location = "Bangalore" });
li.Add(new Employee { Id = 1009, Name = "Rinku", Company = "CGI", Location = "Bangalore" });
```

这是Employee类:

1
2
3
4
5
6
7
8
9
public class Employee  
{
public int Id { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public string Designation { get; set; }
public string Location { get; set; }

}

这就是我们可以使用Parallel Foreach循环的方式。

该循环将立即执行并给出结果。在Foreach循环的情况下,它一一给出结果。假设结果集中有1000条记录,则循环将执行一次1000次以得到结果。

注意: 当您要获取的记录数量很少时,请不要使用Parallel foreach循环。

使用异步编程来处理并发的HTTP请求

同步编程中发生的事情是,每当有请求执行Web API时,就会将线程池中的线程分配给要执行的请求。该线程被阻塞,直到执行该过程并返回结果为止。

在这里,T2 线程一直处于阻塞状态,直到它处理请求并返回结果为止;如果遇到长的执行循环,则它将花费大量时间并一直等待到结束。

假设我们只有3个线程,并且所有线程都已分配给队列中正在等待的三个请求。在这种情况下,如果第四个请求在我们同步实现时出现,它将发出错误,因为现在它没有任何线程可以处理该请求。

因此,要处理更多数量的并发HTTP请求,我们必须使用异步编程。

异步请求处理程序的操作有所不同。当请求到达Web API控制器时,它将获取其线程池线程之一,并将其分配给该请求。

在进程开始执行的同时,线程将返回到线程池。执行完成后,为该请求分配了另一个线程来带来该请求,因此,该线程将不会等到进程执行完成后才返回到线程池以处理另一个请求。

因此,在这里我给出了一个小的注册示例,以及如何在Web API中使用异步编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[AllowAnonymous]  
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
Dictionary<object, object> dict = new Dictionary<object, object>();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
tbl_Users obj = new tbl_Users();
obj.Active = false;
obj.FirstName = model.FirstName;
obj.LastName = model.LastName;
obj.Email = model.Email;
obj.UserId = user.Id;
DefEntity.tbl_Users.Add(obj);
if (DefEntity.SaveChanges() == 1)
{
dict.Add("Success", "Data Saved Successfully.");
return Ok(dict);
}
else
{
return Content(HttpStatusCode.BadRequest, "User Details not Saved.");
}
}
else
{
return GetErrorResult(result);
}
}

压缩Web API的结果

Web API压缩对于提高ASP.NET Web API性能非常重要。在Web中,数据以包(数据包)的形式通过网络传输,从而增加了数据包的大小,这将增加大小并增加Web API的响应时间。因此,减小数据包大小可提高Web API的加载性能。

通过压缩API响应,我们具有以下两个优点。

  • 数据大小将减小

  • 响应时间将增加(增加客户端和服务器之间的通信速度。)

    我们可以通过编码和IIS中的某些设置来压缩Web API。我已经在以下链接中介绍了使用编码对Web API进行压缩的方法。

    • 使用DotNetZip压缩Web API响应
    • 压缩Web API响应
  • 同样,我们可以通过检查动态内容压缩模块来启用IIS压缩 。

  • 因此,通过这种方式,我们可以压缩Web API响应以实现性能。

使用缓存提高性能

缓存是一种在一定时间段内将常用数据或信息存储在本地存储器中的技术。因此,下一次,当客户端请求相同的信息时,它将从本地内存中提供信息,而不是从数据库中检索信息。缓存的主要优点是它通过减少处理负担来提高性能。我们有几种方法可以在Web api中实现缓存。在下面的链接中,我描述了一种实现缓存的方法。

  • 在Web API中实现缓存

    因此,在这里您将找到我们如何在Web API中实现缓存以及它将如何帮助提高性能。

使用高速JSON序列化器

我们经常使用JSON而不是XML来在服务提供者和服务客户端之间交换数据。首先,我们使用它是因为JSON是轻量级的。

在.NET中,有很多序列化器。最受欢迎的是Json.NET,Microsoft选择它作为Web API的默认JSON序列化器。Json.NET之所以出色,是因为它快速,健壮且可配置。

有几种序列化器比Json.Net更快。下面提到其中一些。

我们可以看到Protobuf-Net和JIL是非常快速的序列化程序,因此,如果可以代替Json.net来实现它们,则显然可以提高性能。协议缓冲区或Protobuf是Google的官方序列化程序。要在.Net中使用JIL序列化程序,我已经写了一篇文章,您可以在下面的链接中进行检查。

  • 在C#中使用Jil序列化器和反序列化器库

创建适当的数据库结构

为了提高任何应用程序的性能,我们应该将重点放在数据库结构上。我可以说数据库结构在提高性能方面起着重要作用。这些是我们在处理数据库时应检查的一些注意事项。

  • 尝试使规范化表结构。

  • 为所有表提供适当的索引,以便从表中轻松搜索结果。

  • 将所有相互关联的表与外键和主键相关联。

    我认为在创建数据库结构以提高性能时,我们必须至少遵循这三个规则。

尝试从客户端验证某些属性

从客户端而不是服务器端验证某些模型属性非常好。一旦我们在未经客户端验证的情况下放置了无效数据,则它将进入服务器并检查其是否为有效数据。如果它是无效的数据,它将从服务器给出错误。因此,在这里我们可以使用客户端验证检查往返行程。因此,如果我们可以通过客户端(移动设备,网站等)进行任何可能的验证,那将是很好的。

因此,我认为,如果我们专注于以上讨论的要点,则可以以某种方式提高Web API的性能。

.TDD学习笔记(二)单元测试

Posted on 2020-04-30 | Edited on 2021-04-25 | In 技术

单元测试

定义

单元测试最早来源于Kent Beck,他在开发SmallTalk中引入了这个概念,随着软件工程学的不断发展,使得单元测试已经成为软件编程中一项非常有用的实践。

在维基百科中,“单元测试”是这样定义的:

一个单元测试是一段代码(通常是一个方法),这段代码调用另一段代码,然后检验某些假设的正确性。如果这些假设是错误的,单元测试就失败了。一个单元可以是一个方法或一个函数。

而《单元测试的艺术》作者Roy Osherove则认为,一个单元不仅仅是一个方法,也有可能是包括实现某个功能的多个类和函数。

什么是好的单元测试

Roy Osherove同时也认为,一个单元测试应该具备以下特征:

  1. 它应该是自动化的,可重复执行。
  2. 它应该很容易实现;
  3. 它应该第二天还有意义;
  4. 任何人都应该能一键运行它;
  5. 它应该运行很快;
  6. 它的结果应该是稳定的(如果运行之间没有进行修改的话,多次运行一个测试应该总是能够返回同样的结果)
  7. 它应该能完全控制被测试的单元;
  8. 它应该是完全隔离的(独立于其他测试的运行);
  9. 如果它失败了,我们应该很容易发现什么是期待的结果,进而定位问题所在。

    几种概念

    在这篇博客中,作者对Fake、Mock、Stub进行了对比。

Fakes(伪造)

图片

Fake创建的对象,看似跟原对象一致,但是简化了原来对象的某些行为,使得我们在进行代码过程中,无需通过启动数据库或其他外部组件,即可对服务进行集成测试。

Mock(模拟)

图片

mock是在调用方法中,注入“模拟”的完整的被调用者对象,并在Test方法中,通过注入的这个模拟对象来执行对应的操作。

Stub(打桩)

图片

存根是预先定义一个方法的返回值,以便我们在调用该方法时,返回存根对象,这样使我们的代码不会以不改变原方法、或对原方法产生副作用的情况下,实现某方法。

横切关注点

部分关注点「横切」程序代码中的数个模块,即在多个模块中都有出现,它们即被称作「横切关注点(Cross-cutting concerns, Horizontal concerns)」。

横切关注点也是面向对象编程中的概念,我们通俗意义上理解的AOP框架,可以理解为解决横切关注点问题的一种框架。

日志、异常处理、服务调用、方法调用链路都是大家会遇到的一类关注点问题,而而在《单元测试的艺术》这本书中,作者也指出“时间”(DateTime)也同样是一种问题。例如,如果我们在代码中普遍使用了系统默认的DateTime.Now,那么假设我们要测试代码在元旦和非元旦日期中的不同行为时,是不是手动把系统时间修改为指定的时间?这显然是的代码不利于维护,也不利于代码的可测试性。

通过定义了一个SystemTime 对象来解决这个问题,确实是一种非常不错的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 [TestFixture]
    public class TimeLoggerTests
    {
        [Test]
        public void SettingingSystemTime_Always_ChangesTime()
        {
            SystemTime.Set(new DateTime(2000, 1, 1));
            string output = TimeLogger.CreateMessage("a");
            StringAssert.Contains("01.01.2000", output);
        }
    }
 public class SystemTime
    {
        private static DateTime _dateTime;
        public static void Set(DateTime custom)
        {
            _dateTime = custom;
        }
        public static void Reset()
        {
            _dateTime = DateTime.MinValue;
        }
        public static DateTime Now
        {
            get
            {
                if (_dateTime != DateTime.MinValue)
                {
                    return _dateTime;
                }
                return DateTime.Now;
            }
        }
        
    }

测试框架

测试框架是用来辅助开发者进行单元测试的代码库。在.NET开发环境下,我们常见的的测试框架可以分成以下两种类型:

单元测试框架

单元测试框架框架是帮助开发者进行单元测试的代码库和模块,它也可以作为自动编译过程的一个步骤运行测试。单元测试的框架如此之多,而在.NET中,常见的主要包括这几种:

1、MSTest:这是Visual Studio中最常见的测试框架,在除Visual Studio2019以前的版本中,创建的单元测试项目自带的就是这种测试框架。

2、XUnit:XUnit是一个大家族,在Java、.NET、等多种技术语言下都有XUnit的身影。

3、NUnit:在许多介绍单元测试的书籍中,都会采用NUnit作为示例,在本文中,也主要介绍这种框架。

隔离(模拟)框架

隔离(模拟)是一种可编程的API,使用这种API可以使得创建为对象比手工编写简便、快速和容易。常见的隔离(模拟)框架包括以下几种:

1、Moq:在.NET中常见的Mock框架。

2、NSubstitute:在《单元测试的艺术》一书中,作者Roy Osherove着重介绍过这种测试隔离框架,也经常和Moq框架一起进行比较。

3、Microsoft Fakes:也是一种模拟框架,经常被用于和上述模拟框架对比。

4、FakeItEasy、EasyMoq、JustMock框架:其他模拟框架。

编写良好测试代码中常见的问题

如何给测试方法命名

方法的命名一直是困扰开发者的难题,尤其是单元测试方法。我们该如何给单元测试方法命名呢?目前我了解到两种不同的命名方法:

假设,现有一个新增方法为:

1
public int Add(int x,int y)

一种是Should开头的单元测试命名方法,可以命名为

1
Should_Returns_Sum_When_Add_Numbers();

另外一种是在《单元测试的艺术》这本书中作者用到的命名方法,作者将单元测试命名为三个部分,分别为:被测试方法名,测试场景,预期行为,将三个部分用下划线“_”分开,例如MethodUnderTest_Scenario_Behavior()。按照这个命名方法,上述方法可以被命名为:

1
Add_Nums_Returns_ResultsOfInteger();

静态类或单例如何进行单元测试

静态类

在.NET Framework中经常互相静态类和静态对象,这无形中给我们的单元测试过程带来了不少困扰。我们可以采取以下方式对这些静态类进行测试。

1、静态类应该只限于静态的方法,例如像StringExtension这样的扩展方法,这种方式是可以直接进行测试的。

2、对于历史代码中为包含不少静态成员的“静态”对象,应该将其改成有IoC框架注入的单例对象,这样就能使用mock的方式进行单元测试。

3、对于无法修改的静态对象,我们可以考虑将其隔离。

单例

而对于单例代码,则可以采用将单例逻辑和单例持有者分开的方式,让代码更易于测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MySingleton
{
      private static MySingleton _instance;
      public static MySingleton Instance
      {
          get
          {
              if (_instance == null)
              {
                  _instance = new MySingleton();
              }
              return _instance;
          }
      }
      public void Foo()
      {


      }
}

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public class MySingletonLogic
    { 
        public void Foo()
        {


        }
    }
    public class MySingletonHolder
    {
        private static MySingletonLogic _instance;
        public static MySingletonLogic Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new MySingletonLogic();
                }
                return _instance;
            }
        } 
    }

通过这种方式的改造,使得我们能够非常方便的对Foo方法进行测试了。

何时开始进行单元测试?

最好的时机就是当下,当你需要键入一行逻辑代码时,先写一个测试方法,按照TDD的流程进行开发,将有利于你的代码开发过程处于“自信满满”的状态,而且还能减少代码调试的时间,进而提高代码开发的效率。

.TDD学习笔记(一)单元测试

Posted on 2020-04-30 | Edited on 2021-04-25 | In 技术

引子

回顾

虽然我很早以前就听说单元测试,也曾经多次在项目中引入单元测试框架和单元测试的实践为代码质量的提升带来了一丝助力。

但这种方式更多的是从软件调试的角度出发,即将单元测试作为一种测试方法可用性的入口,而非从TDD、极限编程、或从”Fail Fast,Fix Fast”这种获得快速反馈的方式来使用单元测试,使得实际过程中单元测试的效果并不明显。

直到去年8月下旬开始参加极客学院的TDD实战课才进一步深入了解基于TDD的单元测试的流程、方法和实践的全过程,当时也间歇性的练习了一点Args等Kata项目,才逐步体会到TDD的妙处。

虽然到目前为止对于TDD的了解依然很浅,但在开发过程中,总是有意无意的“站在调用者的角度思考业务逻辑”,并尽可能的思考如何“编写可测试的代码”,总归是一种进步。

我的教训

总结自己学习TDD的一些经验教训:

  1. 需求的识别,总是按惯性一次性把整个需求全部提取了。
  2. 总是习惯于拿着代码一把梭,没有按照“Arange,Assert,Art”的步骤来规划任务。
  3. 步子迈得太大,方法拆得不够细,过程式代码的味道很浓,例如,在练习String Calculate过程中,就有非常明显的问题。当然,这也是许多初学TDD开发者的通病。

图片

图片

  1. 没有深刻理解“重构”的意义,只是把通过单元测试当做一个目的。
  2. Kata的练习频率依然不高,一周只有两到三次,每次不到一个小时。
  3. 单元测试和方法的命名不太规范,无法让人产生直接的理解。
  4. 方法的代码行较多,不符合优质代码的标准。
  5. 方法间适当的空行(分段)很重要。

    什么是TDD

    定义

    TDD的全称是“测试驱动开发”,也是一种旨在提升代码质量的开发实践。这种开发实践的主要步骤是在编写产品代码之前,先编写单元测试代码,然后再由测试代码来决定写什么产品代码,其目的是取得快速反馈,并使用“illustrate the main line”方法来构建程序。

测试驱动开发是戴两顶帽子思考的开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,通过去除冗余的代码,提高代码质量。测试驱动着整个开发过程:首先,驱动代码的设计和功能的实现;其后,驱动代码的再设计和重构。

原则

在上述经验教训过程中,有些步骤其实与TDD的三原则相违背,让我们来回顾一下这三个原则:

  • 不允许编写任何产品代码,除非允许失败的测试通过。

  • 不允许编写多余一个的失败测试,编译成功也是失败。

  • 不允许编写多于恰好让测试通过的产品代码。

    步骤

    TDD其实也有一系列完整的操作流程,包括如下五个步骤:

  • 添加一个小的测试

  • 运行所有测试并且失败

  • 做一点修改

  • 运行所有测试并且成功

  • 重构以消除重复

图片

图1(红绿重构)

可行性之争

TDD也是一种充满争议的开发实践,许多人都吐槽,这种方式在原本开发代码之余,还得额外花三分之一的时间来编写测试代码。不过,我还是推荐《代码整洁之道-程序员的自我修养》这本书中,Robert Bob大叔在第5.1小节说的几句话:

1、此事已有定论!
2、争论已经结束!
3、GOTO是有害的!
4、TDD确实可行。

他明确指出:

过去人们对TDD充满争议,就此发表了不少博客和文章,如今争议依旧来袭。所不同的是,以前人们是认真尝试着去批判和理解TDD,而现在只有夸夸奇谈而已。结论很清楚,TDD的确切实可行,而且每个开发人员都要适应和掌握TDD。

TCR

在《极限中国社区》曾经介绍了一种测试驱动开发过程中的实践模式,这种实践被称为“TCR”。

在实践过程中,开发者始终保持着测试,成功则提交,失败则回滚到上次的代码这样的循环。在并使用了插件来进行自动化回滚,确保每个方法的开发时间被控制在非常小的时间粒度上。这样保证了开发者能够以非常小的步子,非常快的频率,实现代码的开发过程。

图片

TCR

Kata

优秀的代码从来不是天生的,而是通过后天不断的练习培养出来的,尤其是要想写出符合面向对象设计的的好代码,更是需要“刻意练习”。

Kata被人称为是唯一的一种练习TDD的形式。

“Kata”是一种来自日语词汇“形式”的翻译,它描述了武术练习中,通过一种精心编排的动作模式,用来训练自己达到肌肉记忆的水平。

Kata的练习例子是如此之多,只要你有心,总是能在海量的示例代码中找到最适合自己的一个例子。

例如,我所使用的Roy Osherove设计的例子“String Calculate”就是一个非常不错的示例。(当然,我给出的是一段反例代码。)

1
2
3
4
5
6
7
8
9
10
1、An empty string returns zero
2、A single number returns the value
3、Two numbers, comma delimited, returns the sum
4、Two numbers, newline delimited, returns the sum
5、Three numbers, delimited either way, returns the sum
6、Negative numbers throw an exception
7、Numbers greater than 1000 are ignored
8、A single char delimiter can be defined on the first line (e.g. //# for a ‘#’ as the delimiter)
9、A multi char delimiter can be defined on the first line (e.g. //[###] for ‘###’ as the delimiter)
10、Many single or multi-char delimiters can be defined (each wrapped in square brackets)

当然,FizzBuzz或The Prime Factors Kata,Args也是一个非常不错的示例。重要的并非例子本身,而是通过持续不断的练习,形成自己的肌肉记忆。
在引文中,作者Peter Provost认为,最好的办法:

我对人们的建议是连续两周每天早上做一个30分钟的练习。然后再选一个,每天做一次,坚持两周
我不建议大家在工作中练习Kata,除非他们已经准备好了。你应该通过练习一周或六次,直到你已经决定对TDD的循环非常适应为止。否则,就像参加了一场没有技术水平的比赛。

1234…9

溪源

81 posts
4 categories
4 tags
博客园 CSDN
© 2021 溪源
Powered by Hexo v3.9.0
|
Theme – NexT.Pisces v7.1.2
#
ICP备案号:湘ICP备19001531号-2