最近知乎上,有一位大佬邀請我回答下面這個問題,看到這個問題我百感交集,感觸頗多。
在我是新人時,如果有前輩能夠指導方向一下,分享一些踩坑經歷,或許會讓我少走很多彎路,節省更多的學習的成本。
這篇文章根據我多年的工作經驗,給新人總結了25條建議,希望對你會有所幫助。
很多小夥伴不願意給程式碼寫註釋,主要有以下兩個原因:
我在開發的前面幾年也不喜歡寫註釋,覺得這是一件很酷的事情。
但後來發現,有些兩年之前的程式碼,業務邏輯都忘了,有些程式碼自己都看不懂。特別是有部分非常複雜的邏輯和演演算法,需要重新花很多時間才能看明白,可以說自己把自己坑了。
沒有註釋的程式碼,不便於維護。
因此強烈建議大家給程式碼寫註釋。
但註釋也不是越多越好,註釋多了增加了程式碼的複雜度,增加了維護成本,給自己增加工作量。
我們要寫好註釋,但不能太囉嗦,要給關鍵或者核心的程式碼增加註釋。我們可以寫某個方法是做什麼的,主要步驟是什麼,給演演算法寫個demo範例等。
這樣以後過了很長時間,再去看這段程式碼的時候,也會比較容易上手。
我看過身邊很多大佬寫程式碼有個好習慣,比如新寫了某個Util工具類,他們會同時在test目錄下,給該工具類編寫一些單元測試程式碼。
很多小夥伴覺得寫單元測試是浪費時間,沒有這個必要。
假如你想重構某個工具類,但由於這個工具類有很多邏輯,要把這些邏輯重新測試一遍,要花費不少時間。
於是,你產生了放棄重構的想法。
但如果你之前給該工具類編寫了完整的單元測試,重構完成之後,重新執行一下之前的單元測試,就知道重構的結果是否滿足預期,這樣能夠減少很多的測試時間。
多寫單元測試對開發來說,是一個非常好的習慣,有助於提升程式碼質量。
即使因為當初開發時間比較緊,沒時間寫單元測試,也建議在後面空閒的時間內,把單元測試補上。
好的程式碼不是一下子就能寫成的,需要不斷地重構,修復發現的bug。
不知道你有沒有這種體會,看自己1年之前寫的程式碼,簡直不忍直視。
這說明你對業務或者技術的理解,比之前更深入了,認知水平有一定的提升。
如果有機會,建議你主動重構一下自己的爛程式碼。把重複的程式碼,抽取成公共方法。有些引數名稱,或者方法名稱當時沒有取好的,可以及時修改一下。對於邏輯不清晰的程式碼,重新梳理一下業務邏輯。看看程式碼中能不能引入一些設計模式,讓程式碼變得更優雅等等。
通過程式碼重構的過程,以自我為驅動,能夠不斷提升我們編寫程式碼的水平。
有些公司在系統上線之前,會組織一次程式碼評審,一起review一下這個迭代要上線的一些程式碼。
通過相互的程式碼review
,可以發現一些程式碼的漏洞,不好的寫法,發現自己寫程式碼的壞毛病,讓自己能夠快速提升。
當然如果你們公司沒有建立程式碼的相互review機制,也沒關係。
可以後面可以多自己review自己的程式碼。
我們在寫完查詢SQL語句之後,有個好習慣是用explain
關鍵字檢視一下該SQL語句有沒有走索引
。
對於資料量比較大的表,走了索引和沒有走索引,SQL語句的執行時間可能會相差上百倍。
我之前親身經歷過這種差距。
因此建議大家多用explain檢視SQL語句的執行計劃。
關於explain關鍵字的用法,如果你想進一步瞭解,可以看看我的另外一篇文章《explain | 索引優化的這把絕世好劍,你真的會用嗎?》,裡面有詳細的介紹。
在系統上線之前,一定要整理上線的清單,即我們說的:checklist
。
系統上線有可能是一件很複雜的事情,涉及的東西可能會比較多。
假如服務A依賴服務B,服務B又依賴服務C。這樣的話,服務發版的順序是:CBA,如果順序不對,可能會出現問題。
有時候新功能上線時,需要提前執行sql指令碼初始化資料,否則新功能有問題。
要先設定定時任務。
上線之前,要在apollo中增加一些設定。
上線完成之後,需要增加相應的選單,給指定使用者或者角色分配許可權。
等等。
系統上線,整個過程中,可能會涉及多方面的事情,我們需要將這些事情記錄到checklist當中,避免踩坑。
介面檔案對介面提供者,和介面呼叫者來說,都非常重要。
如果你沒有介面檔案,別人咋知道你介面的地址是什麼,介面引數是什麼,請求方式時什麼,介面多個引數分別程式碼什麼含義,返回值有哪些欄位等等。
他們不知道,必定會多次問你,無形當中,增加了很多溝通的成本。
如果你的介面檔案寫的不好,寫得別人看不懂,介面檔案有很多錯誤,比如:輸入引數的列舉值,跟實際情況不一樣。
這樣不光把自己坑了,也會把別人坑慘。
因此,寫介面檔案一定要寫好,儘量不要馬馬虎虎應付差事。
如果對寫介面檔案比較感興趣,可以看看我的另一篇文章《瞧瞧別人家的API介面,那叫一個優雅》,裡面有詳細的介紹。
我們在設計介面的時候,要跟業務方或者產品經理確認一下請求量。
假如你的介面只能承受100qps,但實際上產生了1000qps。
這樣你的介面,很有可能會承受不住這麼大的壓力,而直接掛掉。
我們需要對介面做壓力測試
,預估介面的請求量,需要部署多少個伺服器節點。
壓力測試的話,可以用jmeter、loadRunner等工具。
此外,還需要對介面做限流
,防止別人惡意呼叫你的介面,導致伺服器壓力過大。
限流的話,可以基於使用者id、ip地址、介面地址等多個維度同時做限制。
可以在nginx層,或者閘道器層做限流。
我們在設計介面時,一定要考慮並行呼叫的情況。
比如:使用者在前端頁面,非常快的點選了兩次儲存按鈕,這樣就會在極短的時間內呼叫你兩次介面。
如果不做冪等性設計,在資料庫中可能會產生兩條重複的資料。
還有一種情況時,業務方呼叫你這邊的介面,該介面發生了超時,它有自動重試機制,也可能會讓你這邊產生重複的資料。
因此,在做介面設計時,要做冪等設計。
當然冪等設計的方案有很多,感興趣的小夥伴可以看看我的另一篇文章《高並行下如何保證介面的冪等性?》。
如果介面並行量不太大,推薦大家使用在表中加唯一索引
的方案,更加簡單。
有時候我們提供的介面,需要調整引數。
比如:新增加了一個引數,或者引數型別從int改成String,或者引數名稱有status改成auditStatus,引數由單個id改成批次的idList等等。
建議涉及到介面引數修改一定要慎重。
修改介面引數之前,一定要先評估呼叫端和影響範圍,不要自己偷偷修改。如果出問題了,呼叫方後面肯定要罵娘。
我們在做介面引數調整時,要做一些相容性的考慮。
其實刪除引數和修改引數名稱是一個問題,都會導致那個引數接收不到資料。
因此,儘量避免刪除引數和修改引數名。
對於修改引數名稱的情況,我們可以增加一個新引數,來接收資料,老的資料還是保留,程式碼中做相容處理。
我們在呼叫第三方介面時,由於存在遠端呼叫,可能會出現介面超時
的問題。
如果介面超時了,你不知道是執行成功,還是執行失敗了。
這時你可以增加自動重試機制
。
介面超時會拋一個connection_timeout或者read_timeout的異常,你可以捕獲這個異常,用一個while迴圈自動重試3次。
這樣就能儘可能減少呼叫第三方介面失敗的情況。
當然呼叫第三方介面還有很多其他的坑,感興趣的小夥伴可以看看我的另一篇文章《我呼叫第三方介面遇到的13大坑》,裡面有詳細的介紹。
有時候,線上資料出現了問題,我們需要修復資料,但涉及的資料有點多。
這時建議在處理線上資料前,一定要先備份資料
。
備份資料非常簡單,可以執行以下sql:
create table order_2022121819 like `order`;
insert into order_2022121819 select * from `order`;
資料備份之後,萬一後面哪天資料處理錯了,我們可以直接從備份表中還原資料,防止悲劇的產生。
不要輕易刪除線上欄位,至少我們公司是這樣規定的。
如果你刪除了某個線上欄位,但是該欄位參照的程式碼沒有刪除乾淨,可能會導致程式碼出現異常。
假設開發人員已經把程式改成不使用刪除欄位了,接下來如何部署呢?
如果先把程式部署好了,還沒來得及刪除資料庫相關表欄位。
當有insert請求時,由於資料庫中該欄位是必填的,會報必填欄位不能為空的異常。
如果先把資料庫中相關表欄位刪了,程式還沒來得及發。這時所有涉及該刪除欄位的增刪改查,都會報欄位不存在的異常。
所以,線上環境欄位不要輕易刪除。
我們在設計表的時候,要給相關欄位設定合理的欄位型別和長度。
如果欄位型別和長度不夠,有些資料可能會儲存失敗。
如果欄位型別和長度太大了,又會浪費儲存空間。
我們在工作中,要根據實際情況而定。
以下原則可以參考一下:
我們在設計介面,或者呼叫別人介面的時候,都要避免一次性查詢太多資料。
一次性查詢太多的資料,可能會導致查詢耗時很長,更加嚴重的情況會導致系統出現OOM
的問題。
我們之前呼叫第三方,查詢一天的指標資料,該介面經常出現超時問題。
在做excel匯出時,如果一次性查詢出所有的資料,匯出到excel檔案中,可能會導致系統出現OOM問題。
因此我們的介面要做分頁設計
。
如果是呼叫第三方的介面批次查詢介面,儘量分批呼叫
,不要一次性根據id集合查詢所有資料。
如果呼叫第三方批次查詢介面,對效能有一定的要求,我們可以分批之後,用多執行緒呼叫介面,最後彙總返回資料。
很多小夥伴有一個誤解,認為使用了多執行緒
一定比使用單執行緒
快。
其實要看使用場景。
如果你的業務邏輯是一個耗時的操作,比如:遠端呼叫介面,或者磁碟IO操作,這種使用多執行緒比單執行緒要快一些。
但如果你的業務邏輯非常簡單,在一個迴圈中列印資料,這時候,使用單執行緒可能會更快一些。
因為使用多執行緒,會引入額外的消耗,比如:建立新執行緒的耗時,搶佔CPU資源時執行緒上下文需要不斷切換,這個切換過程是有一定的時間損耗的。
因此,多執行緒不一定比單執行緒快。我們要根據實際業務場景,決定是使用單執行緒,還是使用多執行緒。
很多時候,我們的程式碼為了保證資料庫多張表儲存資料的完整性和一致性,需要使用@Transactional
註解的宣告式事務,或者使用TransactionTemplate
的程式設計式事務。
加入事務之後,如果A,B,C三張表同時儲存資料,要麼一起成功,要麼一起失敗。
不會出現資料儲存一半的情況,比如:表A儲存成功了,但表B和C儲存失敗了。
這種情況資料會直接回滾,A,B,C三張表的資料都會同時儲存失敗。
如果使用@Transactional
註解的宣告式事務,可能會出現事務失效的問題,感興趣的小夥伴可以看看我的另一篇文章《聊聊spring事務失效的12種場景,太坑了》。
建議優先使用TransactionTemplate
的程式設計式事務的方式建立事務。
此外,引入事務還會帶來大事務問題,可能會導致介面超時,或者出現資料庫死鎖的問題。
因此,我們需要優化程式碼,儘量避免大事務的問題,因為它有許多危害。關於大事務問題,感興趣的小夥伴,可以看看我的另一篇文章《讓人頭痛的大事務問題到底要如何解決?》,裡面有詳情介紹。
不知道你在使用小數時,有沒有踩過坑,一些運算導致小數丟失了精度。
如果你在專案中使用了float或者double型別的資料,用他們參與計算,極可能會出現精度丟失問題。
使用Double時可能會有這種場景:
double amount1 = 0.02;
double amount2 = 0.03;
System.out.println(amount2 - amount1);
正常情況下預計amount2 - amount1應該等於0.01
但是執行結果,卻為:
0.009999999999999998
實際結果小於預計結果。
Double型別的兩個引數相減會轉換成二進位制,因為Double有效位數為16位元這就會出現儲存小數位數不夠的情況,這種情況下就會出現誤差。
因此,在做小數運算時,更推薦大家使用BigDecimal
,避免精度的丟失。
但如果在使用BigDecimal時,使用不當,也會丟失精度。
BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));
這個例子中定義了兩個BigDecimal型別引數,使用建構函式初始化資料,然後列印兩個引數相減後的值。
結果:
0.0099999999999999984734433411404097569175064563751220703125
使用BigDecimal的建構函式建立BigDecimal,也會導致精度丟失。
如果如何避免精度丟失呢?
BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));
使用BigDecimal.valueOf方法初始化BigDecimal型別引數,能保證精度不丟失。
有些小夥伴可能寫過這樣的程式碼,在一個for迴圈中,一個個呼叫遠端介面,或者執行資料庫的update操作。
其實,這樣是比較消耗效能的。
我們儘可能將在一個迴圈中多次的單個操作,改成一次的批次操作,這樣會將程式碼的效能提升不少。
例如:
for(User user : userList) {
userMapper.update(user);
}
改成:
userMapper.updateForBatch(userList);
我們在面試中當中,經常會被面試官問到synchronized
加鎖的考題。
說實話,synchronized的鎖升級過程,還是有點複雜的。
但在實際工作中,使用synchronized加鎖的機會不多。
synchronized更適合於單機環境,可以保證一個伺服器節點上,多個執行緒存取公共資源時,只有一個執行緒能夠拿到那把鎖,其他的執行緒都需要等待。
但實際上我們的系統,大部分是處於分散式環境當中的。
為了保證服務的穩定性,我們一般會把系統部署到兩個以上的伺服器節點上。
後面哪一天有個伺服器節點掛了,系統也能在另外一個伺服器節點上正常執行。
當然也能會出現,一個伺服器節點扛不住使用者請求壓力,也掛掉的情況。
這種情況,應該提前部署3個服務節點。
此外,即使只有一個伺服器節點,但如果你有api和job兩個服務,都會修改某張表的資料。
這時使用synchronized加鎖也會有問題。
因此,在工作中更多的是使用分散式鎖
。
目前比較主流的分散式鎖有:
其實這些方案都有一些使用場景。
目前使用更多的是redis分散式鎖。
當然使用redis分散式鎖也很容易踩坑,感興趣的小夥伴可以看看我的另一篇文章《聊聊redis分散式鎖的8大坑》,裡面有詳細介紹。
不知道你有沒有做過介面的效能優化,其中有一個非常重要的優化手段是:非同步
。
如果我們的某個儲存資料的API介面中的業務邏輯非常複雜,經常出現超時問題。
現在讓你優化該怎麼優化呢?
先從索引,sql語句優化。
這些優化之後,效果不太明顯。
這時該怎麼辦呢?
這就可以使用非同步思想來優化了。
如果該介面的實時性要求不高,我們可以用一張表儲存使用者資料,然後使用job或者mq,這種非同步的方式,讀取該表的資料,做業務邏輯處理。
如果該介面對實效性要求有點高,我們可以梳理一下介面的業務邏輯,看看哪些是核心邏輯,哪些是非核心邏輯。
對於核心邏輯,可以在介面中同步執行。
對於非核心邏輯,可以使用job或者mq這種非同步的方式處理。
有些小夥伴,不太習慣在Git上提交程式碼。
非常勤勞的使用idea,寫了一天的程式碼,最後下班前,準備提交程式碼的時候,電腦突然宕機了。
會讓你欲哭無淚。
用Git提交程式碼有個好習慣是:多次提交。
避免一次性提交太多程式碼的情況。
這樣可以減少程式碼丟失的風險。
更重要的是,如果多個人協同開發,別人能夠儘早獲取你最新的程式碼,可以儘可能減少程式碼的衝突。
假如你開發一天的程式碼準備去提交的時候,發現你的部分程式碼,別人也改過了,產生了大量的衝突。
解決衝突這個過程是很痛苦的。
如果你能夠多次提交程式碼,可能會及時獲取別人最新的程式碼,減少程式碼衝突的發生。因為每次push程式碼之前,Git會先檢查一下,程式碼有沒有更新,如果有更新,需要你先pull一下最新的程式碼。
此外,使用Git提交程式碼的時候,一定要寫好註釋,提交的程式碼實現了什麼功能,或者修復了什麼bug。
如果有條件的話,每次提交時在註釋中可以帶上jira任務的id,這樣後面方便統計工作量。
我們一定要多熟悉一下開源的工具類,真的可以幫我們提升開發效率,避免在工作中重複造輪子。
目前業界使用比較多的工具包有:apache的common,google的guava和國內幾個大佬些hutool。
比如將一個大集合的資料,按每500條資料,分成多個小集合。
這個需求如果要你自己實現,需要巴拉巴拉寫一堆程式碼。
但如果使用google的guava包,可以非常輕鬆的使用:
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
List<List<Integer>> partitionList = Lists.partition(list, 2);
System.out.println(partitionList);
如果你對更多的第三方工具類比較感興趣,可以看看我的另一篇文章《吐血推薦17個提升開發效率的「輪子」》。
我們在學習新知識點的時候,學完了之後,非常容易忘記。
往往學到後面,把前面的忘記了。
回頭溫習前面的,又把後面的忘記了。
因此,建議大家培養做筆記的習慣。
我們可以通過寫技術部落格的方式,來記筆記,不僅可以給學到的知識點加深印象,還能鍛鍊自己的表達能力。
此外,工作中遇到的一些問題,以及解決方案,都可以沉澱到技術部落格中。
一方面是為了避免下次犯相同的錯誤。
另一方面也可以幫助別人少走彎路。
而且,在面試中如果你的簡歷中寫了技術部落格地址,是有一定的加分的。
因此建議大家培養些技術部落格的習慣。
建議大家利用空閒時間,多閱讀JDK、Spring、Mybatis的原始碼。
通過閱讀原始碼,可以真正的瞭解某個技術的底層原理是什麼,這些開源專案有哪些好的設計思想,有哪些巧妙的編碼技巧,使用了哪些優秀的設計模式,可能會出現什麼問題等等。
當然閱讀原始碼是一個很枯燥的過程。
有時候我們會發現,有些原始碼程式碼量很多,繼承關係很複雜,使用了很多設計模式,一眼根本看不明白。
對於這類不太容易讀懂的原始碼,我們不要一口吃一個胖子。
要先找一個切入點,不斷深入,由點及面的閱讀。
我們可以通過debug的方式閱讀原始碼。
在閱讀的過程中,可以通過idea工具,自動生成類的繼承關係,輔助我們更好的理解程式碼邏輯。
我們可以一邊讀原始碼,一邊畫流程圖,可以更好的加深印象。
當然還有很多建議,由於篇幅有限,後面有機會再跟大家分享。
當然還有很多建議,由於篇幅有限,後面有機會再跟大家分享。
最後歡迎大家加入蘇三的知識星球【Java突擊隊】,一起學習。
星球中有很多獨家的乾貨內容,比如:Java後端學習路線,分享實戰專案,原始碼分析,百萬級系統設計,系統上線的一些坑,MQ專題,真實面試題,每天都會回答大家提出的問題。
星球目前開通了6個優質專欄:技術選型、系統設計、Spring原始碼解讀、痛點問題、高頻面試題 和 效能優化。