10年程式設計師,想對新人說什麼?

2023-07-25 06:00:55

前言

最近知乎上,有一位大佬邀請我回答下面這個問題,看到這個問題我百感交集,感觸頗多。

 

 

在我是新人時,如果有前輩能夠指導方向一下,分享一些踩坑經歷,或許會讓我少走很多彎路,節省更多的學習的成本。

這篇文章根據我多年的工作經驗,給新人總結了25條建議,希望對你會有所幫助。

1.寫好註釋

很多小夥伴不願意給程式碼寫註釋,主要有以下兩個原因:

  1. 開發時間太短了,沒時間寫註釋。
  2. 《重構》那本書說程式碼即註釋。

我在開發的前面幾年也不喜歡寫註釋,覺得這是一件很酷的事情。

但後來發現,有些兩年之前的程式碼,業務邏輯都忘了,有些程式碼自己都看不懂。特別是有部分非常複雜的邏輯和演演算法,需要重新花很多時間才能看明白,可以說自己把自己坑了。

沒有註釋的程式碼,不便於維護。

因此強烈建議大家給程式碼寫註釋。

但註釋也不是越多越好,註釋多了增加了程式碼的複雜度,增加了維護成本,給自己增加工作量。

我們要寫好註釋,但不能太囉嗦,要給關鍵或者核心的程式碼增加註釋。我們可以寫某個方法是做什麼的,主要步驟是什麼,給演演算法寫個demo範例等。

這樣以後過了很長時間,再去看這段程式碼的時候,也會比較容易上手。

2.多寫單元測試

我看過身邊很多大佬寫程式碼有個好習慣,比如新寫了某個Util工具類,他們會同時在test目錄下,給該工具類編寫一些單元測試程式碼。

很多小夥伴覺得寫單元測試是浪費時間,沒有這個必要。

假如你想重構某個工具類,但由於這個工具類有很多邏輯,要把這些邏輯重新測試一遍,要花費不少時間。

於是,你產生了放棄重構的想法。

但如果你之前給該工具類編寫了完整的單元測試,重構完成之後,重新執行一下之前的單元測試,就知道重構的結果是否滿足預期,這樣能夠減少很多的測試時間。

多寫單元測試對開發來說,是一個非常好的習慣,有助於提升程式碼質量。

即使因為當初開發時間比較緊,沒時間寫單元測試,也建議在後面空閒的時間內,把單元測試補上。

3.主動重構自己的爛程式碼

好的程式碼不是一下子就能寫成的,需要不斷地重構,修復發現的bug。

不知道你有沒有這種體會,看自己1年之前寫的程式碼,簡直不忍直視。

這說明你對業務或者技術的理解,比之前更深入了,認知水平有一定的提升。

如果有機會,建議你主動重構一下自己的爛程式碼。把重複的程式碼,抽取成公共方法。有些引數名稱,或者方法名稱當時沒有取好的,可以及時修改一下。對於邏輯不清晰的程式碼,重新梳理一下業務邏輯。看看程式碼中能不能引入一些設計模式,讓程式碼變得更優雅等等。

通過程式碼重構的過程,以自我為驅動,能夠不斷提升我們編寫程式碼的水平。

4.程式碼review很重要

有些公司在系統上線之前,會組織一次程式碼評審,一起review一下這個迭代要上線的一些程式碼。

通過相互的程式碼review,可以發現一些程式碼的漏洞,不好的寫法,發現自己寫程式碼的壞毛病,讓自己能夠快速提升。

當然如果你們公司沒有建立程式碼的相互review機制,也沒關係。

可以後面可以多自己review自己的程式碼。

5.多用explain檢視執行計劃

我們在寫完查詢SQL語句之後,有個好習慣是用explain關鍵字檢視一下該SQL語句有沒有走索引

對於資料量比較大的表,走了索引和沒有走索引,SQL語句的執行時間可能會相差上百倍。

我之前親身經歷過這種差距。

因此建議大家多用explain檢視SQL語句的執行計劃。

關於explain關鍵字的用法,如果你想進一步瞭解,可以看看我的另外一篇文章《explain | 索引優化的這把絕世好劍,你真的會用嗎?》,裡面有詳細的介紹。

6.上線前整理checklist

在系統上線之前,一定要整理上線的清單,即我們說的:checklist

系統上線有可能是一件很複雜的事情,涉及的東西可能會比較多。

假如服務A依賴服務B,服務B又依賴服務C。這樣的話,服務發版的順序是:CBA,如果順序不對,可能會出現問題。

有時候新功能上線時,需要提前執行sql指令碼初始化資料,否則新功能有問題。

要先設定定時任務。

上線之前,要在apollo中增加一些設定。

上線完成之後,需要增加相應的選單,給指定使用者或者角色分配許可權。

等等。

系統上線,整個過程中,可能會涉及多方面的事情,我們需要將這些事情記錄到checklist當中,避免踩坑。

7.寫好介面檔案

介面檔案對介面提供者,和介面呼叫者來說,都非常重要。

如果你沒有介面檔案,別人咋知道你介面的地址是什麼,介面引數是什麼,請求方式時什麼,介面多個引數分別程式碼什麼含義,返回值有哪些欄位等等。

他們不知道,必定會多次問你,無形當中,增加了很多溝通的成本。

如果你的介面檔案寫的不好,寫得別人看不懂,介面檔案有很多錯誤,比如:輸入引數的列舉值,跟實際情況不一樣。

這樣不光把自己坑了,也會把別人坑慘。

因此,寫介面檔案一定要寫好,儘量不要馬馬虎虎應付差事。

如果對寫介面檔案比較感興趣,可以看看我的另一篇文章《瞧瞧別人家的API介面,那叫一個優雅》,裡面有詳細的介紹。

8.介面要提前評估請求量

我們在設計介面的時候,要跟業務方或者產品經理確認一下請求量。

假如你的介面只能承受100qps,但實際上產生了1000qps。

這樣你的介面,很有可能會承受不住這麼大的壓力,而直接掛掉。

我們需要對介面做壓力測試,預估介面的請求量,需要部署多少個伺服器節點。

壓力測試的話,可以用jmeter、loadRunner等工具。

此外,還需要對介面做限流,防止別人惡意呼叫你的介面,導致伺服器壓力過大。

限流的話,可以基於使用者id、ip地址、介面地址等多個維度同時做限制。

可以在nginx層,或者閘道器層做限流。

9.介面要做冪等性設計

我們在設計介面時,一定要考慮並行呼叫的情況。

比如:使用者在前端頁面,非常快的點選了兩次儲存按鈕,這樣就會在極短的時間內呼叫你兩次介面。

如果不做冪等性設計,在資料庫中可能會產生兩條重複的資料。

還有一種情況時,業務方呼叫你這邊的介面,該介面發生了超時,它有自動重試機制,也可能會讓你這邊產生重複的資料。

因此,在做介面設計時,要做冪等設計。

當然冪等設計的方案有很多,感興趣的小夥伴可以看看我的另一篇文章《高並行下如何保證介面的冪等性?》。

如果介面並行量不太大,推薦大家使用在表中加唯一索引的方案,更加簡單。

10.介面引數有調整一定要慎重

有時候我們提供的介面,需要調整引數。

比如:新增加了一個引數,或者引數型別從int改成String,或者引數名稱有status改成auditStatus,引數由單個id改成批次的idList等等。

建議涉及到介面引數修改一定要慎重。

修改介面引數之前,一定要先評估呼叫端和影響範圍,不要自己偷偷修改。如果出問題了,呼叫方後面肯定要罵娘。

我們在做介面引數調整時,要做一些相容性的考慮。

其實刪除引數和修改引數名稱是一個問題,都會導致那個引數接收不到資料。

因此,儘量避免刪除引數和修改引數名。

對於修改引數名稱的情況,我們可以增加一個新引數,來接收資料,老的資料還是保留,程式碼中做相容處理。

11.呼叫第三方介面要加失敗重試

我們在呼叫第三方介面時,由於存在遠端呼叫,可能會出現介面超時的問題。

如果介面超時了,你不知道是執行成功,還是執行失敗了。

這時你可以增加自動重試機制

介面超時會拋一個connection_timeout或者read_timeout的異常,你可以捕獲這個異常,用一個while迴圈自動重試3次。

這樣就能儘可能減少呼叫第三方介面失敗的情況。

當然呼叫第三方介面還有很多其他的坑,感興趣的小夥伴可以看看我的另一篇文章《我呼叫第三方介面遇到的13大坑》,裡面有詳細的介紹。

12.處理線上資料前,要先備份資料

有時候,線上資料出現了問題,我們需要修復資料,但涉及的資料有點多。

這時建議在處理線上資料前,一定要先備份資料

備份資料非常簡單,可以執行以下sql:

create table order_2022121819 like `order`;
insert into order_2022121819 select * from `order`;

資料備份之後,萬一後面哪天資料處理錯了,我們可以直接從備份表中還原資料,防止悲劇的產生。

13.不要輕易刪除線上欄位

不要輕易刪除線上欄位,至少我們公司是這樣規定的。

如果你刪除了某個線上欄位,但是該欄位參照的程式碼沒有刪除乾淨,可能會導致程式碼出現異常。

假設開發人員已經把程式改成不使用刪除欄位了,接下來如何部署呢?

如果先把程式部署好了,還沒來得及刪除資料庫相關表欄位。

當有insert請求時,由於資料庫中該欄位是必填的,會報必填欄位不能為空的異常。

如果先把資料庫中相關表欄位刪了,程式還沒來得及發。這時所有涉及該刪除欄位的增刪改查,都會報欄位不存在的異常。

所以,線上環境欄位不要輕易刪除。

14.要合理設定欄位型別和長度

我們在設計表的時候,要給相關欄位設定合理的欄位型別和長度。

如果欄位型別和長度不夠,有些資料可能會儲存失敗。

如果欄位型別和長度太大了,又會浪費儲存空間。

我們在工作中,要根據實際情況而定。

以下原則可以參考一下:

  • 儘可能選擇佔用儲存空間小的欄位型別,在滿足正常業務需求的情況下,從小到大,往上選。
  • 如果字串長度固定,或者差別不大,可以選擇char型別。如果字串長度差別較大,可以選擇varchar型別。
  • 是否欄位,可以選擇bit型別。
  • 列舉欄位,可以選擇tinyint型別。
  • 主鍵欄位,可以選擇bigint型別。
  • 金額欄位,可以選擇decimal型別。
  • 時間欄位,可以選擇timestamp或datetime型別。

15.避免一次性查詢太多資料

我們在設計介面,或者呼叫別人介面的時候,都要避免一次性查詢太多資料。

一次性查詢太多的資料,可能會導致查詢耗時很長,更加嚴重的情況會導致系統出現OOM的問題。

我們之前呼叫第三方,查詢一天的指標資料,該介面經常出現超時問題。

在做excel匯出時,如果一次性查詢出所有的資料,匯出到excel檔案中,可能會導致系統出現OOM問題。

因此我們的介面要做分頁設計

如果是呼叫第三方的介面批次查詢介面,儘量分批呼叫,不要一次性根據id集合查詢所有資料。

如果呼叫第三方批次查詢介面,對效能有一定的要求,我們可以分批之後,用多執行緒呼叫介面,最後彙總返回資料。

16.多執行緒不一定比單執行緒快

很多小夥伴有一個誤解,認為使用了多執行緒一定比使用單執行緒快。

其實要看使用場景。

如果你的業務邏輯是一個耗時的操作,比如:遠端呼叫介面,或者磁碟IO操作,這種使用多執行緒比單執行緒要快一些。

但如果你的業務邏輯非常簡單,在一個迴圈中列印資料,這時候,使用單執行緒可能會更快一些。

因為使用多執行緒,會引入額外的消耗,比如:建立新執行緒的耗時,搶佔CPU資源時執行緒上下文需要不斷切換,這個切換過程是有一定的時間損耗的。

因此,多執行緒不一定比單執行緒快。我們要根據實際業務場景,決定是使用單執行緒,還是使用多執行緒。

17.注意事務問題

很多時候,我們的程式碼為了保證資料庫多張表儲存資料的完整性和一致性,需要使用@Transactional註解的宣告式事務,或者使用TransactionTemplate的程式設計式事務。

加入事務之後,如果A,B,C三張表同時儲存資料,要麼一起成功,要麼一起失敗。

不會出現資料儲存一半的情況,比如:表A儲存成功了,但表B和C儲存失敗了。

這種情況資料會直接回滾,A,B,C三張表的資料都會同時儲存失敗。

如果使用@Transactional註解的宣告式事務,可能會出現事務失效的問題,感興趣的小夥伴可以看看我的另一篇文章《聊聊spring事務失效的12種場景,太坑了》。

建議優先使用TransactionTemplate的程式設計式事務的方式建立事務。

此外,引入事務還會帶來大事務問題,可能會導致介面超時,或者出現資料庫死鎖的問題。

因此,我們需要優化程式碼,儘量避免大事務的問題,因為它有許多危害。關於大事務問題,感興趣的小夥伴,可以看看我的另一篇文章《讓人頭痛的大事務問題到底要如何解決?》,裡面有詳情介紹。

18.小數容易丟失精度

不知道你在使用小數時,有沒有踩過坑,一些運算導致小數丟失了精度。

如果你在專案中使用了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型別引數,能保證精度不丟失。

19.優先使用批次操作

有些小夥伴可能寫過這樣的程式碼,在一個for迴圈中,一個個呼叫遠端介面,或者執行資料庫的update操作。

其實,這樣是比較消耗效能的。

我們儘可能將在一個迴圈中多次的單個操作,改成一次的批次操作,這樣會將程式碼的效能提升不少。

例如:

for(User user : userList) {
   userMapper.update(user);
}

改成:

userMapper.updateForBatch(userList);

20.synchronized其實用的不多

我們在面試中當中,經常會被面試官問到synchronized加鎖的考題。

說實話,synchronized的鎖升級過程,還是有點複雜的。

但在實際工作中,使用synchronized加鎖的機會不多。

synchronized更適合於單機環境,可以保證一個伺服器節點上,多個執行緒存取公共資源時,只有一個執行緒能夠拿到那把鎖,其他的執行緒都需要等待。

但實際上我們的系統,大部分是處於分散式環境當中的。

為了保證服務的穩定性,我們一般會把系統部署到兩個以上的伺服器節點上。

後面哪一天有個伺服器節點掛了,系統也能在另外一個伺服器節點上正常執行。

當然也能會出現,一個伺服器節點扛不住使用者請求壓力,也掛掉的情況。

這種情況,應該提前部署3個服務節點。

此外,即使只有一個伺服器節點,但如果你有api和job兩個服務,都會修改某張表的資料。

這時使用synchronized加鎖也會有問題。

因此,在工作中更多的是使用分散式鎖

目前比較主流的分散式鎖有:

  1. 資料庫悲觀鎖。
  2. 基於時間戳或者版本號的樂觀鎖。
  3. 使用redis的分散式鎖。
  4. 使用zookeeper的分散式鎖。

其實這些方案都有一些使用場景。

目前使用更多的是redis分散式鎖。

當然使用redis分散式鎖也很容易踩坑,感興趣的小夥伴可以看看我的另一篇文章《聊聊redis分散式鎖的8大坑》,裡面有詳細介紹。

21.非同步思想很重要

不知道你有沒有做過介面的效能優化,其中有一個非常重要的優化手段是:非同步

如果我們的某個儲存資料的API介面中的業務邏輯非常複雜,經常出現超時問題。

現在讓你優化該怎麼優化呢?

先從索引,sql語句優化。

這些優化之後,效果不太明顯。

這時該怎麼辦呢?

這就可以使用非同步思想來優化了。

如果該介面的實時性要求不高,我們可以用一張表儲存使用者資料,然後使用job或者mq,這種非同步的方式,讀取該表的資料,做業務邏輯處理。

如果該介面對實效性要求有點高,我們可以梳理一下介面的業務邏輯,看看哪些是核心邏輯,哪些是非核心邏輯。

對於核心邏輯,可以在介面中同步執行。

對於非核心邏輯,可以使用job或者mq這種非同步的方式處理。

22.Git提交程式碼要有好習慣

有些小夥伴,不太習慣在Git上提交程式碼。

非常勤勞的使用idea,寫了一天的程式碼,最後下班前,準備提交程式碼的時候,電腦突然宕機了。

會讓你欲哭無淚。

用Git提交程式碼有個好習慣是:多次提交。

避免一次性提交太多程式碼的情況。

這樣可以減少程式碼丟失的風險。

更重要的是,如果多個人協同開發,別人能夠儘早獲取你最新的程式碼,可以儘可能減少程式碼的衝突。

假如你開發一天的程式碼準備去提交的時候,發現你的部分程式碼,別人也改過了,產生了大量的衝突。

解決衝突這個過程是很痛苦的。

如果你能夠多次提交程式碼,可能會及時獲取別人最新的程式碼,減少程式碼衝突的發生。因為每次push程式碼之前,Git會先檢查一下,程式碼有沒有更新,如果有更新,需要你先pull一下最新的程式碼。

此外,使用Git提交程式碼的時候,一定要寫好註釋,提交的程式碼實現了什麼功能,或者修復了什麼bug。

如果有條件的話,每次提交時在註釋中可以帶上jira任務的id,這樣後面方便統計工作量。

23.善用開源的工具類

我們一定要多熟悉一下開源的工具類,真的可以幫我們提升開發效率,避免在工作中重複造輪子。

目前業界使用比較多的工具包有: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個提升開發效率的「輪子」》。

24.培養寫技術部落格的好習慣

我們在學習新知識點的時候,學完了之後,非常容易忘記。

往往學到後面,把前面的忘記了。

回頭溫習前面的,又把後面的忘記了。

因此,建議大家培養做筆記的習慣。

我們可以通過寫技術部落格的方式,來記筆記,不僅可以給學到的知識點加深印象,還能鍛鍊自己的表達能力。

此外,工作中遇到的一些問題,以及解決方案,都可以沉澱到技術部落格中。

一方面是為了避免下次犯相同的錯誤。

另一方面也可以幫助別人少走彎路。

而且,在面試中如果你的簡歷中寫了技術部落格地址,是有一定的加分的。

因此建議大家培養些技術部落格的習慣。

25.多閱讀優秀原始碼

建議大家利用空閒時間,多閱讀JDK、Spring、Mybatis的原始碼。

通過閱讀原始碼,可以真正的瞭解某個技術的底層原理是什麼,這些開源專案有哪些好的設計思想,有哪些巧妙的編碼技巧,使用了哪些優秀的設計模式,可能會出現什麼問題等等。

當然閱讀原始碼是一個很枯燥的過程。

有時候我們會發現,有些原始碼程式碼量很多,繼承關係很複雜,使用了很多設計模式,一眼根本看不明白。

對於這類不太容易讀懂的原始碼,我們不要一口吃一個胖子。

要先找一個切入點,不斷深入,由點及面的閱讀。

我們可以通過debug的方式閱讀原始碼。

在閱讀的過程中,可以通過idea工具,自動生成類的繼承關係,輔助我們更好的理解程式碼邏輯。

我們可以一邊讀原始碼,一邊畫流程圖,可以更好的加深印象。

當然還有很多建議,由於篇幅有限,後面有機會再跟大家分享。

 

當然還有很多建議,由於篇幅有限,後面有機會再跟大家分享。

最後歡迎大家加入蘇三的知識星球【Java突擊隊】,一起學習。

星球中有很多獨家的乾貨內容,比如:Java後端學習路線,分享實戰專案,原始碼分析,百萬級系統設計,系統上線的一些坑,MQ專題,真實面試題,每天都會回答大家提出的問題。

星球目前開通了6個優質專欄:技術選型、系統設計、Spring原始碼解讀、痛點問題、高頻面試題 和 效能優化。