《重構》這本書的代碼都是java,我准備用C#來一遍。
而今天我的主要任務是寫一大段垃圾代碼出來,然後重構(僅限於函數的,不涉及到其它方面的重構)。
程序界面:

功能介紹:
俠客名字自己取,然後點擊按鈕隨機角色的屬性,
根骨,經脈,柔韌,悟性等四項屬性值都是隨機而來。
其他的都是由這四個屬性計算而來:
根骨:影響氣血,基礎外攻和基礎內攻
經脈:影響內力和基礎內攻
柔韌:影響身法和基礎閃避
按鈕功能的垃圾代碼如下:
/// <summary>
/// 產生俠客
/// </summary>
private void btnCreateSwordsman_Click(object sender, EventArgs e)
{
var random = new Random();
int boneValue, meridianValue, flexibilityValue, savvyValue;
txtSurname.Text = "王";
txtShortName.Text = "大牛";
boneValue = random.Next(10);
meridianValue = random.Next(10);
flexibilityValue = random.Next(10);
savvyValue = random.Next(10);
txtBone.Text = boneValue.ToString();
txtMeridian.Text = meridianValue.ToString();
txtFlexibility.Text = flexibilityValue.ToString();
txtSavvy.Text = savvyValue.ToString();
txtHP.Text = (boneValue * 20 + 20).ToString();
txtMP.Text = (meridianValue * 10).ToString();
txtAGI.Text = (flexibilityValue * 5 + 10).ToString();
txtExteriorAttack.Text = (boneValue * 2).ToString();
txtInsideAttack.Text = (meridianValue * 3 + boneValue*2).ToString();
txtDodge.Text = (flexibilityValue * 1.5/100).ToString("p");
}
為了便於理解,所以代碼很少,但是足夠垃圾,讓我們通過下面的學習一步步重構來吧!
基本上關於函數的重構都是因為函數過長而引起的,有的說50行,有的說30行,有的說一個屏幕,不管怎樣,別太長就好。而明顯我上面的函數看起來很短,實際上是因為我偷了懶,比如命名也有隨機的。(還有不要在意那些魔法數字和命名,寫了一半我覺得應該寫dota英雄屬性的隨機,我可以直接抄,因為取名真的好麻煩)
以下所有的這些重構的例子因為代碼本來就很簡單,所以可能看不出明顯的效果,有的時候也許讓你感到莫名其妙,但是你如果把它當做一個很大的系統裡的一部分,再將裡面的邏輯復雜化,那麼這些重構就顯得很有必要了。
1、提煉函數:將函數裡的一段代碼提煉出來,放到一個新的函數中,並讓函數名稱解釋該函數的用途
動機:如果每個函數的粒度很小,那麼函數被復用的機會就更大,覆寫也更容易,更高層的函數讀起來就像注釋。
做法:創造一個新函數(以做什麼命名,而不是怎麼做),提煉代碼到新函數(注意臨時變量和參數)
無局部變量的提煉函數:
/// <summary>
/// 產生俠客
/// </summary>
private void btnCreateSwordsman_Click(object sender, EventArgs e)
{
RandomSwordsmanName();
RandomSwordsmanAttribute();
}
/// <summary>
/// 產生一個隨機的俠客名(你假裝是隨機好了)
/// </summary>
void RandomSwordsmanName() {
txtSurname.Text = "王";
txtShortName.Text = "大牛";
}
/// <summary>
/// 隨機俠客的屬性(按照《重構》的做法,其實這裡可以不做注釋,因為這些函數名已經很清楚了,注釋反而是累贅)
/// </summary>
void RandomSwordsmanAttribute() {
var random = new Random();
int boneValue, meridianValue, flexibilityValue, savvyValue;
boneValue = random.Next(10);
meridianValue = random.Next(10);
flexibilityValue = random.Next(10);
savvyValue = random.Next(10);
txtBone.Text = boneValue.ToString();
txtMeridian.Text = meridianValue.ToString();
txtFlexibility.Text = flexibilityValue.ToString();
txtSavvy.Text = savvyValue.ToString();
txtHP.Text = (boneValue * 20 + 20).ToString();
txtMP.Text = (meridianValue * 10).ToString();
txtAGI.Text = (flexibilityValue * 5 + 10).ToString();
txtExteriorAttack.Text = (boneValue * 2).ToString();
txtInsideAttack.Text = (meridianValue * 3 + boneValue * 2).ToString();
txtDodge.Text = (flexibilityValue * 1.5 / 100).ToString("p");
}
有局部變量的函數提取:
要將隨機產生四個屬性和其它屬性的計算提取函數會涉及到臨時變量的問題,一般是傳參,參數很多就傳對象
/// <summary>
/// 隨機俠客的屬性
/// </summary>
void RandomSwordsmanAttribute() {
var basicInfo = RandomSwordsmanBasicAttribute();
GetOtherInfoByBasicInfo(basicInfo);
}
/// <summary>
/// 隨機俠客的基礎屬性
/// </summary>
/// <returns></returns>
SwordsmanBasicInfo RandomSwordsmanBasicAttribute() {
var basicInfo = new SwordsmanBasicInfo();
var random = new Random();
basicInfo.Bone = random.Next(10);
basicInfo.Meridian = random.Next(10);
basicInfo.Flexibility = random.Next(10);
basicInfo.Savvy = random.Next(10);
return basicInfo;
}
/// <summary>
/// 通過俠客基礎屬性得到其它屬性,並展示出來
/// </summary>
/// <param name="basicInfo"></param>
void GetOtherInfoByBasicInfo(SwordsmanBasicInfo basicInfo)
{
txtBone.Text = basicInfo.Bone.ToString();
txtMeridian.Text = basicInfo.Meridian.ToString();
txtFlexibility.Text = basicInfo.Flexibility.ToString();
txtSavvy.Text = basicInfo.Savvy.ToString();
txtHP.Text = (basicInfo.Bone * 20 + 20).ToString();
txtMP.Text = (basicInfo.Meridian * 10).ToString();
txtAGI.Text = (basicInfo.Flexibility * 5 + 10).ToString();
txtExteriorAttack.Text = (basicInfo.Bone * 2).ToString();
txtInsideAttack.Text = (basicInfo.Meridian * 3 + basicInfo.Bone * 2).ToString();
txtDodge.Text = (basicInfo.Flexibility * 1.5 / 100).ToString("p");
}
/// <summary>
/// 俠客基礎屬性
/// </summary>
public class SwordsmanBasicInfo
{
/// <summary>
/// 根骨
/// </summary>
public int Bone { get; set; }
/// <summary>
/// 經脈
/// </summary>
public int Meridian { get; set; }
/// <summary>
/// 柔韌
/// </summary>
public int Flexibility { get; set; }
/// <summary>
/// 悟性
/// </summary>
public int Savvy { get; set; }
}
然而這還不夠,用函數取代一些的表達式:
/// <summary>
/// 通過俠客基礎屬性得到其它屬性,並展示出來
/// </summary>
/// <param name="basicInfo"></param>
void GetOtherInfoByBasicInfo(SwordsmanBasicInfo basicInfo)
{
txtBone.Text = basicInfo.Bone.ToString();
txtMeridian.Text = basicInfo.Meridian.ToString();
txtFlexibility.Text = basicInfo.Flexibility.ToString();
txtSavvy.Text = basicInfo.Savvy.ToString();
txtHP.Text = GetHP(basicInfo.Bone).ToString();
txtMP.Text = GetMP(basicInfo.Meridian).ToString();
txtAGI.Text = GetAGI(basicInfo.Flexibility).ToString();
txtExteriorAttack.Text = GetExteriorAttack(basicInfo.Bone).ToString();
txtInsideAttack.Text = GetInsideAttack(basicInfo.Meridian ,basicInfo.Bone).ToString();
txtDodge.Text = GetDodge(basicInfo.Flexibility).ToString("p");
}
int GetHP(int bone) {
return bone * 20 + 20;
}
int GetMP(int meridian)
{
return meridian * 10;
}
int GetAGI(int flexibility)
{
return flexibility * 5 + 10;
}
int GetExteriorAttack(int bone)
{
return bone * 2;
}
int GetInsideAttack(int bone, int meridian)
{
return meridian * 3 + bone * 2;
}
float GetDodge(int flexibility)
{
return flexibility * 1.5f / 100;
}
.NET還有更好玩的dynamic玩法:
/// <summary>
/// 通過俠客基礎屬性得到其它屬性,並展示出來
/// </summary>
/// <param name="basicInfo"></param>
void GetOtherInfoByBasicInfo(SwordsmanBasicInfo basicInfo)
{
SetTextBoxValue(txtBone,basicInfo.Bone);
SetTextBoxValue(txtMeridian, basicInfo.Meridian);
SetTextBoxValue(txtFlexibility, basicInfo.Flexibility);
SetTextBoxValue(txtSavvy, basicInfo.Savvy);
SetTextBoxValue(txtHP, GetHP(basicInfo.Bone));
SetTextBoxValue(txtMP, GetMP(basicInfo.Meridian));
SetTextBoxValue(txtAGI, GetAGI(basicInfo.Flexibility));
SetTextBoxValue(txtExteriorAttack, GetExteriorAttack(basicInfo.Bone));
SetTextBoxValue(txtInsideAttack, GetInsideAttack(basicInfo.Meridian, basicInfo.Bone));
SetTextBoxPercentValue(txtDodge, GetDodge(basicInfo.Flexibility));
}
void SetTextBoxValue(TextBox textBox, dynamic num)
{
textBox.Text = num.ToString();
}
void SetTextBoxPercentValue(TextBox textBox, dynamic percent)
{
textBox.Text = percent.ToString("p");
}
當然這仍然不夠,
GetHP之類的函數可以放到SwordsmanBasicInfo類裡,整個代碼在功能上實際上是分為計算和顯示兩個邏輯,有必要將計算屬性,和最後的顯示屬性提取成不同的函數放在類裡
但是這裡只是單純為了舉幾個例子來說明函數的提取重構而已,所以也就沒必要繼續弄了,這段垃圾代碼就留到後面繼續重構吧。