暫無名待修改

竹木一540發表於2020-10-08


前言

一六年,我參與了一個我有生以來的最高大尚的專案,我在專案中負責某功能計費模組的開發,儘管總個專案投入了很多人力,還有引進了很多先進的工具和管理方式,但那還是我有生以來見過最糟的專案之一
一七年,我去朋友的創業公司,幫他組建團隊,一九年至今年,我於一家物流公司擔任架構方面的工作,在開發中,我常見那種寫業務能力很強,但程式碼細節慘不忍睹的開發者,於是我想整理一些我見過的程式碼片斷,並提供一些改進意見


提示:以下是本篇文章正文內容,下面案例可供參考

一.error日誌

對異常catch後的處理已經是老生常談的話題了,不能什麼都不做,也不能用e.printStackTrace()直接列印。下面的程式碼是用slf4j列印日誌:

try{
.....
}catch(XXException e){
	logger.error("send data to XX  failed: {}",e);
}
上面面程式碼看起來沒問題吧,但其實上面的日誌在很多情況下並不能列印出正常的堆疊資訊,在日誌中用到佔位符是受到了其它級別日誌的影響。 我們可以看一下文件中error幾個方法的定義:
/**
     * Log a message at the ERROR level.
     *
     * @param msg the message string to be logged
     */
    public void error(String msg);

    /**
     * Log a message at the ERROR level according to the specified format
     * and argument.
     * <p/>
     * <p>This form avoids superfluous object creation when the logger
     * is disabled for the ERROR level. </p>
     *
     * @param format the format string
     * @param arg    the argument
     */
    public void error(String format, Object arg);

    /**
     * Log a message at the ERROR level according to the specified format
     * and arguments.
     * <p/>
     * <p>This form avoids superfluous object creation when the logger
     * is disabled for the ERROR level. </p>
     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void error(String format, Object arg1, Object arg2);

    /**
     * Log a message at the ERROR level according to the specified format
     * and arguments.
     * <p/>
     * <p>This form avoids superfluous string concatenation when the logger
     * is disabled for the ERROR level. However, this variant incurs the hidden
     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,
     * even if this logger is disabled for ERROR. The variants taking
     * {@link #error(String, Object) one} and {@link #error(String, Object, Object) two}
     * arguments exist solely in order to avoid this hidden cost.</p>
     *
     * @param format    the format string
     * @param arguments a list of 3 or more arguments
     */
    public void error(String format, Object... arguments);  		//(1)適用於佔位符,

    /**
     * Log an exception (throwable) at the ERROR level with an
     * accompanying message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void error(String msg, Throwable t);					//(2)天生就有列印異常資訊的本事	

可以看到error也有多參呼叫(1),所以用佔位符也沒問題,但在呼叫時,會呼叫引數的toString方法,我看到很多error日誌後面出現了一串莫名奇妙的記憶體地址,為了正確列印堆疊資訊,竟然大動干戈,弄了一個工具類,返回error的堆疊資訊。
大可不必如此,eror有一個過載方法天生就是用來列印Exception資訊,見2。但呼叫時我們不應該在msg欄位中出現佔位符。
所以只需要:

logger.error("send data to XX  failed: {}",e)

二 裝箱與拆箱

void funa(Boolean flag){
	if(flag){
		....
	}
}

上面的程式碼看上去非常ok。當flag為null時,會報空指標異常。
某天夜裡,我接到公司的緊急電話,公司裡的pda掃碼上傳出現異常,而PDA(Android)的程式碼也從沒有人動過。我追蹤程式碼到錯誤處,以下是程式碼片斷。

List<Scan> list = .... //這是呼叫服務得到的資料
for(Scan s : list){
	SaveEndtity e = new SaveEntity();
	e.setCout(s.getCount);			//這裡出現了異常
	....
}

這是一個最簡單的拷貝操作,如果不支看兩個類的程式碼,你怎麼也想不到問題會出在哪裡。

class SaveEndtity{
	int count;
}

class Scan{
	Integer count;
} 

僅僅是兩個類,一個用了原始型別,一個用了包裝型別,就在特定條件下,產生了一場災難。


提示:上面的兩個示例是我在上家工作時發現很多程式碼中存在的bug.以下我所舉的示例不能算是bug,但它確實讓我不爽

三 沒完沒了的字串

interface UserService{
	.....
	List<User> queryByAge(String age);
	String getTotalCount(...)
	List<> query(String pageSize,String pageNo)
}

我曾在一家名企見過很多類似的程式碼,當我在上家公司見到更多的是這樣

String userStr = restTemplate.get("http://XXX/getUser?id="+id,String.class)
User u = JsonObject.readValue(userString.User.class);
.... 

andoid端可能有如下東西

inteface UserService{
	@Get("/getUser")
	Observerable<String> find(String userName);	//宣告式遠端介面呼叫	
}

//使用時

userService.get("wzp").subscribe((userStr)->{
		User u = Json.readObject(userStr,User.class);
		...
})
很多高階語言的程式設計師都非常迷戀字串,覺得字串就是erverything。恨不得把類裡的所有欄位型別都設成字串,或資料庫裡的所有欄位都統一用varchar。或者有些程式設計師,必需通過字串才能找到安全感。這真悲哀。

四 過多的巢狀

if(a==b){
	if(check()){
		doThings();
	}
}else{
	if(c == d){
		doThings();//與上面的doThings是同一個方法
	}
}

以上程式碼包括括號總共8行,但它其實與下面三行程式碼等價:

if((a==b && check()) || c==d){
	doThings();
}
離散數學中的什麼條件推導的我幾乎忘了,但我想有時候我們可以不用高深的理論我們也可以寫出比較優雅的程式碼。只要我們留心就行了。 以下程式碼片斷我在code review時經常見到
Result login(String userName,String password){
		if(StringUtils.isBlank(userName)){
			throw new  LongException();
		}else if(StringUtils.isBlank(password)){
			throw new  LongException();;	
		}else{
			User user=	dao.finduser(username)
			if(user == null){
				return new result(false);
			}else{
					....
			}
		}

}
以上程式碼還是比較簡單的,還有比這更復雜的。我曾見過有人在方法裡面拋了個異常,然又在同一個方法裡try這個異常,我看的是一頭霧水,都不知道目的何在。其實寫程式就像是寫作文,要掌握主幹,如果我們圍繞主幹去寫,然後再添枝加葉或許會更好一些,比如一寫程式碼主要功能是查詢使用者並檢查使用者名稱密碼是正正確然後返回結果。

第一步:

Result login(String userName,String password){
		
	Result r =new Result();
	return r;
}

第二 步:

Result login(String userName,String password){


	Result r =new Result();
	User user = dao.queryUser(userName)
	if(user== null){
		r.setRsult(fale)
	}else if(!checkpassword)){
		r.setResult(false)
	}else (r.setResult(true))
	return r;
	
}

第三 步:

Result login(String userName,String password){

	if(StringUtils.isBlank(userName)){
			throw new  LongException();
	}
	if(StringUtils.isBlank(password)){
			throw new  LongException();;	
	}

	Result r =new Result();
	User user = dao.queryUser(userName)
	if(user== null){
		r.setRsult(fale)
	}else if(!checkpassword)){
		r.setResult(false)
	}else {
		r.setResult(true)
	}
	return r;
	
}

我並沒有用線性的方式增編寫程式碼,我更喜歡首先關注業務主體,然後再去新增細節。而且我也少用了很多巢狀。在if中已丟擲了異常,else 就沒有必要使用了。我們的主要業務邏輯沒有巢狀在某個if中,這樣我們可以把驗證移到其它方法中了。

五 亂拷貝

六 引用了不該引用的jar包。

相關文章