自下向上的編寫容易閱讀的代碼方法(下 )

我在 關於極簡編程的思考 中曾提到要編寫可閱讀的代碼。由於代碼是編寫一次,閱讀屢次。 閱讀者包括代碼編寫者,以及後來的維護人員。能讓閱讀代碼更輕鬆,有利於加強項目或者產品的可維護性。前端

本博客分爲上下倆部分,第一部分講解在代碼層次 編寫可閱讀的代碼,參考地址是https://my.oschina.net/xiandafu/blog/1509679java

這一部分講解方法,類,以及一些設計上的考慮,這些考慮並非來自於某些設計原則或者是設計模式,而是基於對象的職責,將在下面會講述sql

發現對象

在上半部分,咱們講到一個解析excel的例子,在我實際項目裏,曾經是這個樣子編程

public void parse(Sheet sheet,StringBuilder error){

User user = readUserInfo(sheet,error);
List<Order> orders = readUserOrderInfo(sheet,error);
UserCredit credit = readUserCreditInfo(sheet,error);
    
}

之因此提供一個StringBuilder 參數,是由於需求是若是解析出錯,須要顯示出錯的的位置,項目開發人員所以將錯誤信息拼接成字符串,最後返回給前端。設計模式

若是審查其實現,你會發現該解析方法處處都是相似以下代碼api

error.append("在"+line+"和」+col+「列錯":+"messsage).append("\n");

這兩段代碼的閱讀者困惑之處就是error定義不能說明如何處理解析錯誤,閱讀者不得不看清楚具體實現才恍然大悟--原來個人前任用StringBuilder是想這麼幹。另一個困惑之處就是在解析excel的時候,就已經寫死了錯誤輸出的樣子,若是想更改,就須要改每一處地方 ,咱們知道業務的excel解析,幾百行代碼算是少的了。要閱讀者幾百行代碼重構對後來者並不是易事。數組

有什麼模式或者設計原則能解決這個嗎?markdown

我想說的是,並無模式和設計原則能解決,開發者缺乏的僅僅是發現和概括對象的能力(設計模式是錦上添花),對於excel解析的錯誤信息,實際上就應該定義一個」錯誤信息「這樣的對象。好比數據結構

public class ExcelParseError{
    public void addError(ParseErrorInfo info ){}
    public void addSimpleError(String line,String col,String message ){}
    public List<ParseErrorInfo> getMessages(){}
    public String toString(){
        .....
    }
}

所以,excel解析最後是這個樣子app

public void parse(Sheet sheet,ExcelParseError error){
User user = readUserInfo(sheet,error);
List<Order> orders = readUserOrderInfo(sheet,error);
UserCredit credit = readUserCreditInfo(sheet,error);
    
}

處理解析錯誤的代碼則變成以下

error.addSimpleError(line,col,message);

再次發現對象

發現對象是讓雜亂代碼變得有序的最重要方式,看以下例子:

public Long  startWorkflow(String user,long orgId,long taskType,long workflowType,Map<String,String> taskParas){
    .....
}

這是一個工做流引擎啓動流程的API,共有5個參數。這是我曾經項目的最先定義的API,後來實際上又擴展了好幾個參數,好比工做流支持版本後,又須要增長一個參數是int workflowVersion。

這6個參數實際上表明瞭啓動工做流須要的三類參數,"工做流參與人的描述","工做流自己的描述",還有"工做流啓動的輸入參數",所以,這個API最終定義成

public Long  startWorkflow(Participant p,WorkflowDef workflow,Variable vars){
    .....
}

Participant對應了工做流參與人描述 WorkflowDef 對應了工做流定義 Variable 則對應了工做流參數

這些對象加強了API的可擴展性,更爲重要的是,他的代碼更加容易閱讀,不管是調用者,仍是api自己的實現,"新發現的對象"讓雜亂無章的變量變得有序起來.

對象是在咱們編程生活中真實存在的,若是能感知到對象存在,則編程會美好不少,一樣,閱讀和維護代碼也會更加方便。在沒有感知對象的狀況下妄談設計模式和和設計原則,就是無源之水。

下一個例子是個人BeetlSQL的例子,有一個SQLLoader類用來加載sql語句,其中有一個片斷是 從markdown 文件加載sql語句。最初代碼以下(警告,代碼有毒,不要閱讀,直接跳過)

bf = new BufferedReader(new InputStreamReader(ins));
String temp = null;
StringBuffer sql = null;
String key = null;
while ((temp = bf.readLine()) != null) {
	if (temp.startsWith("===")) {// 讀取到===號,說明上一行是key,下面是SQL語句
		if (!list.isEmpty() && list.size() > 1) {// 若是鏈表裏面有多個,說明是上一句的sql+下一句的key
			String tempKey = list.pollLast();// 取出下一句sql的key先存着
			sql = new StringBuffer();
			key = list.pollFirst();
			while (!list.isEmpty()) {// 拼裝成一句sql
				sql.append(list.pollFirst() + lineSeparator);
			}
			this.sqlSourceMap.put(modelName + key, new SQLSource(
					sql.toString()));// 放入map
			list.addLast(tempKey);// 把下一句的key又放進來
		}
	} else {
		list.addLast(temp);
	}
}
// 最後一句sql
sql = new StringBuffer();
key = list.pollFirst();
while (!list.isEmpty()) {
	sql.append(list.pollFirst());
}
this.sqlSourceMap.put(modelName + key,
		new SQLSource(sql.toString()));

這段代碼解析markdown文件,讀取以===分割的的sql片斷,並放到sqlSourceMap裏。大概格式以下

disableUser
    ===
    * 這是一個更新用戶信息的SQL語句
    update user set status = 1 where id = #id#

儘管解析代碼不算長,且有不少註釋,但每次在這裏增長一點擴展都極其困難。好比Markdown 支持 」*「 符號做爲註釋語句,那對"*"代碼解析放在個哪一個地方?

後來我對這段代碼進行重構了,實際上,我是發現我須要一個MDParser類來負責這事情 :專門解析md文件,MDParser定義以下(能夠閱讀了)

public class MDParser {
    public MDParser(String modelName,BufferedReader br) throws IOException{
		this.modelName =  modelName;
		this.br = br;
		skipHeader();
	}
	public void skipHeader() throws IOException{
	....
	}
	
	public SQLSource next() throws IOException{
		String sqlId = readSqlId();
		if(status==END){
			return null;
		}
		//去掉可能的尾部空格
		sqlId = sqlId.trim();
		skipComment();
		if(status==END){
			return null;
		}
		int sqlLine = this.linNumber;
		String sql = readSql();
		
		SQLSource source = new SQLSource(modelName + sqlId,sql);
		source.setLine(sqlLine);
		return source;
	}
}

從這個類能夠看到,當讀入一個markdown文件的時候,首選調用skipHeader,去掉md文件開頭無關的文檔總體說明

next方法用來獲取每個sql片斷說明,先調用 readSqlId獲取sql的標示符號,而後 skipComment方法用來忽略sql註釋,最後 readSql用來讀取sql語句內容。

MDParser 使得SQLLoader更加精簡和容易閱讀,也使得關於Markkdown 解析更加容易維護。

警戒String,數組,和 Map

當程序中出現String 參數,數組參數,以及Map的時候,已經在提醒咱們是遺漏了系統的對象。 這三個類型參數固然很是靈活,能容納下任何數據結構,但有可能遺漏了系統隱含的對象。尤爲是數組和Map。我在上一章提到過的例子

Object[] rets = call();
boolean  success = (Boolean)rets[0];
String msg = (String)rets[1];

就沒有下面的定義好

CallResult rets = call();
boolean  success = rets.isSuccess();
String msg =  rets.getMessage();

若是CallResult包含了某個返回值,那麼,將CallResult定義成泛型就更加容易閱讀,好比返回CallResult

public CallResult  getUser(){
    
}

這確定沒有以下代碼更容易閱讀,讓後來者放心去使用

public CallResult<User>  getUser(){
    
}

這一篇我提到的每個好的例子都相對於差的的例子,都會多寫數行代碼,甚至還得寫一個類 ,但毫無疑問,閱讀更加容易,維護更加方便了。

總結 若是隻能用一個設計模式

我作過大量業務系統,電信的也好,金融也好,互聯網項目,仍是創業項目,也寫過很多工具,能公開的好比有Beetl,BeetlSQL,XLSUnit。這麼多工程項目,若是讓我說最重要的設計技巧是什麼,或者只能用一個設計技巧,我會堅決果斷的說,是」職責模式「

職責模式 描述瞭如何發現和劃分對象職責,就比如一個班,應該有班長,各科學習委員,小組長. 再好比,新聞裏常常出現某某重大事故,就會成立了某某專項委員會。在好比,爲了保證項目質量,咱們有測試組,爲了監控項目,咱們有PMO。咱們周圍生活,一直都按照人盡其職,職責劃分這個原則來運做。 若是劃分錯了,很是影響咱們的生活,好比讓我去監控項目進度:(。

職責模式,能夠搜索 GRASP

這是一個不多被人提起的模式,我我的推薦去學習體會。

盧正雨在《絕世高手》裏,從普通人最後變成了食神,若是你看了這個電影,就知道,他成爲食神是由於對食物的細膩感知。我想在《自下向上的編寫容易閱讀的代碼方法》這一部分的總結是 」感知對象的存在「,你也能寫出容易閱讀的代碼,甚至成爲高手。

相關文章
相關標籤/搜索