日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

代码之美——Doom3源代码赏析

發(fā)布時(shí)間:2023/12/13 编程问答 66 豆豆
生活随笔 收集整理的這篇文章主要介紹了 代码之美——Doom3源代码赏析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
摘要:Dyad作者、資深C++工程師Shawn McGrathz在空閑時(shí)翻看了Doom3的源代碼,發(fā)出了這樣的驚嘆:“這是我見(jiàn)過(guò)的最整潔、最優(yōu)美的代碼!”“Doom 3的源代碼讓我對(duì)那些優(yōu)秀的程序員刮目相看。”因此有了本文。

背景介紹:

Doom3是id Software于2004年開(kāi)發(fā)的第一人稱(chēng)射擊游戲,目前以GPL v3協(xié)議開(kāi)源。其采用游戲引擎的是id Tech 4,由id Software創(chuàng)始人、首席程序員John Carmack領(lǐng)導(dǎo)開(kāi)發(fā)。

再做個(gè)簡(jiǎn)單的對(duì)比:作者剛剛完成的Dyad有193k行純C++代碼,Doom3是601k(2004),Quake3是229k(1999),Quake2是136k(1997)。

以下是CSDN譯文,做了部分刪減:

關(guān)于代碼,什么才能被稱(chēng)為“好看”——或者說(shuō)“優(yōu)美”?在和幾個(gè)程序員朋友討論后,我得出了結(jié)論:

  • 代碼應(yīng)該局部連貫而且功能單一:一個(gè)函數(shù)解決一個(gè)問(wèn)題。而且應(yīng)該很清晰。
  • 局部代碼應(yīng)該能夠解釋,至少暗示整體的系統(tǒng)設(shè)計(jì)。
  • 代碼應(yīng)該“自文檔”,盡可能地避免注釋。因?yàn)闊o(wú)論是在讀還是寫(xiě)代碼時(shí),注釋都是一項(xiàng)冗余工作。如果你需要添加注釋才能幫別人理解,那么那段代碼可能需要重寫(xiě)。

這里是idTech4引擎的編碼標(biāo)準(zhǔn),絕對(duì)值得一讀。

統(tǒng)一的語(yǔ)法與詞法分析


我在Doom源代碼中所見(jiàn)最聰明之處在于其詞法分析器和解釋器。所有的資源文件都是語(yǔ)法統(tǒng)一的ASCII文件:腳本、動(dòng)畫(huà)文件、配置文件,等等,所有東西都遵循相同的規(guī)則。因此一大塊代碼就可以閱讀并處理所有的文件。這個(gè)解析器非常健壯,支持一個(gè)C++的主要子集。通過(guò)一個(gè)統(tǒng)一的詞法分析、解釋器,引擎所有組件都不必?fù)?dān)心序列化數(shù)據(jù)的問(wèn)題,因?yàn)橐呀?jīng)準(zhǔn)備好了相應(yīng)的代碼,這保證其它地方的代碼更加整潔。

參數(shù)嚴(yán)格和const化


Doom的代碼非常嚴(yán)格,盡管在我看來(lái),const方面還不夠嚴(yán)格。可能很多程序員都沒(méi)注意到const的多種種作用。我的看法是“任何東西只要可以都應(yīng)該設(shè)定為const”,我希望C++中所有的變量都默認(rèn)是const。Doom參數(shù)幾乎完全遵守“no in-out”規(guī)則,這意味著所有函數(shù)都參數(shù)都不能既是輸入?yún)?shù)也是輸出參數(shù)。這樣,在當(dāng)你向函數(shù)傳入?yún)?shù)時(shí),更容易理解他身上發(fā)生了什么。比如:

從這幾個(gè)const中我就看出來(lái):

  • 這個(gè)函數(shù)不會(huì)修改作為參數(shù)傳入的idPlane。我無(wú)需堅(jiān)持idPlane是否被修改就可以安全地使用它。
  • 函數(shù)中的epsilon也不會(huì)被修改。
  • front, back, frontOnPlaneEdges and backOnPlaceEdges是輸出變量,是值的寫(xiě)入目標(biāo)。
  • 參數(shù)列表后面的const是我最贊賞的地方。它表明idSurface::Split()不會(huì)去修改surface。這是我最喜歡的C++獨(dú)有功能,因?yàn)槲铱梢赃@樣使用:
  • void?f(const?idSurface?&s)?{?
  • s.Split(....);?
  • }?
  • 如果Split沒(méi)有被定義為 Split(...) const,這段代碼將無(wú)法編譯。無(wú)論被誰(shuí)所調(diào)用,f()都不會(huì)去修改外表,即使f()將surface傳遞給另一個(gè)函數(shù),或者調(diào)用一些Surface::method()。const能夠透露出很多關(guān)于函數(shù)甚至整個(gè)系統(tǒng)設(shè)計(jì)的信息,僅僅通過(guò)閱讀這里的函數(shù)聲明,我就明白了surface可以被plane動(dòng)態(tài)地split()。這個(gè)函數(shù)不會(huì)修改surface,而是返回新的surface、front、back數(shù)據(jù),可選地返回frontOnPlaneEdges和backOnPlaneEdges。

    const規(guī)則,以及無(wú)input/output參數(shù)對(duì)我來(lái)說(shuō)也許是最重要的原則,也是區(qū)分好的代碼跟優(yōu)美代碼的關(guān)鍵,它能簡(jiǎn)化整個(gè)系統(tǒng)的理解、編輯和重構(gòu)。

    最少注釋原則


    這是一個(gè)“格式問(wèn)題”,但Doom基本不會(huì)過(guò)度注釋,這很漂亮!我經(jīng)常會(huì)看到這樣的代碼:

    這太讓人惱火了,我通過(guò)名字就可以知道它的作用!如果這個(gè)函數(shù)名不能體現(xiàn)出其功能,毫無(wú)疑問(wèn)應(yīng)該重新命名;如果名字描述得過(guò)多,那么去簡(jiǎn)化它。除非實(shí)在不能通過(guò)重構(gòu)、重命名內(nèi)描述它唯一的功能,那么注釋才是合理的。我本以為程序員在學(xué)校已經(jīng)學(xué)會(huì)注釋的重要性,但實(shí)際上沒(méi)有。注釋很有必要,但它經(jīng)常沒(méi)必要。Doom在這方面做得非常合格,以idSurface::Split()為例,我們看看它是如何注釋的:

  • //?splits?the?surface?into?a?front?and?back?surface,?the?surface?itself?stays?unchanged?
  • //?frontOnPlaneEdges?and?backOnPlaneEdges?optionally?store?the?indexes?to?the?edges?that?lay?on?the?split?plane?
  • //?returns?a?SIDE_??
  • 第一行有點(diǎn)多余,從函數(shù)定義中我們已經(jīng)能明白所有的信息了;但第二、第三行很有價(jià)值,雖然我們已經(jīng)可以推斷出第二行的屬性,但注釋消除了歧義。

    Doom的代碼加上合理的注釋,閱讀非常方便。也許很多人把它歸為格式問(wèn)題,但我認(rèn)為,格式也有正確與否。如果有人修改了函數(shù),并且刪除了最后的const;這樣surface可以直接被函數(shù)修改,于是注釋與代碼不再同步;這樣注釋反過(guò)來(lái)會(huì)導(dǎo)致誤解,導(dǎo)致代碼更加難以閱讀。

    縱向空間


    Doom從不浪費(fèi)縱向空間。我們以t_stencilShadow::R_ChopWinding()為例:

    整個(gè)算法只占了我1/4個(gè)屏幕,剩下的3/4可以用來(lái)觀看其周?chē)南嚓P(guān)代碼塊。實(shí)際上,我經(jīng)常看到這樣的代碼:

    這可以歸為格式問(wèn)題,我有10年編程經(jīng)歷都是像后者那樣,大概在6年前才強(qiáng)行轉(zhuǎn)換為緊湊風(fēng)格的。

    兩者的代碼行數(shù)比是11:18,同樣的代碼后者行數(shù)幾乎是前者的兩倍,所以可能導(dǎo)致看不到后面的代碼塊,就像這樣:

    如果沒(méi)有前面的for循環(huán),僅僅上面這段代碼毫無(wú)意義,如果id沒(méi)有縱向緊湊的風(fēng)格,代碼可能更難閱讀、更難寫(xiě)、更難維護(hù)、也就遠(yuǎn)離了優(yōu)美代碼的定義。

    另外一個(gè)我認(rèn)同的格式是:id永遠(yuǎn)盡可能地使用{},沒(méi)有括號(hào)會(huì)很糟糕,比如我看過(guò)這段代碼:

    這非常丑陋,甚至比把{}放在同一行還要糟糕,我在id的代碼中從未發(fā)現(xiàn)省略{}的情況。省略{}會(huì)導(dǎo)致while代碼塊解析的時(shí)間大幅增加,而且編輯起來(lái)也非常痛苦:如果我希望往else if(c > d)分支中再插入一個(gè)if分支怎么辦?


    最少模板


    id“犯了不少C++的禁忌”,他們重寫(xiě)了所有需要的STD函數(shù)。我個(gè)人對(duì)STD愛(ài)恨交織。在Dyad,我調(diào)試構(gòu)建時(shí)常使用它來(lái)管理動(dòng)態(tài)資源;在發(fā)布時(shí)又會(huì)處理所有的資源,避免使用任何STL函數(shù),以求盡快地加載。STL很不錯(cuò),因?yàn)樗峁┝丝焖俚耐ㄓ脭?shù)據(jù)結(jié)構(gòu);它又很糟糕,因?yàn)槭褂盟?jīng)常導(dǎo)致代碼丑陋不堪,甚至容易出錯(cuò)。例如std::vector<T>類(lèi),如果我想迭代每一個(gè)元素:

    在C++11中要簡(jiǎn)單些:

    但我個(gè)人并不喜歡自動(dòng)化,雖然它簡(jiǎn)化了代碼編寫(xiě),卻導(dǎo)致代碼更難閱讀,最起碼我現(xiàn)在是這么認(rèn)為的。

    STD有的函數(shù)、算法甚至非常荒謬,比如要從std::vector中刪除一個(gè)值:

    你必須每次都能拼寫(xiě)正確!id除去了其中所以含糊不清的部分:他們使用自己的通用容器、字符串類(lèi)等等。他們編寫(xiě)的類(lèi)比起STL要更加專(zhuān)一,易于理解。id還盡可能地避免使用模板,而且使用自己定制的內(nèi)存分配器。STD代碼里則充斥著無(wú)意義的垃圾模板,而且不易于閱讀。

    C++代碼很難寫(xiě)好,所以你需要不斷地努力,不相信的話(huà)可以去看看Microsoft和GCC的STD代碼,這是我見(jiàn)過(guò)的最難看的代碼!

    id通過(guò)不濫用泛型就簡(jiǎn)單地解決了這個(gè)問(wèn)題。他們編寫(xiě)了HashTable<V>和HashIndex類(lèi),HashTable強(qiáng)制key類(lèi)型是const char *,而HashIndex是int->int對(duì)。這看起來(lái)像是很糟糕的C++實(shí)例。他們“本應(yīng)該”只有一個(gè)HashTable類(lèi),然后為編寫(xiě)局部特殊化:KeyType = const char *,然后專(zhuān)門(mén) <int, int>。

    當(dāng)然,id的做法完全正確,也保證了代碼的優(yōu)美。

    對(duì)比更鮮明的是,Hash生成“C++優(yōu)秀實(shí)踐”和id做法的比較:

    為特定類(lèi)型專(zhuān)門(mén)化:

    這樣你可以把ComputeHashForType當(dāng)作HashComputer傳給HashTable:

    這和我的做法很相近,看起來(lái)很聰明,但實(shí)際上很難看!因?yàn)?#xff0c;如果可選的模板參數(shù)很多怎么辦?

    這種情況下函數(shù)定義要更糟:

    如果沒(méi)有代碼高亮,我甚至不能區(qū)分出方法名!

    我也曾看到其它引擎試圖通過(guò)卸載模板參數(shù)規(guī)范到無(wú)數(shù)的typedef,這更糟糕!也許這利于理解,但卻導(dǎo)致了本地代碼和整個(gè)系統(tǒng)邏輯的斷層,所以缺乏美感。例如:

    以及:

    你這樣使用兩者:

    你會(huì)產(chǎn)生疑惑:StringHashTable內(nèi)存分配器——StringAllocator會(huì)涉及全局內(nèi)存嗎?這里導(dǎo)致了混淆,于是你又需要返回之前的代碼檢查(循環(huán))……

    Doom的做法和常規(guī)C++邏輯完全相反:它盡可能地避免泛型,除非有特別的意義。Doom的HashTable需要生成hash值時(shí)怎么辦?它只需要調(diào)用idStr::GetHash()。

    C語(yǔ)言的余韻


    雖然我不清楚id團(tuán)隊(duì)其他人的出身如何,但John Carmack基本上可以說(shuō)是開(kāi)發(fā)C應(yīng)用起家的,id在Quake III之前開(kāi)發(fā)游戲用的都是C語(yǔ)言。我見(jiàn)過(guò)很多沒(méi)有C開(kāi)發(fā)功底的C++程序員,編寫(xiě)代碼都有非常重的C++特色,上面過(guò)度使用模板的情況只是其中一例,其它還有:

    • 過(guò)度使用set/get方法
    • 使用字符串流
    • 過(guò)度使用操作符重載

    id在以上方面都做得非常完美。

    通常很多人會(huì)這樣創(chuàng)建一個(gè)類(lèi):

    這樣不僅浪費(fèi)行數(shù),還需要花費(fèi)更多的時(shí)間編來(lái)寫(xiě)和閱讀代碼。相比之下:

    如果你經(jīng)常為var自增某個(gè)數(shù)字n呢?

    相比于:

    上面的例子明顯容易閱讀和編寫(xiě)。

    id從不使用字符流,字符流通常包含糟糕的操作符重載:<<

    例如:

    雖然它有很多好處,但是很難看,而且語(yǔ)法也讓人討厭。

    id選擇printf()來(lái)代替,這樣也易于閱讀理解。我同意這樣的決定。

    另一方面,Doom還盡量避免操作符重載。雖然操作符重載是非常優(yōu)秀C++特性,但沒(méi)有操作符重載也就沒(méi)有歧義,更便于編寫(xiě)和閱讀。

    橫向空間


    這是我從Doom的代碼中最大的收獲,原來(lái)我是這樣編寫(xiě)代碼的:

    根據(jù)Doom3的編碼標(biāo)準(zhǔn),始終使用相對(duì)于4個(gè)空格的tab,水平對(duì)齊其中所有類(lèi)的定義:

    他們很少在類(lèi)的定義中嵌入內(nèi)聯(lián)函數(shù),我看到的唯一一次是代碼和函數(shù)聲明寫(xiě)在了同一行,這種做法有點(diǎn)不符合規(guī)范。這種類(lèi)定義的組織方式非常容易解析,不過(guò)需要更多的時(shí)間來(lái)編寫(xiě)。

    我討厭多余的代碼編寫(xiě),但這種情況下,我只需要這次稍微多做一點(diǎn)工作,其他程序員在之后接手時(shí)就可以省下很多功夫。相信這里的Doom3編程規(guī)范能夠幫助你理解其代碼之美。(有網(wǎng)友稱(chēng)Google的C++編程規(guī)范與其也有很多相似之處。)

    方法名


    我認(rèn)為Doom在方法名方面缺乏規(guī)范,我個(gè)人會(huì)盡可能地以動(dòng)詞開(kāi)頭命名方法:

    比這樣要好得多:

    以下是John Carmack本人的回復(fù):


    從某些角度來(lái)看,我認(rèn)為Quake3的代碼更加整潔,算是我C語(yǔ)言代碼的風(fēng)格的一次進(jìn)化,而非C++風(fēng)格的第一次迭代。當(dāng)然也可能因?yàn)榭偞a行數(shù)更少,或者是因?yàn)槲乙呀?jīng)10年沒(méi)看過(guò)它的代碼引起的錯(cuò)覺(jué)。我認(rèn)為“好的C++”在可讀性方面比“好的C語(yǔ)言”更好,其它方面大體相同。

    我開(kāi)始掌握C++是在Doom3開(kāi)發(fā)的時(shí)候——在這之前,我有豐富的C語(yǔ)言編程經(jīng)驗(yàn),因?yàn)镹eXT Objective-C編程的原因也有OOP(面向?qū)ο缶幊?#xff09;背景,因此在使用C++的時(shí)候并沒(méi)有對(duì)其使用和習(xí)慣進(jìn)行適當(dāng)針對(duì)性的研究。現(xiàn)在回想起來(lái),真希望提前看過(guò)Effective C++這樣的教程。團(tuán)隊(duì)里其他程序員雖然之前有C++編程經(jīng)驗(yàn),但基本上也是按照我選擇和設(shè)置的風(fēng)格在編程。

    很多年來(lái),我一直懷疑模板,一直在克制地使用它,不過(guò)最終確定自己更喜歡強(qiáng)類(lèi)型,而非充滿(mǎn)奇怪的代碼的頭文件。關(guān)于STL的爭(zhēng)論在id內(nèi)部一直沒(méi)有停息,顯得很有生氣。回想Doom3開(kāi)始開(kāi)發(fā)的時(shí)候,使用STL基本上算不得好主意,直到現(xiàn)在,即使是在游戲中我們也仍然在爭(zhēng)論這件事。

    關(guān)于const,我直到現(xiàn)在基本上還是一個(gè)nazi,我會(huì)斥責(zé)任每一個(gè)不盡可能常量化變量和參數(shù)的程序員。

    我現(xiàn)在的風(fēng)格主要是在向函數(shù)式編程靠近,這樣可以舍去很多舊習(xí),逐漸遠(yuǎn)離一些OOP的方向。

    關(guān)于C++函數(shù)式編程John Carmack寫(xiě)過(guò)一篇《Functional Programming in C++》值得一讀!《程序員》對(duì)這篇文章做過(guò)編譯。

    原文鏈接:KOTAKU


    總結(jié)

    以上是生活随笔為你收集整理的代码之美——Doom3源代码赏析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。