昨天,要寫一段程序完成一個定時任務,是有關Socket 發送的。胖子給我發了一段現成的程序,這段程序基本上的功能是實現了,但是表達的并不是那么清晰,因此我想重構一下。沒想到重構的過程竟然花了一個多小時,從晚上八點多,一下就寫到了十點,但是重構完后,感覺清晰多了。仔細想想收獲頗多,因此在這里寫寫經驗進行總結。
重構程序的目的,不是因為程序不能用才要你去重構,重構的目的是因為一、你的代碼,被人看的次數,遠比它用到的次數多;二、重構有利于你發現問題,讓你的程序結構優化,因此可復用性更強,遵守了知識的唯一性,DRY 原則;三、如果你要改動這段代碼,那么先重構,使得你的代碼好改,這實際是在為你的未來減少工作量,而且一段優秀的代碼,帶給你的價值,遠比你每次都要Ctrl+C,Ctrl+V 大得多。
寫代碼,要讓你的代碼第一次呈現在別人面前的時候,像讀英語一般,那么你的代碼功底是足夠了。你的代碼就可以稱作你最好的文檔了,其余什么文檔,大可不必!
基于昨天的經驗,我新總結了兩條:
一、經常使用重構方法extract method 的人,會發現,總是可以省掉一些臨時變量。這是好事,但這可能會造成如下的結果:
method_one(method_two(method_three(method_four())))
也就是說,很可能會導致這種長串的嵌套,導致程序可讀性的下降,使人看的暈頭轉向。那么如何解決呢,其實是一個度的問題。我給自己定了一個規矩,臨界點是三個函數這樣級聯起來,如果超過三個,我就將它們拆開。比如說上面這個小例子,我會拆成:
arg = method_three(method(four));
method_one(method_two(arg));
雖然浪費了一個臨時變量,但是這樣就可以讓人一眼看懂我的意思,可讀性提升,修改起來自然也會容易些。
二、寫過Java I/O 的人,肯定看到過這樣的程序:
Reader in = null;
Writer out = null;
try
{
in = new InputStreamReader(socket.getInputStream(),"utf8");
out = new OutputStreamWriter(socket.getOutputStream(),"utf8");
/**
* some TODOs here
*
**/
}catch(IOException ioe)
{
System.err.println("error message");
ioe.printStackTrace();
}
finally
{
try
{
if(in != null)
in.close();
if(out != null)
out.close();
}catch(IOException ioe2)
{
System.err.println("some error message");
ioe2.printStackTrace();
}
}
怎么說呢,這段代碼看上去,其實是夠好了,其實不重構也是可以的。也許我偏執吧,我認為它不夠好,因為:首先,大段的try catch 的確會捕獲異常,但是這段代碼至少有好幾段是會獨立拋出異常的,這里包含了四個IO 實例的創建和銷毀,這四段代碼如果出錯都會拋出異常,那么你捕獲的到底是哪個呢?其次,沒有把功能段合理分開,這段代碼的邏輯功能實際上是兩個,一個是讀,一個是寫,那么合并在一起,首先順序很亂,其次容易讓閱讀的人產生困惑,從而造成代碼可讀性差。我是這樣做的:
private void writeFile(String fileName, String outStr)
{
Writer writer = null;
try
{
writer = new OutputStreamWriter(new FileOutputStream(fileName),
"utf8");
}
catch (UnsupportedEncodingException e)
{
System.err.println("不支持的編碼方式");
e.printStackTrace();
}
catch (FileNotFoundException e)
{
System.err.println("初始化文件失敗,或路徑不存在:" + fileName);
e.printStackTrace();
}
try
{
writer.write(outStr);
writer.flush();
}
catch (IOException e)
{
System.err.println("寫文件失敗");
e.printStackTrace();
}
finally
{
try
{
if(writer != null)
writer.close();
}
catch (IOException e)
{
System.err.println("關閉文件失敗");
e.printStackTrace();
}
}
}
類似的,也將讀的邏輯獨立抽出來,雖然,這不但沒使代碼的量減少,卻增加了很多try catch 模塊,不過邏輯上很完整,而且發揮了每個try catch 的最佳功效。我把它起名曰,我個人的偏執情節吧。
困了,要睡覺了,本來還想將代碼從最初模樣,到最后模樣的過程復述一遍,改天有機會再說,精華都已經說了。嘿嘿