java变量命名规则_浅谈JAVA开发规范与开发细节(上)
開發團隊在開發過程中,由于每個人的開發習慣,以及對于技術的理解深淺程度不一,往往一個項目在開發過程中,代碼的質量,代碼的風格都不盡相似,所以有一份適合團隊的代碼規范是非常有必要的,而一個團隊的代碼規范,包含了開發常見的風格習慣以及一些常見代碼細節的寫法規范等,本篇就來淺談一些代碼規范涉及的技術細節和對這些部分的思考。
命名規范
相信經歷過項目開發的人都知道,在開發過程中會涉及無數次的申明操作,這個過程中最讓人頭疼的就是給申明的文件(實例)起個名字了。名字需要準確的表達出背后代表的含義,并且還要通俗易懂,使得代碼干凈漂亮,否則不好的命名反而成為開發的阻礙,干擾維護者和開發者的思路。那么一個好的命名會給我們開發帶來什么好處呢?
為標識符提供附加的信息,賦予標識符現實意義。幫助我們理順編碼的邏輯,減少閱讀和理解代碼的工作量;
使代碼審核變得更有效率,專注于更重要的問題,而不是爭論語法和命名規范這類小細節,提高開發效率;
提高代碼的清晰度、可讀性以及美觀程度;
避免不同產品之間的命名沖突。
那么常見的命名方式有哪些呢?根據各大規范和Java框架主流的方案來說,一般分為四種:
駝峰命名法
駝峰命名法基本上是各大企業使用最多,也是各大規范首推的命名方式。其使用大小寫混合的格式,單詞之間不使用空格隔開或者連接字符連接的命名方式,因此發展處兩種格式:大駝峰命名法(UpperCamelCase)和小駝峰命名法(lowerCamelCase)
這兩種命名方式的區別主要在第一個單詞的首字母上,大駝峰命名法則是首字母大寫命名,而小駝峰則是首個單詞的首字母小寫,比如:firstName, toString等。在jdk中參照了谷歌制定的駝峰命名轉換規則,用來細分不同情況下的駝峰轉換:
1.從正常的表達形式開始,把短語轉換成 ASCII 碼,并且移除單引號,如“Müller’s algorithm”轉換為“Muellers algorithm”
2.如果存在連接符號,就將連接符開始分割為兩個單詞,如果某個分割前某個單詞已經是駝峰命名,也拆分為小寫的兩個單詞,如:AdWords會轉換為ad words,而non-current則轉換為non current 等
3.將所有的字母轉為小寫字母,每個單詞的首字母大寫,就轉換為了大駝峰命名/首字母小寫則轉為小駝峰
4.將所有的單詞連接在一起,即為標示符命名
例如下面的轉換案例:
蛇形命名法
蛇形命名法在Java中極少見到,一般為每個單詞之間都通過‘’進行連接,例如‘outof’
串式命名法
串式命名法和蛇形規則一樣,唯一區別是,每個單詞之間通過'-'連接,例如'out-of'
匈牙利命名法
匈牙利命名法在Java早期的框架中開始出現,由一個或者多個小寫字母開始,使用這些字母作為標示符,用來標記當前命名的變量的用途,例如:usName(表示是用戶的名稱),lAccountNum(表示是Long類型的長整數)等
而在jdk中,針對每一種類型的命名有特定的規范,針對每一種編碼規范來組合使用在不同場景的命名中,如下:
總結下來,jdk命名遵循了三點:
1.命名有準確的意義,絕不使用單詞縮寫或者單詞的部分,例如GoodsItem,絕不會命名為GdItem
2.嚴格遵守命名規范,決不允許一個規則內出現多個規范混用的情況,例如在一個命名中同時出現駝峰命名與蛇形命名等
3.盡量將可讀性的命名放在前面,開發者的習慣一般都是從左到右開始閱讀和編碼,所以將能體現出想要的信息的內容優先放在前面,例如BeijingTime和TimeBeijing的區別
變量申明的時機
前面我們說過命名的規范,那么申明變量是否也需要規范呢?其實也需要,例如現在申明一個類型的變量的時候,往往有人喜歡一個類型的變量在一行內申明完畢,例如:
int size, length;
甚至于出現了一行申明了七八個屬性的情況,或者是在一行內申明了好幾個類型的變量,例如:
int size,entity[];//一行申明多個不同類型變量
看起來代碼似乎節省了,但是對于開發和維護來說,其實反而更容易忽略錯誤,更重要的是申明類型是數組的時候不要把基本類型和[]分開,因為int[] 才是代表了一個類型的整體,分開申明容易被忽略,或者埋下隱患的錯誤,所以往往建議每一行僅申明一個變量,如下:
int size;
int[] entity;
在開發中往往還存在另外一個情況,就是方法內申明局部變量的時候,往往喜歡在方法開始的時候就創建或者申明該變量,但是使用的時機往往在n行代碼以后,甚至于到后面這個申明的變量并沒有使用到,由于間隔太遠,也沒有關注,后面就成了一個死變量,這種情況是很多見的,而反觀jdk的規范中,可以看到都是在需要使用變量的時候創建,或者在需要使用的前幾行代碼申明再去創建,例如:
public void test(String userName){
Account userAccount;
String groceryStoreName;
//中間一堆業務代碼和操作
/*****
****
***/
//通過用戶名獲取userAccount
userAccount = AccountManager.getUserAccount(userName);
if(userAccount == null){
//為null的操作,拋異常
}
//再去獲取名稱
groceryStoreName = userAccount.getGroceryStoreName();
if(groceryStoreName == null){
//為null,拋異常
}
//后續一堆業務代碼
}
但是我們看下規范后的寫法:
public void test(String userName){
//中間一堆業務代碼和操作
/*****
****
***/
//通過用戶名獲取userAccount
Account userAccount = AccountManager.getUserAccount(userName);
if(userAccount == null){
//為null的操作,拋異常
}
//再去獲取名稱
String groceryStoreName = userAccount.getGroceryStoreName();
if(groceryStoreName == null){
//為null,拋異常
}
//后續一堆業務代碼
}
很明顯的可以看出來,代碼更清晰明了,也更有邏輯性。另外在申明類屬性變量的時候,我們建議將變量申明在一起,分塊存放,不建議在類中變量和方法混合在一起使用,例如:
另外在申明類變量的時候,切記不要忘記類變量如果是基礎類型,會有默認值,如非必要,在類屬性創建中建議使用包裝類型,防止因默認值帶來的數據不一致等問題,而在方法內創建局部變量的時候,由于基本類型變量沒有默認值,需要手動申明值,反而建議使用基本類型,而不是使用包裝類,這樣同樣也可以盡量避免無意的拆箱、裝箱行為,在數十萬次百萬次的情況下,對于程序也會造成一定的影響。
if與大括號
if語句是我們開發中最常見的邏輯分支語句之一,同樣的在java中if也會有一些簡潔寫法,例如邏輯業務僅有一行代碼的時候,我們可以省去大括號,直接在if下一行編寫業務代碼,如下:
if(flag)
count ++;
//if以外的邏輯
user.setAge(10);
......
但是熟悉規范的都知道,無論是阿里規范還是jdk的規范,都不推薦使用簡化代碼,這是為什么呢?這讓我想起了2014年蘋果的ios系統爆出來的一個嚴重安全漏洞(“GoTo Fail 漏洞”),而這個漏洞就和大括號有關系,而對應漏洞的代碼大概可以理解為這樣:
if ((error = doSomething()) != 0)
goto fail;
//無論如何都是走到這里,下面再也觸發不了了
goto fail;
if ((error= doMore()) != 0)
goto fail;
fail:
return error;
是不是看出來什么了?沒錯,如果前面的條件生效,就會跳轉到fail的操作,返回error,但是如果不滿足也會跳轉到fail,那么也就是說后續的業務代碼無論如何也觸發不了了,其實了解這個問題的人其實大概可以猜出來,這里就是多寫了一個goto fail;導致編譯器認為了別的業務代碼,但是假設我們加了大括號,這個問題就會迎刃而解,例如:
if ((error = doSomething()) != 0)
{
goto fail;
//無論如何都是走到這里,下面再也觸發不了了
goto fail;
}
if ((error= doMore()) != 0)
goto fail;
fail:
return error;
其實這個時候就會發現即使是多寫了一行代碼,也不會影響整個業務的邏輯,減少了bug產生。看到這里我們似乎明白了,為什么各大規范都建議不省略大括號的寫法了。
包裝類與基本類型
做Java開發的都知道,Java中默認有八種基本類型,但是同樣的也有八種對應的包裝類型,很多時候企業開發和使用的時候對于包裝類型和基本類型的使用并不規范,往往會導致一部分小的隱患的發生。前面我們有介紹建議在類屬性申明的時候使用包裝類型,而在方法內建議使用基本類型,這里我們可以再去思考兩個開發的時候常用的使用場景:
1.判斷兩個數值類型的值是否相等
2.創建數值類型
看過阿里手冊和JDK規范的應該知道,里面都有一條規范,明確指出基本數值類型的包裝類型在比較的時候不允許使用==的方式,而是使用equals,這是為什么呢?我們來看看一個例子:
Integer a = 100, b = 100, c = 150, d = 150;
System.out.println(a == b);//true
System.out.println(c == d);//false
可以看到兩個Integer類型的變量,值一樣的情況下,==比較的結果居然是false?我們通過斷點的方式知道 Integer var = ? 形式聲明變量,會通過?java.lang.Integer#valueOf(int)?來構造 Inte ger 對象,我們來看看valueOf方法的源碼:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到,會去判斷value的值是否在IntegerCache的范圍內,如果在,就會使用IntegerCache中緩存的實例,不存在才會創建新的Integer實例,這個緩存的值,默認是-128到127之間,并且是可以通過配置環境變量的方式動態改變的,這點可以從IntegerCache源碼中看到:
privatestaticclassIntegerCache{
staticfinalint low = -128;
staticfinalint high;
staticfinalInteger cache[];
static{
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
// 省略其它代碼
}
// 省略其它代碼
}
從這我們也可以看出問題2的答案,為什么很多規范都推薦構建實例的時候是Integer a = 5;的形式,而不是new Integer(5);的原因,可以減少實例的創建,復用緩存對象。接著我們再來看第一個問題,==比較和equals比較的區別在哪?我們知道==比較的是兩個實例對象的內存地址,而equals則是比較的具體的實現,而基本類型的包裝類實現實例如果不在緩存范圍內,肯定不是同一個對象,邏輯上內存地址肯定是不一樣的,所以==在超過緩存范圍后,比較的結果并不準確,那么我們該如何比較呢?事實上,基本類型的包裝類中都有獲取具體value的方法,例如Integer中就有intValue的方法,獲取具體的值,類型為基本類型,這樣我們再去==比較就可以了,那么equlas方法為什么可以比較呢?我們就拿Long類型的equals方法的源碼來看一下具體實現:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
可以看到這類包裝類型的比較其實也就是我們上述說的獲取具體value值以后再去==比較的操作。
空指針
空指針基本是每個Java開發人員最惡心的異常也是見過最多的異常之一,可能出現在各種業務代碼和場景中,在阿里規范手冊中,有很多針對空指針的規范和處理,如下:
【強制】Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。【推薦】防止 NPE,是程序員的基本修養,注意 NPE 產生的場景:
返回類型為基本數據類型,return 包裝數據類型的對象時,自動拆箱有可能產生 NPE。反例:public int f () { return Integer 對象}, 如果為 null,自動解箱拋 NPE。
數據庫的查詢結果可能為 null。
集合里的元素即使 isNotEmpty,取出的數據元素也可能為 null。
遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE。
對于 Session 中獲取的數據,建議進行 NPE 檢查,避免空指針。
級聯調用 obj.getA ().getB ().getC (); 一連串調用,易產生 NPE。
可見空指針出現的場景可能會有很多,而在開發中一些必要的檢查,減少空指針是每個程序員都應該有的素質,但是有些不規范的操作或者疏忽可能會導致空指針的誕生,例如:
1.服務交互信息不規范的坑:一個經典的接口服務交互的場景下,往往有時候會將服務中的異常進行try-catch處理,返回的是一個固定的result封裝實例,這種情況下,如果內部屬性設置不規范很容易在調用方使用返回實例進行操作的時候因為疏忽導致空指針異常。
2.返回實例的坑:還有一些服務的代碼編寫過程中,部分開發人員有自己的個性寫法,例如數據庫查詢某個數據的時候,如果查詢不出結果集,并不是返回null,而是創建一個空的實例,進行返回,這一下可好,調用方無論怎么校驗空指針都會在使用getxxx方法獲取到的屬性進行操作的時候報空指針異常,除非調用方將內部所有的get返回的結果都去進行一次空指針判斷,或者根據某幾個唯一屬性確認實例是否為空等,但無論如何操作,都無法避免可能存在的大量的空指針。
3.自動拆箱裝箱的坑:在企業開發的過程中,往往存在大量的實例轉換操作,這個時候我們往往是通過工具類進行轉換,但是有時候我們的實例是存在于兩個工程內的,往往有時候因為是兩個人定義的,同樣名稱的類變量,但是類型一個是基礎類型,一個是包裝類型,這個時候往往我們下意識會覺得java會自動拆箱裝箱,所以沒關系的,肯定會轉換過去的,再或者基本類型有默認值的,肯定不會出現空指針,想法很美好,但是事實真的如此嗎?我們看一個例子:
@Data
public class GoodCreateDTO {
private String title;
private Long price;
private Long count;
}
@Data
public class GoodCreateParam implements Serializable {
private static final long serialVersionUID = -560222124628416274L;
private String title;
private long price;
private long count;
}
這個時候我們潛意識中會認為外部接口的變量都是包裝類型或者引用類型,所以我們在實現了類似如下的轉換代碼的時候就容易出現空指針操作:
public class GoodCreateConverter {
public static GoodCreateParam convertToParam(GoodCreateDTO goodCreateDTO) {
if (goodCreateDTO == null) {
return null;
}
GoodCreateParam goodCreateParam = new GoodCreateParam();
//賦值操作
goodCreateParam.setTitle(goodCreateDTO.getTitle());
goodCreateParam.setPrice(goodCreateDTO.getPrice());
goodCreateParam.setCount(goodCreateDTO.getCount());
return goodCreateParam;
}
}
但是如果在傳遞來的實例中,count不是必傳參數,可能存在null的時候,這個時候我們使用getCount操作,由于獲取的類型是包裝類型,而我們需要賦值的是基本類型,這個時候就會觸發自動拆箱裝箱,null的拆箱就會報空指針異常!
往期精選
CHOICENESS
是兄弟,就來“kan”我總結
以上是生活随笔為你收集整理的java变量命名规则_浅谈JAVA开发规范与开发细节(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Excel数据分析案例一——业绩达成分析
- 下一篇: 电脑技巧:分享七个解决烦人的弹窗广告的小