發(fā)布于:2020-12-30 14:36:13
0
148
0
在過去的幾年中,我與很多初學(xué)者一起工作并指導(dǎo)他們。雖然我顯然見證了我在編程方面的不二之選,但是事情并沒有看起來那么黑白。我在整個(gè)初學(xué)者中都一貫看到了幾種模式和行為。盡管其中一些行為被誤導(dǎo)和有害,但許多行為為高級(jí)開發(fā)人員提供了學(xué)習(xí)機(jī)會(huì)。與普遍的看法相反,有很多經(jīng)驗(yàn)教訓(xùn)的人可以從經(jīng)驗(yàn)較少的人那里學(xué)到,因?yàn)樗麄兊挠^點(diǎn)是無偏見的(初學(xué)者的想法)。
條件反轉(zhuǎn)
我與初學(xué)者見過的最常見的反模式之一是我喜歡稱之為“條件反轉(zhuǎn)”的東西(它可能已經(jīng)以更好的名字存在了)。使用分支語(yǔ)句控制或限制代碼流時(shí),可能會(huì)發(fā)生條件反轉(zhuǎn)。以下是反碼的示例:
function atleastOneOdd(potentialOdds) { if (potentialOdds !== undefined) { if (potentialOdds.length) { return potentialOdds.some((num) => num & 1); } } return false; }
如果您認(rèn)為上述代碼對(duì)于完成這樣一個(gè)簡(jiǎn)單的任務(wù)而言過于復(fù)雜,那么您并不孤單。感覺到的復(fù)雜性是嵌套的結(jié)果,因?yàn)榍短讟O大地促進(jìn)了代碼的“感知到的復(fù)雜性”。這是功能上等效但可讀性更高的實(shí)現(xiàn):
function atLeastOneOdd(potentialOdds) { if (potentialOdds === undefined) { return false; } if (!potentialOdds.length) { return false; } return potentialOdds.some((num) => num & 1); }
通常,更少的嵌套使代碼更易于閱讀和維護(hù)。與所有規(guī)則一樣,總是有例外,因此請(qǐng)確保在做出決定之前先了解上下文。
知道什么時(shí)候看
重要的是要記住,不僅僅是初學(xué)者知道“更少”,還在于他們還沒有對(duì)應(yīng)該期望的東西產(chǎn)生直覺。經(jīng)過一段時(shí)間的編碼(不是字面意思),您對(duì)標(biāo)準(zhǔn)庫(kù)和軟件包中期望的邏輯粒度有了敏銳的認(rèn)識(shí)。一個(gè)很好的例子就是我女友的情況。我的女友是計(jì)算機(jī)科學(xué)專業(yè)的學(xué)生,最近我就她為C ++作業(yè)編寫的代碼提供了一些反饋。作為作業(yè)的一部分,她需要檢查給定的輸入內(nèi)容char 是否為字母。這大致是她的實(shí)現(xiàn):
bool isLetter(const char someChar) { const char letters [] = { ‘a(chǎn)’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’ }; int i = 0; for (i = 0; i < 26; ++i) { if (letters[i] === someChar) { return true; } } return false; }
我甚至不生氣。這是一種直接而邏輯的實(shí)現(xiàn)。但是作為一名經(jīng)驗(yàn)豐富的開發(fā)人員,本能告訴您,必須解決這一普遍存在的問題。實(shí)際上,我的直覺是利用c字符的數(shù)字性質(zhì),然后簡(jiǎn)單地檢查輸入字符是否在特定的ASCII az范圍內(nèi)。但是由于我的“編程直覺”,我懷疑對(duì)于這樣一個(gè)普遍的問題,這仍然有很多工作要做。我告訴她Google是否可以檢查char是否是字母。原來是isalpha-我可能已經(jīng)使用了一百次,但是卻不記得(在足夠的編程中會(huì)發(fā)生的事情-您無法記住所有事情)。
對(duì)是否已經(jīng)存在可以解決您的問題的事物形成直覺,對(duì)于成功作為開發(fā)人員至關(guān)重要。不論好與壞,增強(qiáng)直覺的唯一方法是用多種語(yǔ)言和范例編寫更多的代碼。
尋找最直接的解決方案
初學(xué)者在找到問題的直接或字面解決方案時(shí)絕對(duì)令人贊嘆。僅以上isLetter 一節(jié)中的示例為例。雖然我女友的解決方案絕對(duì)不是最有效的解決方案(內(nèi)存或計(jì)算明智的解決方案),但它非常干凈,可以完成工作。盡管傾向于使用最強(qiáng)力解決方案的代價(jià)可能很高,但在大多數(shù)情況下,這實(shí)際上并不重要。我并不是說您應(yīng)該把性能拋在腦后,而只能依靠蠻力解決方案,但是這里有個(gè)教訓(xùn)需要學(xué)習(xí)。要了解這一課,重要的是要了解初學(xué)者和專家之間的主要區(qū)別之一:他們想要達(dá)到的目標(biāo)。
初學(xué)者通常是通過編程來學(xué)習(xí)如何編程,而經(jīng)驗(yàn)豐富的開發(fā)人員通常是通過編程來達(dá)到最終目的(工作,個(gè)人項(xiàng)目等)。在大學(xué)里,計(jì)算機(jī)科學(xué)老師很少給出有績(jī)效要求的作業(yè)。這導(dǎo)致學(xué)生在學(xué)習(xí)編程時(shí)沒有考慮到性能。剛開始時(shí),這似乎在我們教人們編程的方式上有很大的不足,但是我實(shí)際上認(rèn)為這是高等教育正確完成的少數(shù)事情之一。擔(dān)心性能通常是一種非常致命的疾病的癥狀,這種疾病被稱為“過早優(yōu)化”。根據(jù)我的觀察,一旦程序員開始擔(dān)心性能,他們就很難停止。
如果針對(duì)性能進(jìn)行的優(yōu)化不需要花費(fèi)額外的時(shí)間,但是這確實(shí)不是問題。結(jié)合性能幾乎無關(guān)緊要的事實(shí)(這是來自低級(jí)C / x86程序員)的事實(shí),很清楚為什么總是針對(duì)性能進(jìn)行優(yōu)化是有問題的。這種效果是如此強(qiáng)大,以至于我實(shí)際上已經(jīng)看到初學(xué)者比一個(gè)有能力和經(jīng)驗(yàn)豐富的開發(fā)人員能夠更快地找到一個(gè)可行的解決方案,這僅僅是因?yàn)槌鯇W(xué)者只關(guān)心使其能夠正常工作,而高級(jí)開發(fā)人員則認(rèn)為它必須表現(xiàn)良好。具有諷刺意味的是,即使大四學(xué)生完成了“表演者”解決方案,您也無法分辨他們與初學(xué)者之間的區(qū)別。
一切都必須是一堂課
我一直在初學(xué)者中看到的更令人沮喪的行為之一是無法在類范式之外進(jìn)行編碼。我將此歸咎于每所只向?qū)W生介紹計(jì)算機(jī)科學(xué)程序Java的學(xué)院。我通常與語(yǔ)言無關(guān),但是我總是發(fā)現(xiàn)“一切都必須是一個(gè)類”的要求是Java特別愚蠢的方面。我們明白了,Sun的某個(gè)人真的很喜歡OOP。這種迷戀的代價(jià)是20年后,Java仍在嘗試將其自身重新組裝成人們真正想要使用的語(yǔ)言。
要理解為什么這是一個(gè)實(shí)際問題,而不僅僅是對(duì)Java的抱怨,我們必須考慮初學(xué)者如何學(xué)習(xí)。初學(xué)者傾向于以呈現(xiàn)的粒度將概念內(nèi)在化。因此,當(dāng)他們使用諸如Java之類的語(yǔ)言來強(qiáng)制執(zhí)行類范式時(shí),他們傾向于理解最小的代碼單元是一個(gè)類,并且期望每個(gè)變量都像一個(gè)對(duì)象。使用另一種面向?qū)ο蟮恼Z(yǔ)言(例如C ++)將很快證明,最小的代碼單元不必是一個(gè)類-為原始和無狀態(tài)的邏輯編寫整個(gè)類通常過于費(fèi)力且麻煩。別誤會(huì),在現(xiàn)實(shí)生活中有很多情況需要這些語(yǔ)義和保證,實(shí)際上,這就是Java是最普遍使用的語(yǔ)言之一的原因。
問題在于,這些語(yǔ)義為初學(xué)者提供了誤導(dǎo)性的描述??s小Java世界時(shí),您會(huì)注意到很少有編程語(yǔ)言是面向?qū)ο蟮?。這就是為什么僅使用一種語(yǔ)言學(xué)習(xí)編程如此危險(xiǎn)的原因。您不會(huì)最終學(xué)習(xí)編程,而是最終學(xué)習(xí)語(yǔ)言。
當(dāng)我與其他開發(fā)人員討論了這種信念時(shí),他們經(jīng)常爭(zhēng)辯說這些弊端實(shí)際上是弊端,因?yàn)镴ava避免讓初學(xué)者冒充自己。對(duì)于那些需要雇用許多不受信任的程序員的《財(cái)富》 100強(qiáng)公司來說,這可能是一個(gè)很好的理由,但我們不應(yīng)該向這樣的初學(xué)者介紹編程。
強(qiáng)大的語(yǔ)法和結(jié)構(gòu)
初學(xué)者往往比經(jīng)驗(yàn)豐富的開發(fā)人員更依賴一致的語(yǔ)法和結(jié)構(gòu)??紤]到人類是模式驅(qū)動(dòng)的,并且代碼語(yǔ)法和語(yǔ)義是模式最明確的示例之一,因此,一旦想到這一點(diǎn),這實(shí)際上很有意義。對(duì)于有經(jīng)驗(yàn)的開發(fā)人員,了解函數(shù)的功能(及其操作方式)通常是去定義其功能并閱讀源代碼。似乎無法想象不以這種方式運(yùn)行,但是我?guī)缀醣WC即使是最好的開發(fā)人員也不會(huì)以這種方式開始。模式匹配對(duì)人類的學(xué)習(xí)方式至關(guān)重要,以至于它遠(yuǎn)遠(yuǎn)超出了編程的范圍。例如,眾所周知,英語(yǔ)是最難學(xué)習(xí)的語(yǔ)言之一。
當(dāng)我最近在幫助一個(gè)初學(xué)者編寫一個(gè)內(nèi)部狀態(tài)相對(duì)復(fù)雜的C ++類時(shí),就發(fā)生了一個(gè)實(shí)際的編程示例。根據(jù)類的目標(biāo)/用途,使某些方法直接改變狀態(tài)而其他方法返回副本是有意義的。在一起編寫了很多方法之后,我離開了初學(xué)者自己編寫其余方法。當(dāng)我稍后返回時(shí),我的學(xué)生沒有取得太大進(jìn)步,并告知他們被卡住了。具體來說,他們對(duì)于哪些方法正在改變狀態(tài)而哪些方法沒有使?fàn)顟B(tài)感到困惑。在這種情況下,有經(jīng)驗(yàn)的開發(fā)人員不會(huì)遇到這種情況,因?yàn)樗麄冎粫?huì)查看方法的內(nèi)部邏輯并確定狀態(tài)是否正在發(fā)生突變。但是,初學(xué)者還不夠流利,無法快速解析相同的邏輯(即使他們以前曾編寫過),而是依靠代碼的語(yǔ)法和結(jié)構(gòu)。審查代碼后,我意識(shí)到他們的掙扎部分是我的錯(cuò)。
當(dāng)我們一起實(shí)現(xiàn)初始方法時(shí),我確保可變性和不變性很好地結(jié)合在一起。我還沒有意識(shí)到,這些方法給初學(xué)者帶來了一種誤導(dǎo)性模式。具體來說,每個(gè)可變方法都是void函數(shù),每個(gè)不可變方法都具有返回類型。
class MyType { void addElem(int elem); MyType createCopy(); ... };
我無意間教了我的學(xué)生一種在實(shí)踐中顯然不正確的模式。因此,在他們需要實(shí)現(xiàn)可變bool removeElem(int elem) 或不變的那一刻void printElems(),事情就崩潰了。我說這是我的錯(cuò),因?yàn)樵谝惶旖Y(jié)束時(shí),我很懶。作為一個(gè)經(jīng)驗(yàn)豐富的開發(fā)人員,我對(duì)語(yǔ)法和結(jié)構(gòu)的依賴不如我實(shí)際實(shí)現(xiàn)的邏輯要多。這很愚蠢,因?yàn)榭梢愿倪M(jìn)我編寫的代碼,以使初學(xué)者保持零歧義,并以幾乎為零的成本消除潛在的錯(cuò)誤。我是如何做到的?通過使用const關(guān)鍵字,這使我可以明確指出方法是否有可能改變狀態(tài):
class MyType { void addElem(int elem); bool removeElem(int elem); void printElems() const; MyType createCopy() const; };
忘記他人如何解釋您編寫的代碼確實(shí)很容易。保持一致并利用語(yǔ)言功能,使您能夠編寫可消化的顯式代碼。
結(jié)論
當(dāng)您被介紹給更有效和可維護(hù)的解決方案時(shí),您所接受和采用的許多初始模式都會(huì)被淘汰。話雖如此,我希望本文能夠說明,有時(shí)即使是最有經(jīng)驗(yàn)的開發(fā)人員也可以從一點(diǎn)“學(xué)習(xí)”中受益。雖然初學(xué)者可能對(duì)高級(jí)功能和語(yǔ)言特質(zhì)不太滿意,但是有時(shí)候當(dāng)工作需要完成時(shí),這是天賦。
作者介紹
熱門博客推薦