你的位置:首页 > ASP.net教程

[ASP.net教程]【重构学习】05 函数的重构


《重构》这本书的代码都是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类里,整个代码在功能上实际上是分为计算和显示两个逻辑,有必要将计算属性,和最后的显示属性提取成不同的函数放在类里

但是这里只是单纯为了举几个例子来说明函数的提取重构而已,所以也就没必要继续弄了,这段垃圾代码就留到后面继续重构吧。