下面的類是一個老系統的代碼,如今放到sonar上面進行掃描,掃出來的結果發現複雜度超過了30。java
代碼複雜度是指代碼中的分支數量,好比有一個if分支,代碼複雜度就加1,若是if中有「||」或者「&&」那麼代碼複雜度就加2,for和while同理。通常複雜度超過10的類就算是比較複雜的了,而這個類的複雜度居然達到了30,代碼的糟糕程度可見一斑,如今咱們就來重構一下這個類的代碼。git
原始文件在這裏。
重構開始吧!github
多處String類型非空判斷
1 2 3 4 5 6 |
if (StringUtil.isEmpty(username)) throw new ICRClientException("username can not be null"); if (StringUtil.isEmpty(password)) throw new ICRClientException("password can not be null"); if (udto == null) throw new ICRClientException("ICRUploadDTO can not be null"); |
重構以後:apache
1 2 3 4 5 6 7 8 9 10 11 |
//將原來的地方替換爲 checkStringParamEmpty(username, "username"); checkStringParamEmpty(password, "password"); checkStringParamEmpty(udto.getUrlPath(), "urlPath"); ... //新增一個方法 private void checkStringParamEmpty(String value, String name) throws ICRClientException { if (StringUtil.isEmpty(value)) { throw new ICRClientException(name + " can not be null"); } } |
原代碼中不止這3個參數的校驗,還有不少,越多參數的校驗,咱們重構後的複雜度就會越低。post
代碼複雜度變化:原來是3,修改後爲1。單元測試
多String值判斷
1 2 3 |
if (!udto.getPriority().equals("0") && !udto.getPriority().equals("1") && !udto.getPriority().equals("2") && !udto.getPriority().equals("3")) throw new ICRClientException("priority must be 0/1/2/3"); |
重構以後:測試
1 2 3 4 5 6 7 8 9 |
//將原來代碼替換爲 checkValueWithinList(udto.getPriority()); ... //新增一個方法: private void checkValueWithinList(String priority) throws ICRClientException { if (!Arrays.asList("0", "1", "2", "3").contains(priority)) { throw new ICRClientException("priority must be 0/1/2/3"); } } |
代碼複雜度變化:原來是4,修改後爲1。url
對list的非空判斷
1 2 |
if (list == null || list.size() == 0) throw new ICRClientException("list can not be null"); |
重構以後:spa
1 2 3 4 5 6 7 |
//將原來的代碼替換爲 checkValueWithinList(udto.getPriority()); ... //新增一個方法 private void checkListNoNull(List list) throws ICRClientException { if (list.isEmpty()) throw new ICRClientException("list can not be null"); } |
代碼複雜度變化:原來是2,修改後爲1。code
多個catch的內容相同
1 2 3 4 5 6 7 8 |
int code = 0; try { code = httpClient.executeMethod(post); } catch (HttpException e) { throw new ICRClientException(e.getMessage(), e); } catch (IOException e) { throw new ICRClientException(e.getMessage(), e); } |
重構以後:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//將原來的地方替換爲 int code = executeHttpClient(httpClient, post); ... //新增一個方法 private int executeHttpClient(HttpClient httpClient, PostMethod post) throws ICRClientException { int code; try { code = httpClient.executeMethod(post); } catch (Exception e) { throw new ICRClientException(e.getMessage(), e); } return code; } |
代碼複雜度變化:原來是2,修改後爲1。
if判斷結果複雜化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (code == 200) { try { if (post.getResponseBodyAsString().equals("ok")) { return true; } } catch (IOException e) { throw new ICRClientException(e.getMessage(), e); } return false; } else if (code == 500) { throw new ICRClientException(post.getResponseBodyAsString()); } else { throw new ICRClientException(code + ":" + post.getStatusText()); } |
重構以後:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//將原來代碼替換爲 return returnFinialResult(post, code); ... //新增一個方法 private boolean returnFinialResult(PostMethod post, int code) throws ICRClientException, IOException { if (code == 500) throw new ICRClientException(post.getResponseBodyAsString()); if (code != 200) throw new ICRClientException(code + ":" + post.getStatusText()); try { return post.getResponseBodyAsString().equals("ok"); } catch (IOException e) { throw new ICRClientException(e.getMessage(), e); } } |
代碼複雜度變化:原來是4,修改後爲3。
本地變量始終不爲null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public boolean uploadToICR(String username, String password, ICRUploadDTO udto) throws ICRClientException { HttpClient httpClient = null; PostMethod post = null; httpClient = new HttpClient(); //some code here … } finally { if (post != null) { post.releaseConnection(); } if (httpClient != null) { httpClient.getHttpConnectionManager().closeIdleConnections(0); } } |
重構以後:
1 2 3 4 5 6 7 8 9 10 11 |
public boolean uploadToICR(String username, String password, ICRUploadDTO udto) throws ICRClientException { HttpClient httpClient = new HttpClient(); PostMethod post = null; //some code here … } finally { if (post != null) { post.releaseConnection(); } } } |
代碼複雜度變化:原來是1,修改後爲0。
讀取IO流的方法,爲何要本身實現?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
private byte[] readData(InputStream ins) throws IOException { byte[] buf = new byte[2048]; int count = 0; int len = 0; byte data[] = new byte[2048]; byte[] result = null; try { while ((len = ins.read(data, 0, 2048)) != -1) { int newcount = count + len; if (newcount > buf.length) { byte newbuf[] = new byte[Math .max(buf.length << 1, newcount)]; System.arraycopy(buf, 0, newbuf, 0, count); buf = newbuf; } System.arraycopy(data, 0, buf, count, len); count = newcount; } result = new byte[count]; System.arraycopy(buf, 0, result, 0, count); } finally { ins.close(); } return result; } |
在原代碼裏面本身實現了一個對讀取IO流字節的方法,這個能夠使用apache-io或者guava的API代替:
1 2 3 4 |
//使用apache io API的實現: byte[] bytes = IOUtils.toByteArray(inputStream); //使用guava API的實現: byte[] bytes1 = ByteStreams.toByteArray(inputStream); |
代碼複雜度變化:原來是不少,修改後爲0。
最終重構後的版本見這裏,最後的代碼複雜度從原來的30降到了3。 代碼寫的比較倉促,沒有寫單元測試,其實最好的作法是在重構以前先寫好單元測試,而後再慢慢修改原來的代碼,每修改一處地方跑一遍單元測試,這樣能夠保證你的重構沒有破壞原來的代碼邏輯。