三年前舊代碼的重構、總結與反思

最近在維護一個三年前的舊代碼,用的是laravel框架。php

從某些方面來說,這個代碼算是比較標準爲了實現「在規定的時間內完成相關功能」,同時「程序員水平不高」、「通過大量優化」以後,變地特別爛的。可是其中,程序員的水平和態度是最主要的,其餘相對於而言都是次要的。java

固然,我就是那幾個程序員之一,因此我能夠放心大膽地說本身的壞話laravel

另外本文會屢次提到語言間的對比,固然本文的目的並不在此。程序員

框架之爭

不管是當年來看仍是如今來看,Laravel框架思想仍是結構,都算得上是「Modern PHP」的典範。spring

Laravel之於php,就至關於springboot之於java。docker

Laravel針對http請求引入了中間件,稍微配置一下即可以很方便地使用相似servlet的攔截器,功能還遠比servlet強大。數據庫

針對ORM類的需求,自創了eloquent框架,使用的簡潔性上也算得上是一流。安全

至於安裝、配置、部署、依賴等等,laravel也都提供了徹底通用的方案,這就很可怕了。springboot

能夠這麼說,若是咱們徹底按照laravel的架構,徹底遵守laravel的文檔,寫出來的代碼即使不會很優雅,可是也絕對不會特別坑。php7

固然,laravel缺點也是很明顯的。最重要的一個缺點,性能。

做爲PHP框架,laravel的性能無心識特別拖後腿的地方。咱們這裏已經沒有詳細數據,可是大概的數據咱們能夠提供一下:一個最簡單的路由,裏面只有Redis::set這一個操做,而且沒有任何中間件或者計算邏輯,24C64G的機器,只能支撐到大約300+QPS,即使開了opcache等也沒有質的提高。至於php7,第一個7.0版本是發佈在15年12月,項目上線四個月後,不要說還要等laravel支持PHP7,更不要說php7也知足不了性能需求。

這也就是咱們初版代碼就已是兩種語言異構的緣由。

PHP部分用於處理正常業務請求,Go部分用於處理心跳等其餘請求。

技術和能力

在後續的幾年內咱們也在反思這個問題。

若是咱們當初作的是採用tcp協議進行傳輸數據,服務端也用Go,那麼咱們還能夠作不少「看上去很酷」的事情,例如:咱們能夠實現並處處宣揚C10K、C100K,能夠處處宣揚實現了十萬百萬MPS(Message per second),能夠將全部的任務結果流式傳輸到服務端,能夠作到同步返回結果,可讓用戶體驗上更好。

可是若是這樣作的話,無狀態、流量、日誌存儲等即是要考慮的新問題。這可能會把咱們培養成技術流,可是也可能把咱們的項目變成新的「技術瘤」。

如今回想起當年,更重要的問題在於,咱們肯定當時參與者沒有人敢提出這種方案,更沒有人能駕馭住這個方案

維護成本

維護成本無疑是後期最大的成本。

早期咱們依賴supervisord,在最先期咱們經歷過supervisord和docker的supervisord衝突的故障,後期咱們也經歷過其餘項目也依賴supervisord、由於配置緣由致使其餘項目被中止的故障。故障麼,本身的鍋本身背,也沒什麼好說的。

可是其餘的維護成本是比較多的。

你所能想到的,例如agent的保活,算是比較常見的問題,幾十上百個agent總會有一兩個出問題,這個咱們也都習覺得常了,甚至本身作一作自動修復也能解決問題。

你所不能想到的,有些人將環境相關的任務也給算到你的頭上。有些人會由於「你這個系統怎麼在這個環境出現了這個問題」,查了半天,對方端口沒有打開。

這點在咱們中間件相關的項目比較常見。一般他們會上來就問這個中間件怎麼出這種問題了,實際上呢,讓他們把堆棧完整地發出來以後,告訴他們「caused by裏寫明瞭,unknown host,就是你的XXX域名沒配嘛」。天天都有四五我的問這種問題,也會給答疑方帶來很大的壓力。

在咱們的其餘工做中,也在不斷地探索如何減小教育成本,FAQ、培訓彷佛收效都不高。

僞裝噴人彷佛有效,例如「你XX的是否是開遠程調試了(此處請腦補意大利炮)」,可是這種操做也不能天天都能作的。

如何下降教育成本、讓開發者本身擁有本身解決問題的能力,一直是咱們工做的重點。可是目前看起來,咱們在這方面收效甚微。

需求、功能、BUG和變遷

這個項目咱們也算是頂住了很大壓力,沒有接新的需求,也沒有再去增長新的功能。

咱們當時的理由是「他僅僅是個任務的服務端」,「你只要如此這般寫這個任務即可以實現這個功能了」。

可是後續系統變遷是咱們當時所沒有考慮的。

後續咱們有了一個新的任務管理界面,有了新的統一登陸接口,CMDB的接口也幾經變化。

最終這個系統只剩下API天天不辭辛苦地工做着。

按理來講沒人訪問的接口和界面就應該直接下掉。但是至少這也是親生的bug,我也心軟,沒辦法下手。

根據其餘系統的經驗,有時候咱們是不得不添加部分功能的,而這部分功能咱們可能會引入不少問題。

舉個例子,某些人吐槽爲何大公司的代碼如此之爛,一個項目中httpclient就有四個版本。

這件事情能夠理解,例如早期可能只用了HttpURLConnection進行get請求,中期爲了支撐post請求,支持參數,支持超時,分別對HTTPClient了封裝,後期由於引入JWTs,又封裝了一次。代碼冗餘度變高,可是既然「系統跑得很好」,也就「沒有精簡的必要」。

能夠理解,可是不表明能夠接受。

代碼冗餘一直是內部項目重構時常見的問題,一般表現爲爲了避免影響原有代碼的執行,把現有的代碼拷貝一份,換個名稱,修改一下交給新接口來調用。

Java等靜態語言合併冗餘代碼比較簡單,編譯成功便可保證大部分功能可用。可是php等動態語言咱們則不敢這麼作。PHP作不到「編譯成功便保證基本沒問題」。

就這個例子來看,一方面是開發對HttpClient的認知不足,另外一方面則是開發對代碼的抽象能力不夠,也未留下適當的接口知足將來的需求,纔會出現「一個項目中httpclient就有四個版本」的噩夢。

有些內部系統也會和早期的咱們同樣,首先爲了作出成果,而後纔是追求更高層次。

可是這並非一個作技術的人應該有的態度。

優秀程序員的價值,不在於其所掌握的幾招屠龍之術,而是在細節中見真著。

若是咱們能夠一次把事情作對,而且作好,在容許的範圍內儘量追求卓越,爲何不去作呢?

成果是要有的,可是一個作技術的人,應該有對職業的自我尊重、對自我價值的追求和對卓越的理解和渴求。

完美有多遠?不知道,可是我之後確定會多走幾步。

單元測試和語言

併發控制其實是個蛋疼的問題,誇張一點說,當時的PHP並不能特別輕鬆地實現併發,甚至不能實現併發。咱們目前的服務端實際上只是作了任務轉發,採用了一些取巧的方法實現併發(curl_multi),可是咱們並不能實現併發控制等功能。至於說多線程(pthreads)和多進程(pcntl)的方案,實測下來也並不穩定,測試階段便會產生coredump。

而且通過屢次調優,咱們也最終解決了curl_multi的性能問題,能夠達到成千上萬的併發,而且性能還算能夠。

如今覆盤一下,若是用的是Go的話,能夠很輕鬆地用5-6行代碼增長併發控制。Go語言自身性能不錯,併發也很好。

Go語言的功能之一就是自帶單元測試。這點和maven差很少,可是Go是少數幾個語言層提供測試工具鏈的語言之一。

相比於動態語言,靜態語言的優點之一即是安全。

能夠稍微誇張點講,靜態語言一旦編譯成功,除非有RuntimeExcetion,否則基本不會出問題。

而PHP這種動態類型的語言,就比較蛋疼了:不只寫的時候可能會有問題,不少IDE也沒法意識到你究竟是不是寫了個bug,甚至過幾年回來閱讀代碼,即使是本身參與過的項目,讀起來代碼也很蛋疼。PHP也意識到了這一點,從PHP7引入了類型聲明,也能緩解這個問題。

用Go語言以前,個人習慣是不寫單元測試。用了Go語言以後,我開始養成對全部函數都寫單元測試的習慣。

咱們本文中提到了不少次Go語言,實際上語言對項目的影響並不大,真正起主導做用的,仍是人。

規範

運維規範對本項目的影響並不大,主要是開發規範。

後續的工做中,我不止一次·告誡業務開發,咱們目前全部的規範,不管是運維規範、數據庫開發規範,或者任何代碼開發規範,都是咱們一次一次地踩坑鋪出來的路。

若是當初咱們有數據庫開發規範的話,表結構也不會這麼坑。就像laravel框架同樣,咱們按照規範來寫,不至於讓代碼上升一側層次,可是也不至於讓代碼爛出水平。

在此咱們強烈對小公司和開發人員推薦《阿里巴巴Java開發手冊》,不只有開發規範、還有表結構規範,不管是對開發或是對公司都有好處。

語言對項目的影響並不大,真正起主導做用的,仍是人。

若是人的平均素質並不能達到優秀的話,那麼完善的流程和規範將能很大程度上影響一個項目的質量。

總結

教育成本是後期維護的主要成本之一,咱們也一直嘗試賦予開發者本身解決問題的能力,雖然很難。

人的素質無疑能直接決定一個項目的質量。

固然,對於普通公司、新人這種平均素質達不到優秀的狀況,完善的流程和規範將很大程度上保障一個項目的質量。

靜態語言、單元測試等手段是保障項目穩健性的重要方式。

相關文章
相關標籤/搜索