『代碼效率重構(gòu)與可擴(kuò)展性思路』----“PHP與MySQL的應(yīng)用開發(fā)”閱讀筆記
這是一篇我在閱讀《PHP與MySQL的應(yīng)用開發(fā)》時,2020年9月29日發(fā)表于某平臺的學(xué)習(xí)隨記,現(xiàn)轉(zhuǎn)載至B站。?
關(guān)于如何在編寫PHP及其他代碼時優(yōu)化代碼提高擴(kuò)展性,本篇文章內(nèi)容大多摘自書本《PHP與MySQL的應(yīng)用開發(fā)》。
代碼重構(gòu)實(shí)踐
代碼重構(gòu)是一門實(shí)踐性極強(qiáng)的藝術(shù),并不是“高大上”、要束之高閣的科學(xué),我們要了解更多隱藏在代碼后面的內(nèi)容,并非寫完程序就萬事大吉,那只是懶惰的程序員所為,優(yōu)秀的開發(fā)者要承擔(dān)代碼的所有責(zé)任。
什么是不良代碼
在一些情況下,身擔(dān)上線重任的我們要完成開發(fā)任務(wù),代碼邏輯沒有仔細(xì)考慮,常常會出現(xiàn)代碼冗余,有的甚至是從其他文件中復(fù)制過來的。如果復(fù)制的代碼也存在錯誤,這樣會讓代碼越來越混亂,導(dǎo)致錯誤更大蔓延。
這些“壞味道”的代碼主要表現(xiàn)在如下方面:
代碼邏輯難以被讀懂理解
代碼中要增加很多單行注釋
代碼邏輯存在低級的Bug
存在冗余的代碼
過多面向過程的代碼
在開發(fā)任務(wù)中,如果代碼過多冗余,對于公司的運(yùn)營、項(xiàng)目的管理和后續(xù)的開發(fā)調(diào)試都會增加困難。
什么是好代碼
我們通過三個準(zhǔn)則來判斷代碼是不是好代碼,分別是:
可讀性
可擴(kuò)展性
效率
1. 可讀性
我們在項(xiàng)目中按照一些標(biāo)準(zhǔn)的語法、風(fēng)格來開發(fā)軟件,如果在解釋代碼邏輯時很容易講清楚、講明白,初步判斷就是一篇可讀性較好的代碼。
我們通常遵循一個通用標(biāo)準(zhǔn),或者自己定義一個風(fēng)格。但無論你選擇哪種,當(dāng)同事接替你的工作,按照你的代碼、風(fēng)格以及文檔、注釋等就能順利工作,而無須太多解釋,這也是干凈的好代碼。
在講究嚴(yán)謹(jǐn)?shù)难邪l(fā)團(tuán)隊(duì)中,成員都遵守通用的編碼標(biāo)準(zhǔn),當(dāng)有新人加入團(tuán)隊(duì),可以有效縮短學(xué)習(xí)曲線和有效減少開發(fā)成本。
2. 可擴(kuò)展性
可讀性和可擴(kuò)展性有助于代碼的可維護(hù)性,使得在項(xiàng)目規(guī)劃的時間內(nèi)快速迭代。
使用設(shè)計(jì)模式,它有助于可讀性和可擴(kuò)展性。
軟件開發(fā)中遵循一些可重復(fù)使用的、邏輯的、已知的設(shè)計(jì)模式,它是定義的標(biāo)準(zhǔn)設(shè)計(jì)模式或正常的邏輯流程,因此代碼具備更大的可擴(kuò)展性。
一般的,除非有特殊情況,盡量不去使用while和for這樣的循環(huán)語句,在遍歷數(shù)組時通常會用foreach語句,以避免不必要的混亂,造成可維護(hù)性不佳。
可擴(kuò)展性的主要體現(xiàn)是去除耦合和封裝。去除耦合意味著代碼(主要是函數(shù)/方法和類)不相互依賴或相互重疊,而應(yīng)該是“純粹”的功能實(shí)現(xiàn),任何重疊的功能和其他非關(guān)聯(lián)性實(shí)體都應(yīng)該刪除。
我們正在寫的函數(shù)邏輯應(yīng)該與它的命名相符,不應(yīng)該“掛羊頭賣狗肉”,即不在函數(shù)內(nèi)做額外的東西。
在編寫代碼時使用“工具箱”的方式——眾多函數(shù)庫共同打造大的復(fù)雜系統(tǒng)。
封裝也是去除耦合的重要部分。姚封裝組件,就要從項(xiàng)目的范圍抽取邏輯,并分離其內(nèi)部相互作用縮小在接口級別,這是一個通用模塊化的方法,這樣使得它更容易在以后刪除或更新系統(tǒng)的任何功能,使代碼更清晰。
3. 效率
在開發(fā)過程中,我們應(yīng)在頭腦中時刻牢記“效率”,效率是系統(tǒng)的瓶頸問題。
因?yàn)樵谌魏雾?xiàng)目的代碼中都有使用一些不合理的結(jié)構(gòu):例如嵌套循環(huán)和遞歸。以及一些不正常的處理邏輯,例如哦不使用緩存和發(fā)送/接收數(shù)據(jù)時沒有恰當(dāng)?shù)氖褂镁彺妗?/p>
互聯(lián)網(wǎng)產(chǎn)品中,可讀性和可擴(kuò)展性不好導(dǎo)致的結(jié)果就是運(yùn)行效率降低,大多數(shù)情況下,效率和這兩個規(guī)則成正比。
如何增加代碼可讀性
1. 代碼文件是否遵循相同編碼風(fēng)格
應(yīng)該使用代碼標(biāo)準(zhǔn),最好是一個通用的標(biāo)準(zhǔn),如PSR-1/2.
如果要自定義編碼標(biāo)準(zhǔn),你可以以自己的代碼為范本,為自己和自己的團(tuán)隊(duì)寫一個簡短的說明。
2. 開發(fā)使用的是標(biāo)準(zhǔn)語法結(jié)構(gòu),不使用怪語法
由于PHP的靈活性,可能有多個樣式來編寫程序結(jié)構(gòu)。一般的,我們只在一個項(xiàng)目需堅(jiān)持一種風(fēng)格。
3. 每個文件有一個標(biāo)題,注釋、記錄其在項(xiàng)目中的角色
作為一項(xiàng)基本要求,你應(yīng)該在每個程序文件中包括一個頭與下面的注釋:
應(yīng)用程序的名稱、版本和簡要說明
文件到其他文件和包/組的從屬關(guān)系
4. 通用的代碼保持在最低限度
在項(xiàng)目中為加快開發(fā)速度和引用方便,我們會使用公共腳本,然后使用include包含語法引入當(dāng)前PHP腳本。
如果是一個復(fù)雜的Web應(yīng)用程序,使用公用腳本也會成為頭痛的問題,如果在文件中使用過多的include和require,多人同步開發(fā)工作中他們未必會完全理解,所以要最大限度地減少使用。
如果通用的代碼包含其他模塊函數(shù)和類定義,特別控制結(jié)構(gòu),需要重構(gòu)函數(shù)或類。作為基本經(jīng)驗(yàn)法則,控制結(jié)構(gòu)不應(yīng)該包括在公共的代碼中。
命名方式
類、變量等命名,遵循標(biāo)準(zhǔn)模式和代碼風(fēng)格
命名有意義且無歧義
命名是否過長或過短
變量名和常量的長度平均在8-12個字符,特殊的可以達(dá)到25個字符,但更重要的是不要命名成為不可讀的、繁瑣的名字。
方法/函數(shù)的名稱應(yīng)平均控制在15~25個字符長度,例外的可以達(dá)到30個字符。
類的命名應(yīng)該控制在10~15個字符。
表達(dá)式
表達(dá)式遵循標(biāo)準(zhǔn)格式與風(fēng)格
表達(dá)式理解復(fù)雜還是容易
代碼段
1. 代碼段遵循標(biāo)準(zhǔn)模式/風(fēng)格
保持自己的開發(fā)標(biāo)準(zhǔn),比如在合適的地方加括號、空格等,你的眼睛舒服地看到規(guī)律代碼結(jié)構(gòu)時,在讀代碼時會更增進(jìn)理解。
2. 代碼段大于25行時需要重構(gòu)
如果代碼塊超過25行,建議至少重構(gòu)一次,如果愿意多次重構(gòu)代碼,可以達(dá)到最佳效果。
有時代碼塊會有些特殊,可能會達(dá)到50行甚至更多。這種情況還是少數(shù),應(yīng)盡量避免。長代碼的可讀性不夠好,越小塊代碼的可讀性和可維護(hù)性越大。
3. 有對功能說明的注釋
一般的,在長于平均水平(超過20行)的代碼塊,或執(zhí)行復(fù)雜的任務(wù)或邏輯處加入注釋。
可擴(kuò)展性與效率重構(gòu)
模塊化的代碼往往是高度可擴(kuò)展的,而過程化的代碼往往可擴(kuò)展性低,過程化代碼會更有效率。因此一些常用的做法是,以模塊化的方式開發(fā)和部署在一個過程代碼的方式,可以做到兩全其美。
關(guān)于可擴(kuò)展的代碼的主要方面是:
(正常的邏輯流程和設(shè)計(jì)模式)邏輯的可擴(kuò)展性
模塊化設(shè)計(jì)
去除耦合與封裝
1. 大部分的代碼塊,按照正常的邏輯流程
我們正在處理的小的邏輯問題,請使用的是正確的結(jié)構(gòu),即應(yīng)該使用最合乎邏輯的語言功能。例如,通過簡單的數(shù)組循環(huán)用foreach,而使用for循環(huán)則是不合理的。
2. 復(fù)雜問題的解決方案遵循標(biāo)準(zhǔn)設(shè)計(jì)模式
大型項(xiàng)目中使用設(shè)計(jì)模式時必須的,因?yàn)樗鼈兺ǔH菀桌斫?,并能夠考慮到將來開發(fā)的擴(kuò)展性。
一般的,使用一個標(biāo)準(zhǔn)軟件模式來解決一些復(fù)雜的額外難題,要先編寫實(shí)現(xiàn)的基類。比如什么時候使用factory模式,什么時候使用proxy模式。
模塊化設(shè)計(jì)
1. 代碼結(jié)構(gòu)模塊化設(shè)計(jì)
模塊化設(shè)計(jì)的意義就是將產(chǎn)品按照業(yè)務(wù)不同劃分為不同的模塊。由小的應(yīng)用程序組合成一個大的應(yīng)用程序,這樣更容易開發(fā)和易于擴(kuò)展、維護(hù)。
其中每個模塊都有自己的特性和功能,可以擔(dān)當(dāng)獨(dú)立的功能和應(yīng)用。
模塊核心功能和應(yīng)用程序的入口點(diǎn),我們可以在將來方便地添加新的模塊以擴(kuò)展功能,這就是為什么現(xiàn)在大多數(shù)產(chǎn)品都設(shè)計(jì)為插件模塊來進(jìn)行開發(fā)。
因此開發(fā)者在應(yīng)用程序結(jié)構(gòu)設(shè)計(jì)時,需要有模塊/插件的安裝和卸載功能,需要開發(fā)的核心模塊,對插件的基本結(jié)構(gòu)進(jìn)行定義。
一個代碼中的一些模塊作為單一的實(shí)體和比較少的參數(shù),若不是這樣就需要把它分解成一個新的模塊。當(dāng)我有一個功能,在一個單獨(dú)的類中做了越來越多的 副作用任務(wù)時,就需要毫不猶豫地把這些遷移到一個新的模塊。
一個精心設(shè)計(jì)的應(yīng)用設(shè)計(jì)模塊化后,可以有效地防止只為一個任務(wù)而寫的孤兒代碼。當(dāng)我們寫有一些孤兒代碼時,我就將它遷移到一個實(shí)用模塊中,用于處理通用功能和完成小任務(wù),該模塊就是通用的函數(shù)庫。
當(dāng)這些任務(wù)足夠大、需剝離邏輯,我會再嘗試將它們移動到自己的獨(dú)立的模塊中,這種重構(gòu)是一種持續(xù)的過程。
2. 模塊依賴性最低
模塊自身提供盡可能多的功能,且天然地和其他模塊有良好的依賴關(guān)系,比如電子商務(wù)中庫存模塊依賴于財(cái)務(wù)模塊。
事實(shí)上有許多硬的依賴關(guān)系是不好的,它們使調(diào)試和部署變得比較困難。為了保證模塊間的依賴關(guān)系,我們需要遍歷當(dāng)前每一個模式,然后在代碼庫中看是否有任何模塊之間存在硬的依賴關(guān)系后清除它們。
如果不能,應(yīng)該將這兩個模塊合并到一個模塊,并命名為通用的名稱。
封裝與解耦
1. 函數(shù)、方法和類低耦合,高內(nèi)聚概念
常見的例子,我們在程序中添加分頁功能,從數(shù)據(jù)庫中取得結(jié)果顯示,這是一個很常見的任務(wù)。在早期新手開發(fā)中,會在進(jìn)行分頁的程序里寫一些代碼取數(shù)據(jù)庫的結(jié)果來分頁,構(gòu)成非常具體的功能。
在后來發(fā)現(xiàn),分頁很多功能的代碼是共通的,也就是能夠復(fù)制的邏輯或代碼,因此可以將這些共通的代碼抽象出來形成一個分頁函數(shù)或類。諸如此類,你也會發(fā)現(xiàn)需要對自己代碼解耦,以提高代碼的可重用性和可擴(kuò)展性。
2. 單一職責(zé):模塊和組件相對解耦
代碼保持最低限度的依賴,這是解耦的正確途徑。比如一個類只做一件事,并把這件事做好,且只有一個引起它變化的原因。
單一職責(zé)原則可以看作低耦合、高內(nèi)聚在面向?qū)ο笤瓌t上的引申,將職責(zé)定義為引起變化的原因,以提高內(nèi)聚性來減少引起變化的原因。職責(zé)過多,可能引起它變化的原因越來越多,這將導(dǎo)致職責(zé)依賴,相互之間就產(chǎn)生影響,從而極大地?fù)p傷其內(nèi)聚性和耦合度。單一職責(zé),通常意味著單一的功能,因此不要為一個模塊實(shí)現(xiàn)過多的功能點(diǎn),以保證實(shí)體只有一個引起它變化的原因。
降低依賴關(guān)系重要的指標(biāo)是可用性和可擴(kuò)展等復(fù)雜度沒有增加。
代碼效率
大多數(shù)Web應(yīng)用都要實(shí)現(xiàn)快速響應(yīng),所以效率通常依賴網(wǎng)站開發(fā)者,讓系統(tǒng)“瓶頸”保持在最低限度,無論是內(nèi)存消耗,還是CPU占用率、可用性和節(jié)省網(wǎng)絡(luò)流量。我們都希望應(yīng)用程序的響應(yīng)速度和HTML渲染是“相當(dāng)快”。
內(nèi)存緩沖與網(wǎng)絡(luò)資源緩存,可有效地利用高速緩存高效地利用內(nèi)存和處理器,讓W(xué)eb應(yīng)用具備高性能。
比如讀取一個大文件時,可將文件從磁盤拆分讀取輸出到網(wǎng)絡(luò)緩沖區(qū)。否則需要讀取整個大文件,客戶可能不知道要過多久才能下載,并可能中斷應(yīng)用程序的響應(yīng)。
除了常見的問題和重構(gòu)策略外,牢記我們談?wù)摰男什粌H僅是重構(gòu)了事,也要有優(yōu)化的兼顧。
網(wǎng)絡(luò)帶寬的效率
1. 從服務(wù)器請求資源是否在較低限度
一個Web應(yīng)用程序是一些業(yè)務(wù)邏輯,例如用PHP編寫的腳本,輸出HTML、CSS和JavaScript到客戶端瀏覽器。
實(shí)際情況上,總有一個用戶會為你的頁面加載而等待,,無論他是愿意等待得再久一點(diǎn),還是發(fā)現(xiàn)加載很慢,而關(guān)閉窗口跑掉。一般來說,頁面在512KB的連接速率上,唱過5秒還沒有顯示完成,用戶將開始感到不耐煩。Amazon有一個統(tǒng)計(jì)數(shù)字:如果頁面在3秒內(nèi)未加載完成,將流失57%的用戶,結(jié)果是PV減少,用戶率降低。每延長1秒,會損失16億美金。
為了能夠快速加載,你需要減少從服務(wù)器請求資源的數(shù)量。比如分離CSS文件,而不是將它們合并在一個文件中。另外js也和css一樣,也需要進(jìn)一步來優(yōu)化。
我們還可以壓縮CSS和js文件的尺寸,使用js壓縮工具,如YUIJS壓縮機(jī)和CSSO。對圖片可以使用CSS sprites的技術(shù),這種技術(shù)可以將一組圖片合并在一起成為一個單一的文件,可以有效減少帶寬占用。
2. 使用Ajax
現(xiàn)代化的Web應(yīng)用基于Ajax的異步調(diào)用功能越來越多,這沒有任何問題,只要你的服務(wù)器回調(diào)時沒有太大延遲即可。
但需要注意的是,不要過度使用Ajax來執(zhí)行非常簡單的任務(wù)。沒有必要盡量不去調(diào)用服務(wù)器,如果真的需要,再去取,而不是持續(xù)的實(shí)時數(shù)據(jù)。如果需要像股票類的實(shí)時數(shù)據(jù),可以考慮使用數(shù)據(jù)push技術(shù),如Comet和WebSocket。
內(nèi)存效率低
1. 是否使用了遞歸
在PHP中,使用遞歸是有負(fù)面代價的:它會比較占用內(nèi)存空間。
2. 重復(fù)太多了嗎
使用遞歸迭代的局限性已了解,另外涉及代碼效率,比如嵌套循環(huán),可以用其他解決方案來解決。
比如我們的頁面要顯示20條新聞文章,可以先顯示最新的5條,再加一個更多的鏈接。
3. 檢查數(shù)據(jù)庫查詢
這個查詢很簡單,但問題是當(dāng)用戶表超過10000個時,查詢有很多列,這個SQL在一個查詢數(shù)據(jù)庫的所有行,效率大大降低。
這是一個典型的無計(jì)劃的查詢。如果你沒有使用FROM子句,應(yīng)該花費(fèi)一些時間來規(guī)劃需要的SQ L查詢,需要決定什么樣的條件的數(shù)據(jù)需要查詢。
另外隨著記錄成倍的增長,我們應(yīng)該提前考慮到這一點(diǎn),不要講*號看作是理所當(dāng)然的。要限制特定的字段和行數(shù),如果實(shí)在不了解的話,也要使用LIMIT子句限制生成的記錄集。