為什么我不再用 .NET 框架
.NET平臺很棒。真的很棒。直到它不再那么棒。我為什么不再用.NET?簡單來說,它限制了我們選擇的能力(對我來說很重要),轉(zhuǎn)移了我們的注意力,使得我們向內(nèi)認(rèn)知它的安全性,替代了幫助我們認(rèn)知外面廣闊世界的所有可能性。
[系好安全帶:這個文章的長度幾乎成了一本書…]
優(yōu)點(diǎn)
首先讓我開始說說.NET做得對的許多事吧,盡管這其中的大多數(shù)并不來自.NET本身,但卻是由.NET社區(qū)而來。
C#
C#令人驚嘆。我認(rèn)為它是一個令人驚嘆的編程語言。從強(qiáng)大的C語言背景而來,我徹底地喜歡其語法,流和這門語言的所帶來的感覺。當(dāng)然有我可能改變的事,但總體來說它是一門扎實(shí)的語言。并且基于開發(fā)人員使用的編程語言如此巨額的百分比和Windows操作系統(tǒng)的優(yōu)越性,它是一門眾所周知的語言。
ReSharper
我也很喜歡Resharper。在JetBrains工作的開發(fā)者們都是奇跡般的人。如果沒有ReSharper和一些相關(guān)的工具,我可能并不會如此喜歡C#。
BDD and MSpec
我也很喜歡簡稱為機(jī)器規(guī)格(mspec)的BDD風(fēng)格的框架。它是一個令人驚嘆的測試框架,真正支持在測試中使用正確的語言測試本身。在使用mspec之前,我的測試真是一團(tuán)糟并且很礙我的事。
另外,當(dāng)我們創(chuàng)建GoConvey—基于Golang的BDD測試框架的時候,Mspec對于我的組織來說是一個巨大的靈感和激勵。
多語言運(yùn)行時
我認(rèn)為多語言的CLR(公共語言運(yùn)行時)的觀念真得使得JVM的世界思考著。我不知道任何非Java的JVM語言在CLR之前,但隨著“公共語言運(yùn)行時”的到來,我的理解是這使得使用JVM的人們向前進(jìn)并且最終創(chuàng)造了如Scala和Clojure這樣偉大的JVM編程語言。如果我錯了請糾正我。再者,CLR使得Sun公司的人們坐下來并關(guān)注它,因?yàn)镴ava有一點(diǎn)陳舊并且隨著Java 8的到來,僅僅現(xiàn)在才在多個方面追趕著。競爭是一件非常好的事。
NuGet
另一個顯著的例子是NuGet。這個包在Windows中作為一個整體特別是在Windows的開發(fā)中,它的管理軼事是糟透的。NuGet解決了很多問題,他們也通過從Python和Ruby借用了很多東西去做了很多正確的事。有改進(jìn)的余地嗎?當(dāng)然。但比起其他一些選擇在這兒或那兒的包升級來說,我還沒有感到使用NuGet有這許多痛楚。
Mono
對于Mono的開發(fā)者們,我不能不說太棒了。他們所創(chuàng)造的太驚奇了。沒有任何官方支持和不顧潛在的懸在他們頭上的法律問題,他們向前推進(jìn)并創(chuàng)造了一個居然能替代官方運(yùn)行時的實(shí)現(xiàn)。我已經(jīng)有一些運(yùn)行在產(chǎn)品中應(yīng)用程序,在Mono下運(yùn)行了幾乎一年而沒有任何問題。它的產(chǎn)品準(zhǔn)備好了嗎?這可能取決于你的應(yīng)用程序(見下文“Mono”)。
CQRS 和事件溯源
可以認(rèn)為,關(guān)于.NET最好事之一是,它是CQRS的誕生地并有相關(guān)的技術(shù):事件溯源。就算這樣,CQRS+ES本身并沒有什么很新的東西。正如Greg Young將會告訴你的,這是由一堆40年歷史原料為我們重新打包并更名的。對于大型代碼庫我有些非常嚴(yán)重的問題,當(dāng)我5年前使用CQRS+ES的時候,它完全釋放了我的域。CQRS+ES現(xiàn)在是命名模式的并且其成長是顯而易見的。這可能是因?yàn)?NET已經(jīng)能夠和其他的開發(fā)平臺交互共享的原因。除了這個之外,大多數(shù)的創(chuàng)新是從外部來的。
缺點(diǎn)
優(yōu)點(diǎn)先放在一邊,讓我們看看什么出錯了和我為什么不再用.NET框架。關(guān)于我最近開發(fā)平臺的遷移,最能激勵我的事是我可以利用許多最好的部分而丟下不好的部分(如下文所說)。
Windows
正如前文所述,當(dāng)面對基于網(wǎng)絡(luò)的服務(wù)器軟件時,Windows并不是一個好的選手。在我看來,Windows的另一個真正的大問題是傳統(tǒng)的Windows開發(fā)者是通常僅僅擅長于Windows,當(dāng)他們離開安樂窩之后就會很快迷失,這對于Linux開發(fā)者來說卻不是問題。計(jì)算遠(yuǎn)不止是Windows。開發(fā)者僅僅能操作單一的操作系統(tǒng)的一個問題是它不可避免得導(dǎo)致Windows的激增。換句話說,Windows生了Windows。沒辦法打破這個循環(huán)。
另一方面,*NIX的開發(fā)者通常熟悉多操作系統(tǒng)(Linux,Unix,OSX,Windows等等),一個操作系統(tǒng)的內(nèi)部工作原理,不同的分布(基于Debian和基于Fedora),窗口管理器,桌面管理器,文件系統(tǒng),包管理,編譯,重新編譯,重新打包,命令行“fu”等等。
我的一個心病是文件系統(tǒng)。NTFS并不是系統(tǒng)唯一的文件系統(tǒng),對于任何給予的任務(wù)它幾乎都不是最好的選擇。ZFS,BTRFS,ReiserFs,ext*等等,有一些很酷的特性。我也很喜歡為了各種高速/透明的磁盤操作,能從BASH創(chuàng)建回路設(shè)備或者創(chuàng)建RAM設(shè)備。這在Windows中不會發(fā)生—如果沒有第三方軟件的話。
在AWS云服務(wù)中,啟動一個Windows機(jī)器要花掉足足10多分鐘。我大約15-20秒就能啟動一個簡單的Linux機(jī)器。當(dāng)涉及到云計(jì)算規(guī)模,它能夠迅速擴(kuò)展是很重要的,因?yàn)楫?dāng)擴(kuò)展很重要時,10-15分鐘就像是永恒的。
Visual Studio
在我這另一根刺,當(dāng)屬Visual Studio。我需要一個大大超出預(yù)期的 IDE 去做任何開發(fā),這個想法困擾著我。它只是如Windows一樣龐大的資源豬。我有一個內(nèi)核i7 3770K 3.5GHZ的臺式機(jī),以16GB的內(nèi)存和最大4512GB的固態(tài)硬盤去編譯。它差不多刷爆了Windows體驗(yàn)指數(shù),但Windows+VS仍然很慢。(是的,ReSharper使得它更慢了,但是ReSharper對這來說是值得的。)
現(xiàn)在我在MacBook Pro上開發(fā),它比起我的強(qiáng)大的臺式機(jī)來說只有更少的CPU馬力,但運(yùn)行明顯更快,在一個短小的學(xué)習(xí)曲線之后,UX(用戶體驗(yàn))變得無限美好了。事實(shí)上,我甚至不再用鼠標(biāo)了—我的雙手一直在鍵盤或觸控板上,我可以用手勢操作我的電腦并讓它回應(yīng)—不像在Windows。
關(guān)于VS很酷的一個事是調(diào)試器。它的查看和使用,令人難以置信得方便。每隔一段時間會在監(jiān)視窗口報(bào)告錯誤的值,導(dǎo)致花費(fèi)更多時間去調(diào)試。同時,這也是很大的負(fù)面,因?yàn)镃LR默認(rèn)的,多線程的世界使得我一開始就需要一個調(diào)試器。沒有調(diào)試器是一個解脫的體驗(yàn),因?yàn)樗仁鼓阋粤硪环N方式編程。
VS同樣也有創(chuàng)建“csproj”和“sln”文件的壞毛病。我恨這些。當(dāng)然,C#必須知道編譯什么和何時編譯。我理解這點(diǎn)。在Golang中,引用在代碼中使用了很重要的語句。如果它不是.NET中用到的工程文件,我可能使用簡單的文本編輯器編碼C#,并且對這門語言更流暢。使用git rebase操作時,這些文件也有導(dǎo)致合并沖突。
別讓我開始說換行符的差異。我不能相信直到今天我們還在處理這樣的事。如果VS解決方案文件以Linux行結(jié)束符結(jié)束,通過雙擊它并不能載入該解決方案,因?yàn)閂S解決方案文件分析器讀不出它來。
源代碼管理
幸運(yùn)的是,我早就跳出了微軟陣營的源代碼管理(版本控制系統(tǒng)VSS)。我早在2000年初,在VSS無數(shù)次丟失了我的提交之后,就使用了Subversion(譯者注:Subversion是開源的版本控制系統(tǒng))。之后git(譯者注:git是開源的版本控制系統(tǒng),內(nèi)容管理系統(tǒng)等)出現(xiàn)了,我又迷上了它。不幸的是,沒有Windows的接口—對我來說是典型的遭遇。最終有人創(chuàng)建了一個接口,我就用了那個并且沒有回頭。Git是一把非常鋒利的刀,但當(dāng)你正確運(yùn)用它的時候,它是一個強(qiáng)大而高效的工具。我曾經(jīng)在一個小工程中用過TFS(譯者注:Team Foundation Server,工作流協(xié)作引擎),它是一個怪物—和所有來自Redmond(譯者注:美國微軟總部)的產(chǎn)品一樣。它感染了我的項(xiàng)目文件并且污染了我的源代碼目錄。真可惡。不,還是謝謝你。給了我任意一天用命令行g(shù)it…或者可能是SourceTree,如果你需要從GUI得到一點(diǎn)關(guān)愛。
Mono
是的,這是第二次提及Mono。正如Mono本身如此驚艷一樣。在.NET的世界,它仍然二等公民。無論什么時候我嘗試在Mono上運(yùn)行任何重要的東西,我通常都在和漏洞作斗爭。幸運(yùn)的是,對下載代碼,查找問題,發(fā)送請求和在Linux上編譯代碼我沒有感到不舒服。但是這件事我都記不清做了多少遍了。
是的,CLR是個巨大的怪物,并且對一個非官方的應(yīng)用在不同的操作系統(tǒng)都有相同的行為,簡直是個類似于分開紅海的奇跡。但事實(shí)是,我不得不花費(fèi)如此多的時間來填補(bǔ)漏洞以使我的代碼能夠正確運(yùn)行,實(shí)在是很難為其辯護(hù)。
Mono的特定區(qū)域也慢。也許它不是在慢在過載,但對我來說Web服務(wù)器是關(guān)鍵所在。并且它非常慢,最后,慢到了最底下—即使是微不足道的東西。我想好消息是它只能從這兒得到更好的。我也應(yīng)該提及Mono的開發(fā)者可能忘了Linux,比起我可能知道的還多,所以我不能太挑剔。
IIS
也許IIS在嘗試著為太多的應(yīng)用程序做太多的事情。它從作為一個web服務(wù)器變?yōu)橄馢2EE應(yīng)用程序容器一樣的應(yīng)用程序宿主。它也站在慢速這一邊。我猜如果我需要更高的性能,我應(yīng)該編寫我自己的web服務(wù)器,但我真的很想只關(guān)注我應(yīng)用程序的代碼??赡芾肳indows事件服務(wù)器將是好的,但nginx(譯者注:一個高性能的HTTP和反向代理服務(wù)器,也是一個IMAP/POP3/SMTP代理服務(wù)器)和其他服務(wù)器只是不喜歡在Windows中生產(chǎn)。
虛擬的以JVM為基礎(chǔ)的實(shí)現(xiàn),例如Netty(譯者注:JBOSS提供的一個java開源框架),很容易處理每秒650K+/的請求量。IIS在運(yùn)行一個簡單的CLR應(yīng)用程序“Hello,World!”,處理大約每秒50K的請求量時就會壅塞。(有趣的題外話,參考基準(zhǔn)開發(fā)者通過TCP套接字創(chuàng)建了一個簡單的C#的web服務(wù)器,它能處理大約每秒120K的請求量。)
#p#
狹隘的心理
前些年有個運(yùn)動叫做ALT.NET。該運(yùn)動是全部是關(guān)于尋找我們自身之外的更廣闊的開發(fā)社區(qū)以作為一個整體,并匯聚不同的部分。有趣的是,那是StructureMap、Autofac、NuGet、ASP.NET MVC和許多其它工具的靈感來源。在傳統(tǒng)的.NET的圈子里,這個運(yùn)動受到了很多的不屑和鄙視。我把這看作是,作為一個整體的社區(qū)普遍的狹隘心理和怠惰的一個極大的例證。(的確,它們中的一些可能會消失,進(jìn)而以包括Redis,MongoBD還有其它的不同的技術(shù)而出現(xiàn)。)
有這么多很棒的方案在那里。假定微軟已注定是唯一正確之路的想法是荒謬的。如果是這樣的話,我們就都還在使用Visual Studio的設(shè)計(jì)工具去拖放按鈕和鏈接元素到一個WebForm的界面上,我們會設(shè)定了該按鈕并且依賴ViewState以幫助我們與可怕的HTTP所帶來的恐懼隔開。我從我的一個部署的代碼庫中最后一個WebForm中擺脫的那一天,是個光榮的值得慶賀的日子。
誰又曾想過“網(wǎng)絡(luò)控制”是個好主意?很顯然我考慮過因?yàn)槲液攘薑ool-Aid(譯者注:卡夫公司出品的飲料,這里意指明知是注定的或有危險(xiǎn)的仍然去做,有負(fù)面涵義)并且完全接受它。它狠咬了我。見過2MB的ViewState嗎?
[注:當(dāng)我寫這篇文章的時候,原來的標(biāo)題,“為什么我不再用.NET”,意味著整個.NET生態(tài)系統(tǒng)。標(biāo)題感覺有點(diǎn)短于是我更新為“為什么我不再用.NET框架”。我想.NET作為一個生態(tài)系統(tǒng),包括了所有的工具,工程,平臺,組織還有很多開發(fā)者。這就是為什么有些更廣泛的.NET社區(qū)的元素在我的這篇文章中受到抨擊原因。]
性能殺手
C,Java和C#中典型的多線程范例都強(qiáng)烈推薦使用鎖和互斥。對于鎖來說有個隱藏的開銷:它們慢得難以忍受。使用Disruptor(JVM中的無鎖的環(huán)形緩存[譯者注:實(shí)際上就是擁有一個序號指向下一個可用元素的數(shù)組]),你可以很容易得每秒處理20M以上的事件。在.NET中使用規(guī)定的“最佳實(shí)踐”等任何超過每秒十幾次的傳輸,都被認(rèn)為是體面又好的性能表現(xiàn),在這一點(diǎn)上來說你僅僅需要更大/更好/更多的硬件設(shè)備。事實(shí)上,我見過第三方客戶端庫(Rabbit,Couch,Mongo等等)中鎖語句遍布整個代碼。即使在我的代碼中沒有任何的并發(fā),默認(rèn)的和首選的方法都用了鎖。
無鎖的、事件驅(qū)動的方法允許你大幅降低硬件和資金支出。大部分應(yīng)用程序可以輕易地運(yùn)行在兩臺機(jī)器上,第二臺機(jī)器僅僅在冗余和失效備援時是必須的,以防因?yàn)橛布嚓P(guān)的問題導(dǎo)致第一臺機(jī)器不可用的時候起作用。
這個問題的另一個方面是調(diào)用網(wǎng)絡(luò)和磁盤子系統(tǒng)的傳統(tǒng)方式:同步,阻塞代碼。如果你需要多個并發(fā)的HTTP請求,你需要更多的線程。大多數(shù)人不知道的是,為維持線程多出的1-2MB和上下文切換線程的需求,使得CPU內(nèi)核消耗所有的時間顛簸在上下文切換上而不是做真正的工作。所以現(xiàn)在我們得到了在一個應(yīng)用程序中數(shù)百或數(shù)千的線程,占用了RAM,并造成CPU停滯不前。還有個更好的方式。
Netty/NIO (JVM),Erlang,Node,Gevent (Python)和Go都支持使用事件驅(qū)動的子系統(tǒng)操作(選擇/epoll[譯者注:Linux內(nèi)核中的一種可擴(kuò)展IO事件處理機(jī)制]/kqueue[譯者注:FreeBSD的可擴(kuò)展的事件通知接口])。這就意味著當(dāng)?shù)却龜?shù)據(jù)包被tx/rx跨網(wǎng)絡(luò)的時候,CPU可以自由地去做其它,重要的工作。因?yàn)镴VM的成熟,Netty可以認(rèn)為是做這項(xiàng)工作最快的,但我喜歡Go用Goroutines操作這個的方式—它簡單,優(yōu)雅,很容易推理,沒有像意大利面條一樣的回調(diào)。
SQL Server
作為一名.NET開發(fā)者,當(dāng)你開始一個新的工程時,有一些事是你通常會去做的:
- 創(chuàng)建一個新的solution
- 將其部署到Team Foundation Server(譯者注:Microsoft 應(yīng)用程序生命周期管理 (ALM) 解決方案的核心協(xié)作平臺)
- IIS中建立相應(yīng)的網(wǎng)站入口
- 創(chuàng)建一個新的SQL Server數(shù)據(jù)庫
- 在solution中關(guān)聯(lián)Entity Framework(通常是2010年之后創(chuàng)建的工程)
- 開始設(shè)計(jì)你的數(shù)據(jù)庫和ActiveRecord實(shí)體
在大多數(shù)情況下這不是編寫代碼的正確方式。當(dāng)然它可能在某些情況下有效,但是作為一個“默認(rèn)的架構(gòu)”它并不是你想要的。為什么在我們甚至還沒理解問題領(lǐng)域之前已經(jīng)做了任何技術(shù)上的選擇?這簡直是本末倒置了。
微軟的生態(tài)系統(tǒng)鼓勵每個人使用SQL Server。在Visual Studio中和SQL Service進(jìn)行交互或者使用SQL Management Studio(和它的前身,SQL查詢分析器)是如此令人難以置信的容易。這種以數(shù)據(jù)庫為中心的重點(diǎn),是欽定的或唯一正確的方式的一部分。它使你更加迷戀微軟。廠商鎖定始終對廠商來說是好的。
為什么我們要如此開發(fā)?為什么我們不更多地考慮應(yīng)用程序的行為而不是它如何存儲的?現(xiàn)在我所有的項(xiàng)目都使用基于JSON的鍵/值存儲。有了這種功能,我可以選擇任何我想要的存儲引擎,包括SQL Server,Oracle,PostgreSQL,MySQL,Cassandra, CouchDB, CouchBase, Dynamo, SimpleDB, S3, Riak, BerkeleyDB, Firebird, Hypertable, RavenDB, Redis, Tokyo Cabinet/Tyrant, Azure Blobs,文件系統(tǒng)中的明文JSON文件等等等等。突然之間,我們能夠開始根據(jù)其優(yōu)點(diǎn)而不是僅僅對其熟悉來選擇存儲引擎了。
題外話:在AWS RDS的云上運(yùn)行過SQL Server嗎?別這么做。當(dāng)然它會工作,但是一些例如復(fù)制這樣最簡單的事是不存在的。文章充斥著對SQL Server不能在AWS RDS上工作的引用。
結(jié)論
也許我在軟件開發(fā)中學(xué)到的兩件最重要的教訓(xùn)是:
- 邊界和封裝的重要性(以多種形式)
- 付出代價(jià)以得到正確的模型和抽象
許多年前我恨“模型”這個詞。每個人都會把它到處扔,它是一個如此過載的術(shù)語,很難理解它的含義和它為什么這么重要。就這點(diǎn)來說,我僅僅會說模型是對你想要封裝的現(xiàn)實(shí)的一個有限的表示。也許最簡單的例子就是地球儀的墨卡托投影了。這很確切得說明了一件事:導(dǎo)航。如果你在其他的事情上使用它,它并不毫無價(jià)值。如果你不專注于付出代價(jià)去使模型正確,去封裝商業(yè)現(xiàn)實(shí),那么沒有任何技術(shù)能夠拯救你。
我對.NET最大的抱怨是,“唯一正確的方式”引導(dǎo)你遠(yuǎn)離理想的模型并把你推向關(guān)注實(shí)現(xiàn)細(xì)節(jié)和技術(shù)缺陷的方向。這樣的關(guān)注導(dǎo)致技術(shù)實(shí)施滲血并且感染模型,最終導(dǎo)致它腐爛變質(zhì),因?yàn)樗荒苓m應(yīng)不斷變化的商業(yè)需求。當(dāng)這發(fā)生的時候,開發(fā)者掙扎著并蹬踢著,如同吸毒者一樣,他們從一個新技術(shù)轉(zhuǎn)向另一個,以期望下一個強(qiáng)大的技術(shù)能夠治愈他們的病痛。
技術(shù)本身并不是靈丹妙藥,相反地,它是關(guān)于取舍和選擇。只有正確地理解了商業(yè)行為并把它們封裝進(jìn)結(jié)構(gòu)良好的,易于理解的模型中,以幫助保持技術(shù)堆棧在屬于它的地方—作為一個實(shí)現(xiàn)細(xì)節(jié)。
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 框架,因?yàn)樗粩嗟刂厣曜约?的主張),不斷地想要比它的本身更多的:一個實(shí)現(xiàn)細(xì)節(jié)。
原文鏈接: Jonathan Oliver 翻譯: 伯樂在線 - EluQ