<ruby id="h6500"><table id="h6500"></table></ruby>
    1. <ruby id="h6500"><video id="h6500"></video></ruby>
          1. <progress id="h6500"><u id="h6500"><form id="h6500"></form></u></progress>

            我從資深軟件工程師學(xué)到的避坑大法

            發(fā)表于:2019-09-02來(lái)源:Neil Kakkar作者:Neil Kakkar點(diǎn)擊數: 標簽:
            過(guò)去一年中,我坐在一位資深的軟件工程師旁邊,可以仔細地觀(guān)察他是怎么工作的。我們兩人經(jīng)常共同編程,使得這項觀(guān)察更為容易。此外,在團隊文化中,從背后窺探寫(xiě)代碼的人并不

            過(guò)去一年中,我坐在一位資深的軟件工程師旁邊,可以仔細地觀(guān)察他是怎么工作的。我們兩人經(jīng)常共同編程,使得這項觀(guān)察更為容易。此外,在團隊文化中,從背后窺探寫(xiě)代碼的人并不令人反感。以下是我所學(xué)到的:

            編寫(xiě)代碼

            如何命名

            我首先著(zhù)手的是 React UI。我們有一個(gè)主要組件來(lái)放置其他所有的組件。我喜歡在代碼里加點(diǎn)幽默感,因此我想要將它命名為 GodComponent。當進(jìn)入代碼審查環(huán)境的時(shí)候,我才明白為什么命名這么難。

            在計算機科學(xué)里有兩個(gè)難題:內存不足、命名、以及差一(off-by-one)錯誤。——Leon Bambrick

            我每個(gè)命名的代碼段都有隱藏含義在里面。GodComponent 是所有我不必費心去尋找合適位置來(lái)存放那些垃圾的地方,它可以容納所有東西。如果我早早把它命名為 LayoutComponent,之后的我就會(huì )發(fā)現它所做的就是分配 layout,沒(méi)有狀態(tài)。

            我發(fā)現命名好的另一個(gè)好處是:如果它看起來(lái)太長(cháng)了,就像 LayoutComponent 包含了很多業(yè)務(wù)邏輯層,我就知道是時(shí)候要重構了,因為業(yè)務(wù)邏輯層并不屬于這里。如果是以 GodComponent 命名,這里的業(yè)務(wù)邏輯層也不會(huì )和其他有所區別。

            命名你的集群?以在服務(wù)器上運行的服務(wù)名稱(chēng)來(lái)命名更好,直到用它們來(lái)運行其他服務(wù)為止。我們最終以團隊的名字來(lái)命名服務(wù)器。

            在函數上也是同樣的道理。doEverything() 是一個(gè)糟糕的名字,會(huì )有很多難以預料的后果。如果這個(gè)函數能夠做所有事情,那么在測試函數某個(gè)特定部分時(shí)將變得非常困難。因為不管這個(gè)函數有多大,你都不會(huì )覺(jué)得奇怪,畢竟這個(gè)函數應該做所有的事情。這時(shí)候就需要改名、重構了。

            有意義的命名也有不太好的一面。如果名字的表意太強,結果掩蓋了一些功能上的細微差別怎么辦?例如:當你在 SQLAlchemy 中調用 session.close() 時(shí),這只會(huì )關(guān)閉會(huì )話(huà)但不會(huì )關(guān)閉底層數據庫的連接。

            在這種情況下,可以以 x,y,z 來(lái)命名而不是 count(),close(),insertIntoDB(),這樣可防止為其賦予隱性含義并強制開(kāi)發(fā)人員仔細檢查它所執行的操作。

            歷史代碼和下一名開(kāi)發(fā)者

            你曾否看過(guò)一些代碼,覺(jué)得它們很奇怪?這些代碼為什么這么做呢?它們的實(shí)現一點(diǎn)都不合理。

            我曾負責過(guò)遺留代碼庫。代碼中有諸如「當 Mohammad 發(fā)現情況時(shí)取消注釋代碼」這類(lèi)的注釋。這是在做什么?誰(shuí)是 Mohammad?

            在這里可以做下角色轉換——想象下一個(gè)人來(lái)看我的代碼,他們是否會(huì )覺(jué)得奇怪?

            同行審查可以某種程度上解決代碼注釋這個(gè)問(wèn)題。這讓我想到了上下文的概念:注意我團隊正處的上下文位置。

            如果我忘記了這部分代碼,之后又回到了代碼工作上,沒(méi)有注釋的話(huà)我不能重新創(chuàng )建上下文,我可能只會(huì )想:「為什么他們要這么寫(xiě)?這沒(méi)有任何意義……哦,等等,是我寫(xiě)的?!?/p>

            這里就是開(kāi)發(fā)文檔和注釋該出現的地方。

            文檔和注釋

            文檔和注釋有助于維護上下文和分享知識。

            正如李在《如何構建好軟件》中所說(shuō),「軟件的主要價(jià)值不是編寫(xiě)它的代碼,而是編寫(xiě)它的人所積累的知識?!?/p>

            比如說(shuō),我們有個(gè)似乎沒(méi)有人用過(guò)的、面向隨機客戶(hù)端的 API 終端。因為這些原因,我就應該把它刪除嗎?畢竟這是一個(gè)技術(shù)累贅。

            如果說(shuō),在某個(gè)特定國家,有 10 名記者會(huì )一年一次將他們的報道發(fā)送到這個(gè)終端,怎么辦?你如何測試它?如果沒(méi)有開(kāi)發(fā)文檔(那時(shí)就沒(méi)有)就不能測試。所以我們沒(méi)有測試。我們刪除了那個(gè)終端。過(guò)了幾個(gè)月后,到了一年中發(fā)送的時(shí)間,因為這個(gè)終端已經(jīng)不存在了,10 名記者也就無(wú)法發(fā)送這 10 份重要報告。

            雖然熟悉產(chǎn)品的人已經(jīng)離開(kāi)了團隊,但是現在代碼中有注釋解釋終端的作用。

            據我所知,文檔是每個(gè)團隊都在努力的東西。不僅僅是代碼的文檔,還有關(guān)于代碼的流程。

            自信地刪掉垃圾代碼

            我過(guò)去很不喜歡刪除垃圾代碼或過(guò)時(shí)的代碼。我認為過(guò)去寫(xiě)的代碼都是神圣的。我的想法是:「他們寫(xiě)這些代碼的時(shí)候肯定有一些想法?!惯@是傳統和文化與第一性原則之間的碰撞,與刪除一年一次的終端發(fā)生的事相同。我在那里學(xué)到了詳細的一課。

            我嘗試基于已有代碼進(jìn)行工作,但是資深工程師會(huì )嘗試解決掉它——全部刪除。一個(gè)永遠無(wú)法到達的 if 聲明?一個(gè)不應該調用的函數?是的,都消失了。

            至于我呢?我只會(huì )把我的函數寫(xiě)在最上面。我沒(méi)有減少這些技術(shù)累贅,反而增加了代碼的復雜程度,以及誤導別人的可能。下一個(gè)人將事情拼湊起來(lái)會(huì )更困難。

            現在我受到的啟發(fā)是:有一些代碼你可能不理解,也有一些代碼你知道永遠不會(huì )用。刪除那些你永遠都不會(huì )用的代碼,小心那些你不理解的代碼。

            代碼審查

            代碼審查對學(xué)習來(lái)說(shuō)非常有用。這是你寫(xiě)代碼和其他人寫(xiě)代碼時(shí)進(jìn)行的外部反饋循環(huán)。

            兩種實(shí)現有什么區別呢?一種方法比另一種好嗎?每次代碼審查時(shí)我都問(wèn)自己:「他們?yōu)槭裁催@樣做?「。每當我找不到合適的答案時(shí),我就會(huì )去和他們談?wù)劇?/p>

            在第一個(gè)月后,我開(kāi)始在同事的代碼中找到錯誤(就像他們對我代碼做的一樣)。同行審查對我來(lái)說(shuō)變得更有趣了——這是我期待的游戲——一個(gè)提高我代碼意識的游戲。

            我的啟發(fā)是:在理解代碼如何實(shí)現前不要批準它。

            測試

            我非常喜歡測試,以至于如果沒(méi)有測試就將代碼寫(xiě)入代碼庫我會(huì )感到非常不舒服。

            如果整個(gè)應用程序只做一件事(就像我所有的學(xué)校項目),那么手動(dòng)測試是可以的。但是如果該應用程序可完成 100 種不同的功能,那該怎么辦呢?我不想花半個(gè)小時(shí)來(lái)測試所有的功能,何況有時(shí)候還會(huì )忘記一些需要測試的地方。

            所以就出現了自動(dòng)化測試。

            我認為測試是一種文檔,是對代碼假設的文檔。測試會(huì )告訴我(或我之前的人)他們預想代碼是如何工作的,以及他們預期哪里會(huì )出錯。

            所以,當寫(xiě)測試時(shí),我會(huì )記?。?/p>

            1. 記錄如何使用測試時(shí)用到的類(lèi)/函數/系統。
            2. 記錄我所想到的會(huì )出錯的地方。

            在大多數情況下,以上的結論是在我在測試而不是實(shí)現的過(guò)程中想到的。

            以下是我在 Google 衛生間小休時(shí)學(xué)到的例子:

            • 我在 #2 中遺漏了一些東西,那里是 bug 出現的地方;
            • 所以每當發(fā)現 bug 時(shí),確保修復 bug 的代碼也有相應的測試(稱(chēng)為回歸測試),用于記錄信息:這里可能出現另一種錯誤。

            僅僅編寫(xiě)這些測試并不能提高我代碼的質(zhì)量,而編寫(xiě)代碼卻可以。但是我從閱讀測試代碼中獲得了寫(xiě)更好代碼的直覺(jué)。

            但是,并不只有這一種測試,這就是為什么有部署環(huán)境測試的原因。

            你可以有完美的測試單元,但是如果沒(méi)有系統測試,就會(huì )出現以下的情況:

            這同樣適用于已經(jīng)測試好的代碼:如果你機器上沒(méi)有你需要的庫,你會(huì )崩潰。

            為了測試你需要:

            • 有一臺你用于開(kāi)發(fā)的機器;
            • 有一臺你用于測試的機器;
            • 最后,有一臺你部署的機器(請不要用與開(kāi)發(fā)程序使用同一臺)。

            如果測試和部署機器之間的環(huán)境不匹配,你就遇到麻煩了。所以這里就出現了部署環(huán)境。

            • 我們先有本地開(kāi)發(fā)環(huán)境,在我的機器上是 docker;
            • 然后有服務(wù)器上的開(kāi)發(fā)環(huán)境,機器上安裝了一系列的庫(和開(kāi)發(fā)工具),我們在安裝了代碼的機器上進(jìn)行開(kāi)發(fā)。其他相關(guān)依賴(lài)的測試都可以在這里進(jìn)行;
            • 接下來(lái)是 beta/stage 環(huán)境,它與生產(chǎn)環(huán)境完全一樣;
            • 最后是生產(chǎn)環(huán)境,它是代碼運行和服務(wù)于實(shí)際客戶(hù)的機器上的環(huán)境。

            這里的想法是嘗試捕獲單元和系統測試無(wú)法捕獲的錯誤。例如,請求系統和響應系統之間的 API 不匹配。個(gè)人項目與小公司的情況大不一樣。不是每個(gè)人都有資源來(lái)搭建自己的設備。然而,這個(gè)想法仍適用于像 AWS 和 AZURE 這樣的云供應商。

            你可以為開(kāi)發(fā)和生產(chǎn)設置分開(kāi)的集群。AWS ECS 使用 docker 鏡像來(lái)部署,所以即使跨環(huán)境事情也會(huì )相對平穩。棘手的一點(diǎn)是其他 AWS 服務(wù)之間的集成。你是否可以在正確的環(huán)境中調用正確的終端呢?

            你甚至可以更進(jìn)一步:下載其他 AWS 服務(wù)的備用容器鏡像并使用 docker-compose 來(lái)配置本地完整的環(huán)境。它會(huì )加速反饋循環(huán)。

            設計

            為什么我要將設計放到寫(xiě)代碼和測試的后面呢?設計本應該在第一位,但是如果我沒(méi)有在環(huán)境中寫(xiě)代碼和測試,我可能會(huì )不擅長(cháng)設計一個(gè)遵循環(huán)境特性的系統。

            在設計系統時(shí),有很多事情需要考慮:

            • 使用編號是多少?
            • 有多少用戶(hù)?預期增長(cháng)是多少?(即需要使用多少數據行)
            • 未來(lái)可能出現的問(wèn)題是什么?

            我需要把它轉成一個(gè)名為「需求收集」的合理清單。這個(gè)過(guò)程有點(diǎn)與靈活性的原則相悖——在開(kāi)始系統開(kāi)發(fā)之前,你可以設計多少部分呢?但是這是一種平衡——你需要選擇什么時(shí)候做什么。當然僅僅收集需求并不是所有需要考慮的事情。我認為,在設計中包含了開(kāi)發(fā)的過(guò)程也是值得去做的。例如:

            • 本地開(kāi)發(fā)如何運作?
            • 怎么打包和部署?
            • 如何進(jìn)行端對端的測試?
            • 怎么對新的服務(wù)進(jìn)行壓力測試?
            • 怎么管理機密信息?
            • CI/CD 集成?

            我們最近為 BNEF 開(kāi)發(fā)了一個(gè)新的搜索系統。做這件事真的很棒。我開(kāi)始設計本地開(kāi)發(fā),學(xué)習 DPKG(打包和部署)和試圖解決部署機密信息的問(wèn)題。

            誰(shuí)會(huì )想到對產(chǎn)品中的機密信息進(jìn)行部署會(huì )變得如此棘手呢?

            1. 你不能將這些信息存到代碼中,因為這樣任何人都能看得到。
            2. 把它們作為環(huán)境變量?這是一個(gè)好主意。但你怎么把它們放在那里?(每次機器啟動(dòng)時(shí)訪(fǎng)問(wèn) PROD 機器來(lái)填充環(huán)境變量是一件痛苦的事情)
            3. 部署為機密文件?文件從哪里來(lái)呢?怎么進(jìn)行填充呢?

            而且我們不想進(jìn)行手動(dòng)操作。

            最后我們使用了一個(gè)有角色訪(fǎng)問(wèn)控制的數據庫(只有我們的機器可以與數據庫對話(huà))。我們的代碼在啟動(dòng)時(shí)從這個(gè)數據庫中獲取秘密數據。這個(gè)能在開(kāi)發(fā)、測試和產(chǎn)品之間很好地復制——在各自的數據庫中都有機密。

            同樣的,對于像 AWS 這樣的云供應商,這可能非常不同。你不必考慮太多機密。獲取你角色賬戶(hù),在用戶(hù)界面中輸入機密數據,在需要的時(shí)候你的代碼會(huì )找到它們。它簡(jiǎn)化了很多時(shí)間,這非???,而我很高興有經(jīng)驗領(lǐng)會(huì )這種簡(jiǎn)易性。

            設計時(shí)考慮維護需求

            設計系統是件令人興奮的事。維護系統呢?就沒(méi)那么有趣了。

            我在維護過(guò)程中遇到了這個(gè)問(wèn)題:系統為什么會(huì )降級,以及如何降級?

            有兩個(gè)原因可以解答為什么系統也會(huì )有降級的時(shí)候:

            首先,系統不應當舍棄舊的東西,而是在已有的基礎上增加更多功能。系統更新傾向于增加而不是刪除。

            其次,帶著(zhù)最終目標來(lái)設計。一個(gè)進(jìn)化到做不該做的事情的系統和一個(gè)從零來(lái)設計做同樣事情的系統一樣,沒(méi)有用。這是一種系統的倒退。因此需要對系統進(jìn)行降級。

            現在我知道至少三種降低降級機率的方法:

            • 將業(yè)務(wù)邏輯和基礎設施分開(kāi):通常是對基礎設施降級——當使用量增加、框架過(guò)時(shí)、出現零日漏洞等情況下;
            • 圍繞系統維護建立流程。對舊的和新的組件都使用相同的更新。這可以防止組件之間出現差異,保持整個(gè)代碼「現代化」;
            • 確保一直修剪你不想要的/舊的東西。

            部署

            將功能進(jìn)行捆綁部署還是逐個(gè)部署呢?如果答案是將功能捆綁在一起,則會(huì )出現問(wèn)題。

            接下來(lái)要問(wèn)的問(wèn)題是:為什么想要把功能進(jìn)行捆綁呢?

            • 部署是否花費過(guò)多時(shí)間?
            • 代碼審查是否容易進(jìn)行?

            不管是什么原因,這是需要修復的流程瓶頸。

            捆綁功能部署至少有兩個(gè)問(wèn)題

            1. 如果一個(gè)功能中有 bug,將妨礙另一個(gè)功能執行;
            2. 增加整體出錯的風(fēng)險。

            然后,無(wú)論你選擇什么部署過(guò)程,你總是希望你的機器像一頭牛而不是像寵物一樣。它們并不珍貴。你知道每臺機器上運行的是什么,以及如何在死機的情況下重新創(chuàng )建它們。當一臺機器死機時(shí),你不會(huì )心煩意亂,你只需要啟動(dòng)一臺新機器。你像牛一樣放養它們,而不是像寵物一樣養著(zhù)他們。

            程序出錯的時(shí)候

            當事情出錯時(shí),而且一定會(huì )有出問(wèn)題的時(shí)候,黃金法則是將對客戶(hù)的影響最小化。

            當事情出了差錯,我自然傾向于趕快解決 bug。事實(shí)證明,這并不是最理想的解決方案。與其修復哪里錯了,即使只是「修改一行」,所做的第一件事應該是回滾版本?;氐街暗墓ぷ鳡顟B(tài),這是讓客戶(hù)恢復工作最快的方法。

            過(guò)了這個(gè)時(shí)候,才應該看看哪里出了問(wèn)題并修復那些 bug。

            在你的集群中出現一臺「垮掉」的機器也應當是同樣的做法——在試圖找出機器出了什么問(wèn)題之前,先把它停了,并標記它不可用。

            首先找 bug 這種本能會(huì )引導我走上解決 bug 的漫長(cháng)旅途,反而偏離了讓客戶(hù)先恢復工作這一理想的目標狀態(tài)。有時(shí)候,我覺(jué)得它沒(méi)有工作的原因是因為寫(xiě)的代碼有問(wèn)題,而仔細閱讀每一行代碼后會(huì )陷入混亂,像是一種深度優(yōu)先搜索。

            之后,我的啟發(fā)是,首先開(kāi)始廣度優(yōu)先搜索,然后再深度優(yōu)先搜索,去除最頂端的節點(diǎn)。能否用已有的資源確認:

            • 機器啟動(dòng)了嗎?
            • 是否安裝了正確的代碼?
            • 配置是否正確?
            • <代碼特定配置>,像代碼中的路由是否正確?
            • 模式版本是否正確?
            • 然后進(jìn)入代碼。

            在某次出錯的問(wèn)題上,我們以為機器上沒(méi)有正確安裝 nginx,但結果是配置被設置為了 false。

            當然,我不需要總是這樣做。有時(shí)候錯誤信息已經(jīng)足以減少需要搜索代碼的區域。而且當我無(wú)法解決這個(gè)問(wèn)題時(shí),我嘗試并持續修改代碼以將問(wèn)題降到最低。修改的次數越少,我就能越快地處理實(shí)際問(wèn)題。

            但是我現在還是會(huì )記錄花了 1 個(gè)多小時(shí)來(lái)解決的 bug:遺漏了什么?這通常是一些我忘記檢查的愚蠢錯誤,比如像設置路由、確保模式版本和服務(wù)版本匹配等。這是熟悉使用的技術(shù)堆棧的另一步,而且只有經(jīng)驗會(huì )告訴我為什么系統無(wú)法運行。

            監控

            這是我以前從未想過(guò)去做的事。說(shuō)句公道話(huà),在全職編碼之前,我從沒(méi)維護過(guò)系統。我只是搭建它們,使用 1 個(gè)星期后然后進(jìn)行下一項工作。

            有兩個(gè)系統,一個(gè)有良好的監控,另一個(gè)并不那么好。我逐漸非常喜歡監控。如果我不知道 bug 在哪我就不能修改錯誤。其中一種最糟糕的感覺(jué)是從客戶(hù)那里知道有 bug。

            「我做了什么?!我甚至不知道我的系統出了什么問(wèn)題?」

            我認為監控由 3 個(gè)部分組成——日志、衡量標準和警報。

            日志

            以代碼中進(jìn)行日記記錄就像人寫(xiě)日志一樣,是一個(gè)進(jìn)化的過(guò)程。

            你要找到你可能需要監控的東西,日志記錄下來(lái),運行系統。一段時(shí)間后,你會(huì )發(fā)現你沒(méi)有足夠信息來(lái)解決的 bug。這是增強日志記錄的好時(shí)機——你的代碼少了些什么?

            我想你會(huì )憑直覺(jué)地知道什么東西很重要需要記錄,但是在我們的服務(wù)器中我和資深軟件工程師所記錄的東西有很多不同。我認為只要請求-相應日志就足夠了,但是他會(huì )有更多的記錄內容,比如查詢(xún)執行時(shí)間、代碼進(jìn)行的一些特定的內部調用,以及何時(shí)轉儲日志。一切都已經(jīng)解決了。

            幾乎不可能在沒(méi)有日志的情況下進(jìn)行調試——如果你不知道系統的狀態(tài),你怎么重新創(chuàng )建它呢?

            衡量標準和驚爆

            衡量標準可以源于日志,也可以獨立于日志(例如向 AWS CloudWatch 和 Grafana 發(fā)送時(shí)間)。你可以決定你的衡量指標并在代碼運行時(shí)發(fā)送數字。

            警報是把所有東西整合到一個(gè)的強大監控系統的粘合劑。如果一個(gè)衡量標準是當前產(chǎn)品中運行的機器數量,當這個(gè)數字降到 50% 時(shí),這是一個(gè)很好的警報——你知道有什么出錯了。

            失敗計數高于某個(gè)閾值時(shí)?是的,又一個(gè)警報。

            這里暗示了另一個(gè)需要養成的習慣。當你修復 bug 時(shí),你不僅僅關(guān)注如何修復 bug,而是你為什么不早點(diǎn)發(fā)現它呢?是否有布置警報?如何能夠更好地監控來(lái)避免類(lèi)似的問(wèn)題?

            我還不知道如何監控 UI。即使吧組件測試到位,也還不足以了解出錯的情況。這些錯誤通常是由客戶(hù)來(lái)告訴我們的——這看起來(lái)不太對勁。

            總結

            在過(guò)去的一年里,我學(xué)到了很多東西。當我對這篇文章進(jìn)行回顧時(shí),我能夠更好地體會(huì )到我的成長(cháng)。希望你也可以從這里得到一些東西!


            原文轉自:https://neilkakkar.com/things-I-learnt-from-a-senior-dev.html#when-things-go-wrong

            老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月
              <ruby id="h6500"><table id="h6500"></table></ruby>
              1. <ruby id="h6500"><video id="h6500"></video></ruby>
                    1. <progress id="h6500"><u id="h6500"><form id="h6500"></form></u></progress>