【導(dǎo)讀】程序員在著手一個(gè)項(xiàng)目時(shí),需要做的關(guān)鍵決定之一就是選擇一種語(yǔ)言,或是一組語(yǔ)言,用于實(shí)施該系統(tǒng)。這一決定不僅會(huì)影響系統(tǒng)的實(shí)現(xiàn),也會(huì)影響設(shè)計(jì)。既然這個(gè)決定會(huì)導(dǎo)致深刻而長(zhǎng)遠(yuǎn)的結(jié)果,那么是不是在做這個(gè)抉擇時(shí)應(yīng)該更加務(wù)實(shí)?很多時(shí)候,我們會(huì)盲目地偏頗于我們選擇的語(yǔ)言。
例如,我們應(yīng)該使用面向?qū)ο蟮恼Z(yǔ)言還是過(guò)程語(yǔ)言?選擇什么語(yǔ)言對(duì)項(xiàng)目以及作為項(xiàng)目一部分的程序的生命周期有著深遠(yuǎn)的影響,很多次,我們基于一些非常善變的因素,沒(méi)有思考太多 就去選語(yǔ)言:這語(yǔ)言是我慣常用來(lái)實(shí)現(xiàn)這類(lèi)系統(tǒng)的;這語(yǔ)言我了解得最透徹;這是我最喜歡的語(yǔ)言,我很享受于用這種語(yǔ)言編程;等等。
既然這個(gè)決定會(huì)導(dǎo)致深刻而長(zhǎng)遠(yuǎn)的結(jié)果,那么我們是不是在做這個(gè)抉擇時(shí)應(yīng)該更加務(wù)實(shí)?很多時(shí)候,我們會(huì)盲目地偏頗于我們選擇的語(yǔ)言。而且,有時(shí)候我們之所以不喜歡選擇這種語(yǔ)言的原因可能正是為什么我們要選擇那種語(yǔ)言的原因。
如果我們能夠放開(kāi)胸懷,坦誠(chéng)地對(duì)待自己持有的偏見(jiàn),那么我們就可以減輕一些類(lèi)似在裝修時(shí)硬要將方釘釘進(jìn)圓形孔的痛苦。雖然我們沒(méi)有什么秘訣來(lái)為項(xiàng)目選擇完美語(yǔ)言,但還是可以遵循一些原則,幫助我們做出一個(gè)更好,更合適的語(yǔ)言選擇。
沒(méi)有完美的語(yǔ)言
這一點(diǎn)對(duì)任何人,甚至是新手而言,都是在意料之中的,并且我們很多人都愿意承認(rèn),“當(dāng)然,這種語(yǔ)言并不是完美的語(yǔ)言,”但與此同時(shí),我們很多人還是 會(huì)說(shuō),“這語(yǔ)言是最好的編程語(yǔ)言”。說(shuō)一種語(yǔ)言是項(xiàng)目的最好語(yǔ)言的關(guān)鍵是項(xiàng)目的背景,也就是說(shuō),最好的語(yǔ)言只存在于一定的范圍內(nèi)。這就是我們的第一條原則:
沒(méi)有完美的語(yǔ)言:每一種語(yǔ)言都有它的優(yōu)點(diǎn)和缺點(diǎn)。
例如,許多通常使用運(yùn)行時(shí)語(yǔ)言,如Java或Python的開(kāi)發(fā)人員,聲稱(chēng)C或C ++令人透不過(guò)氣來(lái),會(huì)因?yàn)殛P(guān)注例如內(nèi)存管理這類(lèi)低層次的細(xì)節(jié),或關(guān)心編譯時(shí)類(lèi)型檢查的嚴(yán)格粒度,而扼殺分置于開(kāi)發(fā)人員的職責(zé)。這是事實(shí),只要我們正在開(kāi)發(fā)的項(xiàng)目不關(guān)注看似瑣碎的任務(wù),如內(nèi)存管理或發(fā)生在單一循環(huán)中的copy-assignment的數(shù)量。
相反,如果我們工作在一個(gè)項(xiàng)目,或項(xiàng)目的一部分,那么對(duì)于代碼應(yīng)該如何高效以及程序的關(guān)鍵性安全的偏見(jiàn)需求是自然而然的,這些看似繁瑣的細(xì)節(jié)可能正是我們正在尋找的粒度水平。在這種新的背景下,Java或Python的運(yùn)行時(shí)性質(zhì)似乎過(guò)于漠不關(guān)心或過(guò)于心不在焉。相反,我們希望當(dāng)內(nèi)存分配和釋放的時(shí) 候,能夠嚴(yán)格控制有多少move-assignment和copy-assignment被執(zhí)行,并在編譯時(shí)捕捉盡可能多的錯(cuò)誤,而不是讓錯(cuò)誤滲入運(yùn)行時(shí) (表現(xiàn)為運(yùn)行時(shí)異常)。
雖然在理論上“沒(méi)有完美的語(yǔ)言”這一點(diǎn)聽(tīng)起來(lái)是顯而易見(jiàn)的,但是我們作為開(kāi)發(fā)人員的行為通常會(huì)背離這個(gè)概念:我們說(shuō)我們知道我們最喜歡的語(yǔ)言是不完美的,但我們還是繼續(xù)對(duì)我們開(kāi)發(fā)的項(xiàng)目使用這種語(yǔ)言,不管它是否適合。此外,當(dāng)其他的開(kāi)發(fā)人員質(zhì)疑我們選擇的語(yǔ)言時(shí),我們會(huì)堅(jiān)決捍衛(wèi)我們的選擇,而不愿意從他或她的反駁中看見(jiàn)事實(shí)的真相。請(qǐng)記?。好恳环N語(yǔ)言都有它的優(yōu)點(diǎn)和缺點(diǎn)。了解你掌握的語(yǔ)言的優(yōu)點(diǎn)和缺點(diǎn),然后根據(jù)實(shí)際情況做出選擇。
你不喜歡一種語(yǔ)言的原因可能就是你應(yīng)該使用它的原因
這似乎違反直覺(jué),但有的時(shí)候,我們之所以不喜歡一門(mén)語(yǔ)言可能正是使用某種語(yǔ)言的原因。還是上面的例子,在我作為一個(gè)C ++開(kāi)發(fā)人員的經(jīng)驗(yàn)中,很多時(shí)候因?yàn)橛心敲炊嗖煌母拍钜櫍▋?nèi)存管理和對(duì)象壽命時(shí)間,C ++編程三原則等),以致于完成項(xiàng)目的一個(gè)簡(jiǎn)單功能都會(huì)變得繁瑣不堪。在用C ++開(kāi)發(fā)幾周之后,使用Python,Java或另一種“更高級(jí)”的語(yǔ)言,簡(jiǎn)直就像上天的恩賜:但真的是這樣的嗎?
有時(shí)候,可能我們不喜歡一門(mén)語(yǔ)言的原因正是我們要使用該語(yǔ)言的原因。如果我正在開(kāi)發(fā)一個(gè)驅(qū)動(dòng)程序或一些關(guān)鍵性安全,實(shí)時(shí)的系統(tǒng),上面表述的繁瑣不堪的原因可能正是這個(gè)語(yǔ)言的最大優(yōu)勢(shì)。例如,C ++提供了一種機(jī)制用于表達(dá)當(dāng)對(duì)象被復(fù)制時(shí)被執(zhí)行的邏輯,這在效率和嚴(yán)謹(jǐn)性井然有序的時(shí)候是非常寶貴的。
這可能看上去都很好都很棒,因此我們很難確切指出在某個(gè)背景下,某種你看不順眼的語(yǔ)言可能反而更有幫助。那么,我們?cè)撛趺粗滥男┠悴幌矚g的語(yǔ)言是有幫助的呢?這就引出了我們的第二條原則:
對(duì)自己要誠(chéng)實(shí):知道自己為什么不喜歡一門(mén)語(yǔ)言,不要教條化自己的憎惡。
例如,在上面那個(gè)C ++的例子中,我之所以不喜歡長(zhǎng)時(shí)間地用C ++編程,是因?yàn)檫@語(yǔ)言要求思想嚴(yán)謹(jǐn),否則很容易犯錯(cuò),就像是被困于叢林中(過(guò)多地關(guān)注樹(shù)木,而不是樹(shù)林這個(gè)整體)。這種嚴(yán)謹(jǐn)會(huì)妨礙開(kāi)發(fā)人員去質(zhì)疑,如“我要在堆棧上或堆上創(chuàng)建對(duì)象嗎,或者部分在堆棧上,另一部分在堆上?”或“要讓這個(gè)類(lèi)可擴(kuò)展,應(yīng)該通過(guò)模板參數(shù)還是通過(guò)繼承?”等決定。在其他語(yǔ)言中, 開(kāi)發(fā)人員只需分別創(chuàng)建一個(gè)對(duì)象以及使用面向?qū)ο蟮睦^承就可以完成這些任務(wù),然后進(jìn)入到下一個(gè)功能,因?yàn)檎Z(yǔ)言(或者,更準(zhǔn)確地說(shuō),編譯器或解釋器)關(guān)注這些細(xì)節(jié)。
但是,如果我對(duì)自己誠(chéng)實(shí)的話(huà),我會(huì)承認(rèn),我之所以不喜歡C ++的這些功能,是因?yàn)樗鼘⒈磉_(dá)這些細(xì)節(jié)的責(zé)任歸咎于我。在其他語(yǔ)言中,我不僅不需要負(fù)責(zé)這些細(xì)節(jié),而且我也沒(méi)有責(zé)任表達(dá)這些細(xì)節(jié):它們被抽象遠(yuǎn)離開(kāi)發(fā)人員。在一個(gè)這些細(xì)節(jié)是必不可少的上下文中,我不喜歡C ++的原因正是我應(yīng)該使用這種語(yǔ)言的原因。
這是否意味著,我們應(yīng)該愁眉苦臉地使用這些會(huì)讓我們對(duì)這語(yǔ)言惱怒的功能?也沒(méi)有必要?;蛟S你可以換個(gè)角度:不要將這些功能當(dāng)作缺點(diǎn),也許我們應(yīng)該擁抱它們,將它們當(dāng)作完成任務(wù)的必需品。我們不應(yīng)該說(shuō)“這真是一個(gè)悲劇,”而應(yīng)該說(shuō),“謝天謝地,我居然能用這種語(yǔ)言做到這一點(diǎn)。”請(qǐng)記?。涸谀承┍尘跋拢@些功能將是上天的恩賜,而在其他情況下,它們才是累贅。至于為什么不喜歡某一門(mén)語(yǔ)言的功能,請(qǐng)誠(chéng)實(shí)地告訴自己。
越熟悉其他語(yǔ)言,越好
對(duì)于這一點(diǎn),就是我們要說(shuō)的第三個(gè)原則:
如果你擁有的唯一工具是一個(gè)錘子,那么你看每一個(gè)問(wèn)題都像是釘子。
這條規(guī)則并不適用于軟件工程,但它尖銳地表現(xiàn)了許多軟件開(kāi)發(fā)的情況。很多時(shí)候,我們選擇一種語(yǔ)言,或一種語(yǔ)言支持的工具(如Java的JMS,Python的ASYNCIO,Rails的Ruby等),是因?yàn)槲覀冎浪鼈兇嬖?。如果我們唯一熟悉的語(yǔ)言是Java,那么我們會(huì)將我們碰到的 所有問(wèn)題都適應(yīng)到Java的上下文中。例如,“我需要為一個(gè)通信應(yīng)用創(chuàng)建一個(gè)路由框架。在Java中我該怎么做呢?”這就限制了可供我們使用的工具,并人為地限制我們?yōu)橥瓿晒ぷ鬟x擇合適工具的余地。
解決這個(gè)問(wèn)題的方法是擴(kuò)大你的視野,了解其他語(yǔ)言的的功能和錯(cuò)綜復(fù)雜之處。正如Andrew Hunt和David Thomas在《The Pragmatic Programmer》中給出的建議,一個(gè)好的做法就是,每年學(xué)習(xí)一門(mén)新的語(yǔ)言。這可不沒(méi)有聽(tīng)上去那么容易,學(xué)習(xí)一門(mén)語(yǔ)言對(duì)不同的人將意味著不同的事情。 還有一個(gè)衍生問(wèn)題是,我們對(duì)正在進(jìn)行中的項(xiàng)目往往只會(huì)使用這一種語(yǔ)言,從而使得學(xué)習(xí)的另一種語(yǔ)言顯得毫無(wú)用處。例如,假設(shè)我是一個(gè)Android開(kāi)發(fā)人 員,基本上每天只用Java,那么學(xué)習(xí)C??赡芫蜁?huì)顯得不合時(shí)宜地浪費(fèi)時(shí)間。
不要被假象所蒙蔽。學(xué)習(xí)其他語(yǔ)言的優(yōu)勢(shì)體現(xiàn)在我們能從不同的角度去看問(wèn)題,并且使用最適合該問(wèn)題的工具。為了做到這一點(diǎn),我們必須學(xué)習(xí)其他語(yǔ)言的相關(guān)警告,以及開(kāi)發(fā)人員使用這些語(yǔ)言解決問(wèn)題的方式。例如,如果一個(gè)開(kāi)發(fā)人員想用C ++執(zhí)行元編程,那么他或她可以使用C ++中的Template Metaprogrammming(TMP),但他或她也可以使用Java中的反射。理解其他語(yǔ)言是如何解決類(lèi)似問(wèn)題的,可以減少我們認(rèn)為它毫無(wú)用處的風(fēng) 險(xiǎn)。
再說(shuō)一個(gè)例子,如果我們需要能夠改變一個(gè)類(lèi)的運(yùn)行時(shí)特征,那么一個(gè)深入熟悉C ++錯(cuò)綜復(fù)雜性的C ++開(kāi)發(fā)人員,可能會(huì)試圖編造一個(gè)延伸這個(gè)編譯時(shí)語(yǔ)言的界限的解決方案。而另一個(gè)C ++開(kāi)發(fā)人員,由于對(duì)Java也有一定的了解,就能夠說(shuō),“我喜歡C ++,但Java的運(yùn)行時(shí)反射更適合解決這個(gè)問(wèn)題。”
因?yàn)橛腥绱酥嗟木幊陶Z(yǔ)言任開(kāi)發(fā)人員擇選,因此,優(yōu)先安排學(xué)習(xí)什么語(yǔ)言很重要。不妨從當(dāng)今最流行的語(yǔ)言入手(可參考《most popular languages on Github》,《Language Trends on Github》,《The 9 most popular computer languages》,《according to the Facebook for programmers》等)。
語(yǔ)言是手段而不是目的
這是第四條,也是最后一條原則,聽(tīng)上去可能最哲學(xué),但也可以說(shuō)是最重要的:
編程語(yǔ)言是一種手段,而不是目的。
除非你是一個(gè)語(yǔ)言標(biāo)準(zhǔn)的作者或是一個(gè)編譯器的作者,否則你就應(yīng)該將編程語(yǔ)言當(dāng)作是一種手段而不是目的,目的是完成項(xiàng)目:最終的目標(biāo)是要完成項(xiàng)目,而不是使用特定的語(yǔ)言。這并不意味著每個(gè)開(kāi)發(fā)人員就無(wú)權(quán)要求他或她喜歡或不喜歡的語(yǔ)言(實(shí)際上,如果我們對(duì)自己誠(chéng)實(shí)的話(huà),這些好惡反而能夠讓我們受惠;參見(jiàn) 上面的第二條原則),但我們不應(yīng)該自欺欺人作出這樣的決定,如,“這對(duì)我來(lái)說(shuō)是使用該語(yǔ)言這一功能的一個(gè)很好的機(jī)會(huì)”,除非該語(yǔ)言的功能真正適合項(xiàng)目的需求。
重要的是要記住,語(yǔ)言只是表達(dá)如何解決手頭問(wèn)題的一種方法:請(qǐng)確保你選擇了最能表達(dá)解決問(wèn)題域的語(yǔ)言。
其他需要考慮的地方
下面是一些我們?cè)谶x擇語(yǔ)言的時(shí)候,需要補(bǔ)充考慮的地方:
考慮語(yǔ)言如何與其他語(yǔ)言的交互。例如,如果你認(rèn)定Python是完成大部分項(xiàng)目的最好語(yǔ)言,但在你的項(xiàng)目中有一個(gè)定義良好的組件,需要極高水平的粒度或效率(更適合用C或C++ ),這并不意味著你不能在這個(gè)項(xiàng)目上使用Python。相反,考慮使用Python,特定組件用C或C ++寫(xiě),然后使用Python C API接口此組件。請(qǐng)注意,要制定這樣的解決方案,我們需要知道Python有一個(gè)C API;因此,了解最流行語(yǔ)言的這些功能是很有幫助的。
中間件可以允許使用多種語(yǔ)言。例如,如果有兩個(gè)必須進(jìn)行通信的應(yīng)用程序,如移動(dòng)設(shè)備和一個(gè)服務(wù)器應(yīng)用程序,但這并不意味著它們必須使用相同的語(yǔ)言(當(dāng)然也可以相同,如果你判斷認(rèn)為這是最好決定的話(huà))。如果這個(gè)移動(dòng)設(shè)備是一款A(yù)ndroid手機(jī),而服務(wù)器應(yīng)用程序非 常適合作為一個(gè)Python應(yīng)用程序的話(huà),那么使用一個(gè)消息代理,如RabbitMQ,可以讓你在通信的同時(shí)使用這兩種語(yǔ)言:Android應(yīng)用程序可以 使用Java RabbitMQ API,而服務(wù)器應(yīng)用程序可以使用Python RabbitMQ API。