“硬核”代码重构

1,882 阅读7分钟

在学习编程的路上,相信大家这几个词一定不少听,什么 面相对象、封装继承多态、内功心法21种设计模式 等等 。但是却很少用到,或者说用到的都是被动使用。大牛们在写代码前早就构思好了,接口,基类等等。自己写代码的时候,很少有把面向对象想的面很全,很容易在遇上不够优秀的代码,这时候就需要重构了。

但是我们却很少去重构,可能原因有很多,比如很重要的一点:不想改出Bug;不想增加工作量(我是要5点半下班的男人,女朋友还在等我做饭);时间很紧,先实现功能先;代码是82年的不敢动!!!

其实重构可以写出更健壮的代码、减少后面的工作量、让开发者更好阅读。看了很多重构的文章,发现很多是一些基本的,命名规范或者拆函数什么的。这篇文章写下我重构的一些思路和重构之前代码对比。好了,废话不多说,上项目。威武、威武、威武........

简单介绍一下项目,项目就是一个客户端小工具用来审核编写递交的说明是否规范。这里说下,面向对象是一种思想,和语言无关。只要是面向对象语言,无论你是C#、Java、TypeScript、Python,都是可以用这种思想重构的。

tools.png

上图就是小工具了一个部分截图,有若干个栏,每个栏都是填写一些对应的修改内容,在审核校验时,会检查写的内容是否符合标准。

前辈已经完成一些栏的校验,我的任务是完成剩下栏:写正则表达式,然后在不标准的时候提示就好了,是不是觉得根本没有必要重构,依葫芦画瓢就好了在我拿到代码的时候,是这样的代码:

// 下面变量是和界面绑定的变量,RaisePropertyChanged作用在变量改变的时候通知前端重新渲染
// 不熟悉C#代码的,只用知道这些变量就是和前台绑定的就是了

    private string _editDescription;
    /// <summary>
    /// 修改说明内容
    /// </summary>
    public string EditDescription
    {
        get { return _editDescription; }
        set
        {
            _editDescription = value;
            RaisePropertyChanged(() => EditDescription);
        }
    }

    private string _editDescriptionRules;
    /// <summary>
    /// 修改说明校验规则
    /// </summary>
    public string EditDescriptionRules
    {
        get { return _editDescriptionRules; }
        set
        {
            _editDescriptionRules = value;
            RaisePropertyChanged(() => EditDescriptionRules);
        }
    }

    private bool _editDescriptionHasValidationError;
    /// <summary>
    /// 修改说明校验标志
    /// </summary>
    public bool EditDescriptionHasValidationError
    {
        get { return _editDescriptionHasValidationError; }
        set
        {               
            _editDescriptionHasValidationError = value;
            RaisePropertyChanged(() => EditDescriptionHasValidationError);
        }
    }

    private string _integratedNote;
    /// <summary>
    /// 集成注意内容
    /// </summary>
    public string IntegratedNote
    {
        get { return _integratedNote; }
        set
        {
            _integratedNote = value;
            RaisePropertyChanged(() => IntegratedNote);
        }
    }

    private string _integratedNoteRules;
    /// <summary>
    /// 集成注意规则
    /// </summary>
    public string IntegratedNoteRules
    {
        get { return _integratedNoteRules; }
        set
        {
            _integratedNoteRules = value;
            RaisePropertyChanged(() => IntegratedNoteRules);
        }
    }

    private bool _integratedNoteHasValidationError;
    /// <summary>
    /// 集成注意校验标志
    /// </summary>
    public bool IntegratedNoteHasValidationError
    {
        get { return _integratedNoteHasValidationError; }
        set
        {
            _integratedNoteHasValidationError = value;
            RaisePropertyChanged(() => IntegratedNoteHasValidationError);
        }
    }
     
// 这里随便举了两栏的变量,后面还有若干栏。

依葫芦画瓢以后呢,我发现原来是这样的。每一栏用了单独三个变量直接去绑定:编写的内容、是否标准的标志、不标准提示语。我是一个懒人啊,在我画了两个瓢以后,就很烦(一直在那复制变量,在那改来改去),这些个变量都是差不多一个意思。为啥让我重复在复制,修改呢?

明显每栏有相同项啊,对不对,一个是内容,一个状态,一个是错误提示语啊,咆哮!!!

这时候,我想起了书本上的一句话: "早重构,常重构"

我已经安奈不住我那颗懒惰的心里,因为下面还有“狠多狠多”栏,每一行有三个类似的变量,我这依葫芦画瓢,这个星期加班,就在复制粘贴去了。我是要上进,要每天进步的人,不能这样!

我为啥不能将这相同的共性抽象出来呢?是不咯,这个时候,我想起了《葵花宝典》的第一页的第一句:“万物皆对象”,于是本能的告诉我,每一栏看做一个对象,只是每栏的值不一样。然后我就写了一个”鸡肋“(基类),代码如下:

    /// <summary>
    /// 栏 基类
    /// </summary>
    public class Base: ObservableObject
    {
        private string _content;
        /// <summary>
        /// 内容
        /// </summary>
        public virtual string Content
        {
            get { return _content; }
            set
            {
                _content = value;
                RaisePropertyChanged(() => Content);
            }
        }

        private string _errorTip;
        /// <summary>
        /// 错误提示
        /// </summary>
        public virtual string ErrorTip
        {
            get { return _errorTip; }
            set
            {
                _errorTip = value;
                RaisePropertyChanged(() => ErrorTip);
            }
        }

        private bool _isError;
        /// <summary>
        /// 是否错误
        /// </summary>
        public virtual bool IsError
        {
            get { return _isError; }
            set
            {
                _isError = value;
                RaisePropertyChanged(() => ErrorTip);
            }
        }
    }

virtual是为了让子类能够重写get和set(如果有需求的话,为后面扩展做准备),然后字段从3个变到了1个了。

/// <summary>
/// 修改说明栏
/// </summary>
public class EditDescription : Base { }		
private EditDescription _editDescriptions;
/// <summary>
/// 修改说栏绑定变量
/// </summary>
public EditDescription EditDescriptions
{
    get { return _editDescriptions; }
    set
        {
            _editDescriptions = value;
            RaisePropertyChanged(() => EditDescriptions);
        }
}
        
// 其他的一样,我就不多写了

那,我们来算一下账,原先的变量,每一栏有3个变量,一个变量有6行代码的话,假如我这个有100栏,就是:

重构前: 100(栏)x3x6 = 1800 行代码 (阿西吧!!!)。

重构后: 100(栏)x1x6 = 600 行代码 。

小学算数: 1800 - 600 = 1200 (1200行,你说干点啥不好啊)

秀儿们算下,你花这么多时间,在那复制粘贴,不敢去动前辈们的代码,还是勇敢一点呢?

然后是不是感觉到一个继承就简单了很多呢,这只是一个面向对象的简单运用,就让我们少写了这么多代码。

前辈和我说,写代码就像练武功,就像你会了“九阳神功”或者"吸星大法",学其他武功看一眼就会。也就是说,当你理解了面向对象以后呢,你自然而然的就会写出很精简的代码了。阿西吧,又扯远了。

变量好了,抬头一看函数,我的脸便有点抽搐,啊!!这函数有毒!函数是这样的:

  // 此处函数用来设置每一栏报错时边框变红
    private void SetValidateFlag()
    {
        // 绑定的实体类判断状态
        if (tsEntity.EditDescriptionHasValidationError)
        {
            // 控件边框改颜色
            EditDescriptionHsExpander.BorderThickness = new Thickness(1);
            EditDescriptionHsExpander.BorderBrush = _readBrush;
        }

        if (tsEntity.TestSuggestionHasValidationError)
        {
            TestSuggestionHsExpander.BorderThickness = new Thickness(1);
            TestSuggestionHsExpander.BorderBrush = _readBrush;
        }

        if (tsEntity.IntegratedNoteHasValidationError)
        {
            IntegratedNoteHsExpander.BorderThickness = new Thickness(1);
            IntegratedNoteHsExpander.BorderBrush = _readBrush;
        }
        // 此处省略一万个if,每个栏都有一个if
    }

然后大家懂的嘛,在我改了两栏以后,我又耐不住性子了。再一看,感觉似曾相识燕归来的感觉啊!有没有,每个if中都有三个类似的东西。我那个心啊,又忍不住悸动了起来,像是等初恋的感觉,想她来,来了又不知道要干哈。然后我发现其实if中判断的就是每栏的状态,括号里面是对控件栏的设置。然后想嘛,能不能搞个类似循环的东西,只要写一个for就好了呢。

思考以后,是这样的,我把控件也变成了栏的一个属性了,这样if判断里面就都是判断一个类了。代码就变成了这样:

    public class Base: ObservableObject
    {
        /// <summary>
        /// 模块控件(新增)
        /// </summary>
        public object Control { get; set; }
        
        //其他的和上面的Base一样
    }

然后 SetValidateFlag() 函数就变成了这样:

    /// <summary>
    /// 设置校验界面效果
    /// </summary>
    private void SetValidateFlag()
    {
        // listBase 所有栏实体集合
        foreach (var item in listBase)
        {
            if (item.IsError)
            {
                var control = item.Control as HsExpander;
                if (control == null)
                    continue;
                // 将控件的边框变成红色
                control.BorderThickness = new Thickness(1);
                control.BorderBrush = _readBrush;
            }
        }
    }

好了嘞,好好的一个if的葫芦瓢给咱给整没了。这时候我们来算算我们这个重构,省了多少事。老样子:

原先的一个if算5行代码,我这个有100栏,就是:

重构前: 100(栏)x 5 = 500 行代码 (全是if啊)。

重构后: 我数了一下,没有错的话应该是:14行代码 。

重构一下以后,变量得到了减少且对外统一。感觉一颗心得到了小小满足,感觉灵魂得到了升华,不知道是自己太容易满足,还是代码世界里给我的成就感。感觉“乾坤大挪移”瞬间上了两层。

这就是我在写代码的时候一个小小的重构思路,希望能够帮助到大家一点。然后这个是我的Github,如果有帮助到大家的项目,可以给我点个star, 小生在这边谢谢啦!!!