首页 > 代码库 > 不再用.NET框架

不再用.NET框架

.NET 平台很棒。真的很棒。直到它不再那么棒。我为什么不再用 .NET?简单来说,它限制了我们选择的能力(对我来说很重要),转移了我们的注意力,使得我们向内认知它的安全性,替代了帮助我们认知外面广阔世界的所有可能性。

  [系好安全带:这个文章的长度几乎成了一本书…]

优点

  首先让我开始说说 .NET 做得对的许多事吧,尽管这其中的大多数并不来自 .NET 本身,但却是由 .NET 社区而来。

  C#

  C#令人惊叹。我认为它是一个令人惊叹的编程语言。从强大的C语言背景而来,我彻底地喜欢其语法,流和这门语言的所带来的感觉。当然有我可能改变的事,但总体来说它是一门扎实的语言。并且基于开发人员使用的编程语言如此巨额的百分比和 Windows 操作系统的优越性,它是一门众所周知的语言。

  ReSharper

  我也很喜欢 Resharper。在 JetBrains 工作的开发者们都是奇迹般的人。如果没有 ReSharper 和一些相关的工具,我可能并不会如此喜欢C#。

  BDD and MSpec

  我也很喜欢简称为机器规格(mspec)的 BDD 风格的框架。它是一个令人惊叹的测试框架,真正支持在测试中使用正确的语言测试本身。在使用 mspec 之前,我的测试真是一团糟并且很碍我的事。

  另外,当我们创建 GoConvey—基于 Golang 的 BDD 测试框架的时候,Mspec 对于我的组织来说是一个巨大的灵感和激励。

  多语言运行时

  我认为多语言的 CLR (公共语言运行时)的观念真得使得 JVM 的世界思考着。我不知道任何非 Java 的 JVM 语言在 CLR 之前,但随着“公共语言运行时”的到来,我的理解是这使得使用 JVM 的人们向前进并且最终创造了如 Scala 和 Clojure 这样伟大的 JVM 编程语言。如果我错了请纠正我。再者,CLR 使得 Sun 公司的人们坐下来并关注它,因为 Java 有一点陈旧并且随着 Java 8 的到来,仅仅现在才在多个方面追赶着。竞争是一件非常好的事。

  NuGet

  另一个显著的例子是 NuGet。这个包在 Windows 中作为一个整体特别是在 Windows 的开发中,它的管理轶事是糟透的。NuGet 解决了很多问题,他们也通过从 Python 和 Ruby 借用了很多东西去做了很多正确的事。有改进的余地吗?当然。但比起其他一些选择在这儿或那儿的包升级来说,我还没有感到使用 NuGet 有这许多痛楚。

  Mono

  对于 Mono 的开发者们,我不能不说太棒了。他们所创造的太惊奇了。没有任何官方支持和不顾潜在的悬在他们头上的法律问题,他们向前推进并创造了一个居然能替代官方运行时的实现。我已经有一些运行在产品中应用程序,在 Mono 下运行了几乎一年而没有任何问题。它的产品准备好了吗?这可能取决于你的应用程序(见下文“Mono”)。

  CQRS 和事件溯源

  可以认为,关于 .NET 最好事之一是,它是 CQRS 的诞生地并有相关的技术:事件溯源。就算这样,CQRS+ES 本身并没有什么很新的东西。正如 Greg Young 将会告诉你的,这是由一堆 40 年历史原料为我们重新打包并更名的。对于大型代码库我有些非常严重的问题,当我 5 年前使用 CQRS+ES 的时候,它完全释放了我的域。CQRS+ES 现在是命名模式的并且其成长是显而易见的。这可能是因为 .NET 已经能够和其他的开发平台交互共享的原因。除了这个之外,大多数的创新是从外部来的。

  缺点

  优点先放在一边,让我们看看什么出错了和我为什么不再用 .NET 框架。关于我最近开发平台的迁移,最能激励我的事是我可以利用许多最好的部分而丢下不好的部分(如下文所说)。

  Windows

  正如前文所述,当面对基于网络的服务器软件时,Windows 并不是一个好的选手。在我看来,Windows 的另一个真正的大问题是传统的 Windows 开发者是通常仅仅擅长于 Windows,当他们离开安乐窝之后就会很快迷失,这对于 Linux 开发者来说却不是问题。计算远不止是 Windows。开发者仅仅能操作单一的操作系统的一个问题是它不可避免得导致 Windows 的激增。换句话说,Windows 生了 Windows。没办法打破这个循环。

  另一方面,*NIX 的开发者通常熟悉多操作系统(Linux,Unix,OSX,Windows 等等),一个操作系统的内部工作原理,不同的分布(基于 Debian 和基于 Fedora),窗口管理器,桌面管理器,文件系统,包管理,编译,重新编译,重新打包,命令行“fu”等等。

  我的一个心病是文件系统。NTFS 并不是系统唯一的文件系统,对于任何给予的任务它几乎都不是最好的选择。ZFS,BTRFS,ReiserFs,ext*等等,有一些很酷的特性。我也很喜欢为了各种高速/透明的磁盘操作,能从 BASH 创建回路设备或者创建 RAM 设备。这在 Windows 中不会发生—如果没有第三方软件的话。

  在 AWS 云服务中,启动一个 Windows 机器要花掉足足 10 多分钟。我大约 15-20 秒就能启动一个简单的 Linux 机器。当涉及到云计算规模,它能够迅速扩展是很重要的,因为当扩展很重要时,10-15 分钟就像是永恒的。

  Visual Studio

  在我这另一根刺,当属 Visual Studio。我需要一个大大超出预期的 IDE 去做任何开发,这个想法困扰着我。它只是如 Windows 一样庞大的资源猪。我有一个内核 i7 3770K 3.5GHZ 的台式机,以 16GB 的内存和最大 4512GB 的固态硬盘去编译。它差不多刷爆了 Windows 体验指数,但 Windows+VS 仍然很慢。(是的,ReSharper 使得它更慢了,但是 ReSharper 对这来说是值得的。)

  现在我在 MacBook Pro 上开发,它比起我的强大的台式机来说只有更少的 CPU 马力,但运行明显更快,在一个短小的学习曲线之后,UX(用户体验)变得无限美好了。事实上,我甚至不再用鼠标了—我的双手一直在键盘或触控板上,我可以用手势操作我的电脑并让它回应—不像在 Windows。

  关于 VS 很酷的一个事是调试器。它的查看和使用,令人难以置信得方便。每隔一段时间会在监视窗口报告错误的值,导致花费更多时间去调试。同时,这也是很大的负面,因为 CLR 默认的,多线程的世界使得我一开始就需要一个调试器。没有调试器是一个解脱的体验,因为它迫使你以另一种方式编程。

  VS 同样也有创建“csproj”和“sln”文件的坏毛病。我恨这些。当然,C#必须知道编译什么和何时编译。我理解这点。在 Golang 中,引用在代码中使用了很重要的语句。如果它不是 .NET 中用到的工程文件,我可能使用简单的文本编辑器编码C#,并且对这门语言更流畅。使用 git rebase 操作时,这些文件也有导致合并冲突。

  别让我开始说换行符的差异。我不能相信直到今天我们还在处理这样的事。如果 VS 解决方案文件以 Linux 行结束符结束,通过双击它并不能载入该解决方案,因为 VS 解决方案文件分析器读不出它来。

  源代码管理

  幸运的是,我早就跳出了微软阵营的源代码管理(版本控制系统 VSS)。我早在 2000 年初,在 VSS 无数次丢失了我的提交之后,就使用了 Subversion (译者注:Subversion 是开源的版本控制系统)。之后 git (译者注:git 是开源的版本控制系统,内容管理系统等)出现了,我又迷上了它。不幸的是,没有 Windows 的接口—对我来说是典型的遭遇。最终有人创建了一个接口,我就用了那个并且没有回头。Git 是一把非常锋利的刀,但当你正确运用它的时候,它是一个强大而高效的工具。我曾经在一个小工程中用过 TFS (译者注:Team Foundation Server,工作流协作引擎),它是一个怪物—和所有来自 Redmond (译者注:美国微软总部)的产品一样。它感染了我的项目文件并且污染了我的源代码目录。真可恶。不,还是谢谢你。给了我任意一天用命令行 git…或者可能是SourceTree,如果你需要从 GUI 得到一点关爱。

  Mono

  是的,这是第二次提及 Mono。正如 Mono 本身如此惊艳一样。在 .NET 的世界,它仍然二等公民。无论什么时候我尝试在 Mono 上运行任何重要的东西,我通常都在和漏洞作斗争。幸运的是,对下载代码,查找问题,发送请求和在 Linux 上编译代码我没有感到不舒服。但是这件事我都记不清做了多少遍了。

  是的,CLR 是个巨大的怪物,并且对一个非官方的应用在不同的操作系统都有相同的行为,简直是个类似于分开红海的奇迹。但事实是,我不得不花费如此多的时间来填补漏洞以使我的代码能够正确运行,实在是很难为其辩护。

  Mono 的特定区域也慢。也许它不是在慢在过载,但对我来说 Web 服务器是关键所在。并且它非常慢,最后,慢到了最底下—即使是微不足道的东西。我想好消息是它只能从这儿得到更好的。我也应该提及 Mono 的开发者可能忘了 Linux,比起我可能知道的还多,所以我不能太挑剔。

  IIS

  也许 IIS 在尝试着为太多的应用程序做太多的事情。它从作为一个 web 服务器变为像 J2EE 应用程序容器一样的应用程序宿主。它也站在慢速这一边。我猜如果我需要更高的性能,我应该编写我自己的 web 服务器,但我真的很想只关注我应用程序的代码。可能利用 Windows 事件服务器将是好的,但 nginx (译者注:一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器)和其他服务器只是不喜欢在 Windows 中生产。

  虚拟的以 JVM 为基础的实现,例如 Netty (译者注:JBOSS 提供的一个 java 开源框架),很容易处理每秒 650K+/的请求量。IIS 在运行一个简单的 CLR 应用程序“Hello,World!”,处理大约每秒 50K 的请求量时就会壅塞。(有趣的题外话,参考基准开发者通过 TCP 套接字创建了一个简单的 C# 的 web 服务器,它能处理大约每秒 120K 的请求量。)

  狭隘的心理

  前些年有个运动叫做 ALT.NET。该运动是全部是关于寻找我们自身之外的更广阔的开发社区以作为一个整体,并汇聚不同的部分。有趣的是,那是 StructureMap、Autofac、NuGet、ASP.NET MVC 和许多其它工具的灵感来源。在传统的 .NET 的圈子里,这个运动受到了很多的不屑和鄙视。我把这看作是,作为一个整体的社区普遍的狭隘心理和怠惰的一个极大的例证。(的确,它们中的一些可能会消失,进而以包括 Redis,MongoBD 还有其它的不同的技术而出现。)

  有这么多很棒的方案在那里。假定微软已注定是唯一正确之路的想法是荒谬的。如果是这样的话,我们就都还在使用 Visual Studio 的设计工具去拖放按钮和链接元素到一个 WebForm 的界面上,我们会设定了该按钮并且依赖 ViewState 以帮助我们与可怕的 HTTP 所带来的恐惧隔开。我从我的一个部署的代码库中最后一个 WebForm 中摆脱的那一天,是个光荣的值得庆贺的日子。

  谁又曾想过“网络控制”是个好主意?很显然我考虑过因为我喝了 Kool-Aid (译者注:卡夫公司出品的饮料,这里意指明知是注定的或有危险的仍然去做,有负面涵义)并且完全接受它。它狠咬了我。见过 2MB 的 ViewState 吗?

  [注:当我写这篇文章的时候,原来的标题,“为什么我不再用 .NET”,意味着整个 .NET 生态系统。标题感觉有点短于是我更新为“为什么我不再用 .NET 框架”。我想 .NET 作为一个生态系统,包括了所有的工具,工程,平台,组织还有很多开发者。这就是为什么有些更广泛的 .NET 社区的元素在我的这篇文章中受到抨击原因。]

  性能杀手

  C,Java 和 C# 中典型的多线程范例都强烈推荐使用锁和互斥。对于锁来说有个隐藏的开销:它们慢得难以忍受。使用 Disruptor (JVM 中的无锁的环形缓存[译者注:实际上就是拥有一个序号指向下一个可用元素的数组]),你可以很容易得每秒处理 20M 以上的事件。在 .NET 中使用规定的“最佳实践”等任何超过每秒十几次的传输,都被认为是体面又好的性能表现,在这一点上来说你仅仅需要更大/更好/更多的硬件设备。事实上,我见过第三方客户端库(Rabbit,Couch,Mongo 等等)中锁语句遍布整个代码。即使在我的代码中没有任何的并发,默认的和首选的方法都用了锁。

  无锁的、事件驱动的方法允许你大幅降低硬件和资金支出。大部分应用程序可以轻易地运行在两台机器上,第二台机器仅仅在冗余和失效备援时是必须的,以防因为硬件相关的问题导致第一台机器不可用的时候起作用。

  这个问题的另一个方面是调用网络和磁盘子系统的传统方式:同步,阻塞代码。如果你需要多个并发的 HTTP 请求,你需要更多的线程。大多数人不知道的是,为维持线程多出的1-2MB 和上下文切换线程的需求,使得 CPU 内核消耗所有的时间颠簸在上下文切换上而不是做真正的工作。所以现在我们得到了在一个应用程序中数百或数千的线程,占用了 RAM,并造成 CPU 停滞不前。还有个更好的方式。

  Netty/NIO (JVM),Erlang,Node,Gevent (Python)和 Go 都支持使用事件驱动的子系统操作(选择/epoll[译者注:Linux 内核中的一种可扩展 IO 事件处理机制]/kqueue[译者注:FreeBSD 的可扩展的事件通知接口])。这就意味着当等待数据包被 tx/rx 跨网络的时候,CPU 可以自由地去做其它,重要的工作。因为 JVM 的成熟,Netty 可以认为是做这项工作最快的,但我喜欢 Go 用 Goroutines 操作这个的方式—它简单,优雅,很容易推理,没有像意大利面条一样的回调。

  SQL Server

  作为一名 .NET 开发者,当你开始一个新的工程时,有一些事是你通常会去做的:

  • 创建一个新的 solution
  • 将其部署到 Team Foundation Server (译者注:Microsoft 应用程序生命周期管理 (ALM) 解决方案的核心协作平台)
  • IIS 中建立相应的网站入口
  • 创建一个新的 SQL Server 数据库
  • 在 solution 中关联 Entity Framework (通常是 2010 年之后创建的工程)
  • 开始设计你的数据库和 ActiveRecord 实体

  在大多数情况下这不是编写代码的正确方式。当然它可能在某些情况下有效,但是作为一个“默认的架构”它并不是你想要的。为什么在我们甚至还没理解问题领域之前已经做了任何技术上的选择?这简直是本末倒置了。

  微软的生态系统鼓励每个人使用 SQL Server。在 Visual Studio 中和 SQL Service 进行交互或者使用 SQL Management Studio (和它的前身,SQL 查询分析器)是如此令人难以置信的容易。这种以数据库为中心的重点,是钦定的或唯一正确的方式的一部分。它使你更加迷恋微软。厂商锁定始终对厂商来说是好的。

  为什么我们要如此开发?为什么我们不更多地考虑应用程序的行为而不是它如何存储的?现在我所有的项目都使用基于 JSON 的键/值存储。有了这种功能,我可以选择任何我想要的存储引擎,包括 SQL Server,Oracle,PostgreSQL,MySQL,Cassandra, CouchDB, CouchBase, Dynamo, SimpleDB, S3, Riak, BerkeleyDB, Firebird, Hypertable, RavenDB, Redis, Tokyo Cabinet/Tyrant, Azure Blobs,文件系统中的明文 JSON 文件等等等等。突然之间,我们能够开始根据其优点而不是仅仅对其熟悉来选择存储引擎了。

  题外话:在 AWS RDS 的云上运行过 SQL Server 吗?别这么做。当然它会工作,但是一些例如复制这样最简单的事是不存在的。文章充斥着对 SQL Server 不能在 AWS RDS 上工作的引用。

结论

  也许我在软件开发中学到的两件最重要的教训是:

  • 边界和封装的重要性(以多种形式)
  • 付出代价以得到正确的模型和抽象

  许多年前我恨“模型”这个词。每个人都会把它到处扔,它是一个如此过载的术语,很难理解它的含义和它为什么这么重要。就这点来说,我仅仅会说模型是对你想要封装的现实的一个有限的表示。也许最简单的例子就是地球仪的墨卡托投影了。这很确切得说明了一件事:导航。如果你在其他的事情上使用它,它并不毫无价值。如果你不专注于付出代价去使模型正确,去封装商业现实,那么没有任何技术能够拯救你。

  我对 .NET 最大的抱怨是,“唯一正确的方式”引导你远离理想的模型并把你推向关注实现细节和技术缺陷的方向。这样的关注导致技术实施渗血并且感染模型,最终导致它腐烂变质,因为它不能适应不断变化的商业需求。当这发生的时候,开发者挣扎着并蹬踢着,如同吸毒者一样,他们从一个新技术转向另一个,以期望下一个强大的技术能够治愈他们的病痛。

  技术本身并不是灵丹妙药,相反地,它是关于取舍和选择。只有正确地理解了商业行为并把它们封装进结构良好的,易于理解的模型中,以帮助保持技术堆栈在属于它的地方—作为一个实现细节。

  And that’s why I left the .NET Framework because it kept reasserting itself and wanting to be more than it was: an implementation detail. 这就是我为什么不再用 .NET 框架,因为它不断地重申自己(的主张),不断地想要比它的本身更多的:一个实现细节。