zoukankan      html  css  js  c++  java
  • C#笔记2:重构

    转: 最常用的重构指导

    参考:http://www.cnblogs.com/KnightsWarrior/archive/2010/06/30/1767981.html,本文示例代码多来自此处;

    参考:《重构:改善既有代码》;

    完美而高档的摩天大厦应至少具备两个特点:房间内部是清洁的、结构上是无懈可击的。优秀的代码也应如此。码农要负责打扫房间,架构师负责搭建一个经得起考验的代码结构。有些人兼顾码农和架构的角色。如果你既不是码农,也不是架构师,那么就请离代码远点,离重构远点,要有多远滚多远。

    一:打扫房间

    1:避免重复代码

    避免重复代码在大多数情况下适用,但是我有一个逆观点是:允许重复代码,如果它影响到你的架构。

    2:提取方法原则,超过30行?

    并不一定超过30行的代码就必须提取为方法,当然,原则上,大部分情况下应该是这样的。还有,如果提取方法让你的代码更清晰,你就应该提取方法,如下:

    namespace LosTechies.DaysOfRefactoring.ExtractMethod.Before {        public class Receipt        {            private IList<decimal> Discounts { get; set; }            private IList<decimal> ItemTotals { get; set; }

            public decimal CalculateGrandTotal()           {                decimal subTotal = 0m;                foreach (decimal itemTotal in ItemTotals)                    subTotal += itemTotal;

                if (Discounts.Count > 0)               {                    foreach (decimal discount in Discounts)                        subTotal -= discount;                }

                decimal tax = subTotal * 0.065m;

                subTotal += tax;

                return subTotal;           }        }    }

    namespace LosTechies.DaysOfRefactoring.ExtractMethod.After   {        public class Receipt        {            private IList<decimal> Discounts { get; set; }            private IList<decimal> ItemTotals { get; set; }

            public decimal CalculateGrandTotal()           {                decimal subTotal = CalculateSubTotal();

                subTotal = CalculateDiscounts(subTotal);

                subTotal = CalculateTax(subTotal);

                return subTotal;           }

            private decimal CalculateTax(decimal subTotal)           {                decimal tax = subTotal * 0.065m;

                subTotal += tax;               return subTotal;            }

            private decimal CalculateDiscounts(decimal subTotal)           {                if (Discounts.Count > 0)                {                    foreach (decimal discount in Discounts)                        subTotal -= discount;                }                return subTotal;            }

            private decimal CalculateSubTotal()           {                decimal subTotal = 0m;                foreach (decimal itemTotal in ItemTotals)                    subTotal += itemTotal;                return subTotal;            }        }    }

    3:警惕超过300行的类

    如果它不是个门面类,那么超过300行的类很多时候过于复杂,俗称“上帝类”,因为它妄图做太多事情,可以考虑重构成更小的类;

    4:过多的方法参数

    方法参数超过5个几乎总是有问题的,可以把参数提取为一个实体类。当然,越接近于底层我越能容忍这种情况的发生,比如 DAL 类,查询条件多的情况下,我会允许带很多参数。

    5:没有必要的注释

    很多人拿微软的 FCL(基础类库) 来举反例,说 MS 的注释简直全面俱到。对不起,你要看清它在开发什么,它在开发 API,供我等小白使用的,所以它必须提供一份全面俱到的 API 说明。大多数情况下,干掉你代码中的注释,把代码写的让别人能直接看得懂。如果一定要写注释,则一定要按规范格式来,不是两个反斜杠后面跟一段话就叫做注释,你给自己身上贴满创可贴试试。

    6:不要用异常

    用 Tester-Doer 模式取代异常,不要尝试总是使用异常。

    7:要用异常

    不要使用这种代码:

    public bool Insert(Model model)   {        //some other code        Dal dal = new Dal();        if (dal.Insert(model))        {            return true;        }        else        {            return false;        }    }

    直接让异常往上抛,

    public bool Insert(Model model)   {        //some other code        new Dal(.Insert(model));    }

    直到某个地方愿意处理地方。

    8:方法内的代码属于一个层级

    穿衣服,穿裤子属于一个层级。穿衣服,造汽车,就不是同一个层级。

    9:Dispose

    如果某个东西需要 Close,就应该实现 IDispose。

    10:Static Or Not

    如果该类需要进入单元测试,则它不应该是 Static 的。如果静态了,代码就是在测试的收你得额外增加一个包装类。

    11:Shotgun Surgery(霰弹式修改)

    现象:当外部条件发生变化时,每次需要修改多个Class来适应这些变化,影响到很多地方。就像霰弹一样,发散到多个地方。

    重构策略:使用Move Method和Move Field将Class中需要修改的方法及成员变量移植到同一个Class中。如果没有合适的Class,则创建一个新Class。实现目标是,将需要修改的地方集中到一个Class中进行处理。

    12:Feature Envy(依恋情结)

    现象:Class中某些方法“身在曹营心在汉”,没有安心使用Class中的成员变量,而需要大量访问另外Class中的成员变量。这样就违反了对象技术的基本定义:将数据和操作行为(方法)包装在一起。

    重构策略:使用Move Method将这些方法移动到对应的Class中,以化解其“相思之苦”,让其牵手。

    13:组合与继承,你有两种选择

    这里无所谓说哪种好,哪种坏,看情况,以下是这两种的表现形式,你可以进行互转。

    namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.Before   {        public class Sanitation        {            public string WashHands()            {                return "Cleaned!";            }        }

        public class Child : Sanitation       {        }    }

    namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.After   {        public class Sanitation        {            public string WashHands()            {                return "Cleaned!";            }        }

        public class Child       {            private Sanitation Sanitation { get; set; }

            public Child()           {                Sanitation = new Sanitation();            }

            public string WashHands()           {                return Sanitation.WashHands();            }        }    }

    14:分解复杂判断

    复杂的判断基础总是要分解的,因为它太容易阅读了,写注释?注释一坨 Shit?

    namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.Before   {        public class Security        {            public ISecurityChecker SecurityChecker { get; set; }

            public Security(ISecurityChecker securityChecker)           {                SecurityChecker = securityChecker;            }

            public bool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)           {                bool hasPermission = false;

                if (user != null)               {                    if (permission != null)                    {                        if (exemptions.Count() == 0)                        {                            if (SecurityChecker.CheckPermission(user, permission) || exemptions.Contains(permission))                            {                                hasPermission = true;                            }                        }                    }                }

                return hasPermission;           }        }    }

    namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.After   {        public class Security        {            public ISecurityChecker SecurityChecker { get; set; }

            public Security(ISecurityChecker securityChecker)           {                SecurityChecker = securityChecker;            }

            public bool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)           {                if (user == null || permission == null)                    return false;

                if (exemptions.Contains(permission))                   return true;

                return SecurityChecker.CheckPermission(user, permission);           }        }    }

    15:尽快返回

    实际上,该条是要求我们,可以在方法内部多使用 return,直到最后才 return,会使得最终结果看起来很复杂;

    二:盖房子

    1:寻找边界

    架构的第一原则,是寻找边界,最直观的成果物就是建立几个解决方案,解决方案内有多少个项目。划边界的最终目的就是要告诉组员:什么样的代码应该编写到哪个解决方案中。

    2:建立公共库

    任何解决方案几乎都需要一个公共库,用于放置一些 Helper 类。

    3:资源可以作为一个单独的项目

    不同的资源可以独立成为不同的项目,图片、JS、Style字典、配置文件等,都可以作为资源。另外,第三方的 DLL 也需要作为资源独立出来,把它们注册到全局程序集中不如直接作为 Content 包含进项目来的舒爽。

    4:客户端逻辑最小化

    .NET 程序最让人诟病的是:混淆了也可窥测你的源码。除了这个原因,从解耦的角度看,UI 或者其它客户端项目,都应该知道更少的逻辑才好。

    5:基于测试的与MVC、MVVM、MVP

    如果一开始你并不知道什么是 MVC 或者 MVVM,那么没关系,先试着掌握单元测试,把代码写成基于测试的。我有一个激进的观点是,所有的架构模式,其实目的都是为了代码可测试。

    6:AOP

    权限认证是典型的面向切面编程。不是 Attribute 才能带来 AOP 思想,把要运行的代码交给一个 Action ,也能实现 AOP。

    7:模版模式、继承与多态

    继承不是多态,继承的另一个价值叫做:模版模式。如果一件 Case 有多个实现途径,它就应该是模版的,因为你总能找到一些方法放置到父类中去;

    8:工厂模式与工厂

    类不是被调用者 new 出来的,而是调用某个类的某个方法后被返回出来的,就叫做工厂模式。这类也叫做对象容器。对象容器也可以很复杂,复杂到叫做一个框架,比如 Unity。

    9:观察者模式、事件通知

    事件就是观察者模式。解耦也可以使用观察者模式来实现。

    10:接口的存在都是有目的的

    自从 面向接口编程 这个概念提出来后,接口就开始变得漫天飞。接口的出现不能基于某种假设,而是实际已经发生了作用。

    11:避免二转手的代码

    二转手的代码常常来自于所谓三层架构代码,UI-BLL-DAL,然后 BLL 中的大量方法实际就只有一句话 Dal.Update(model),老实说,我受够了这样的代码。

    12:见到条件,就考虑是否使用策略模式

    “使用策略类” 是指用设计模式中的策略模式来替换原来的switch case和if else语句,这样可以解开耦合,同时也使维护性和系统的可扩展性大大增强。

    如下面代码所示,ClientCode 类会更加枚举State的值来调用ShippingInfo 的不同方法,但是这样就会产生很多的判断语句,如果代码量加大,类变得很大了的话,维护中改动也会变得很大,每次改动一个地方,都要对整个结构进行编译(假如是多个工程),所以我们想到了对它进行重构,剥开耦合。

    namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.Before
    {
        public class ClientCode
        {
            public decimal CalculateShipping()
            {
                ShippingInfo shippingInfo = new ShippingInfo();
                return shippingInfo.CalculateShippingAmount(State.Alaska);
            }
        }
    
        public enum State
        {
            Alaska,
            NewYork,
            Florida
        }
    
        public class ShippingInfo
        {
            public decimal CalculateShippingAmount(State shipToState)
            {
                switch (shipToState)
                {
                    case State.Alaska:
                        return GetAlaskaShippingAmount();
                    case State.NewYork:
                        return GetNewYorkShippingAmount();
                    case State.Florida:
                        return GetFloridaShippingAmount();
                    default:
                        return 0m;
                }
            }
    
            private decimal GetAlaskaShippingAmount()
            {
                return 15m;
            }
    
            private decimal GetNewYorkShippingAmount()
            {
                return 10m;
            }
    
            private decimal GetFloridaShippingAmount()
            {
                return 3m;
            }
        }
    }

    重构后的代码如下所示,抽象出一个IShippingCalculation 接口,然后把ShippingInfo 类里面的GetAlaskaShippingAmount、GetNewYorkShippingAmount、GetFloridaShippingAmount三个方法分别提炼成三个类,然后继承自IShippingCalculation 接口,这样在调用的时候就可以通过IEnumerable<IShippingCalculation> 来解除之前的switch case语句,这和IOC的做法颇为相似。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.After_WithIoC
    {
        public interface IShippingInfo
        {
            decimal CalculateShippingAmount(State state);
        }
    
        public class ClientCode
        {
            [Inject]
            public IShippingInfo ShippingInfo { get; set; }
    
            public decimal CalculateShipping()
            {
                return ShippingInfo.CalculateShippingAmount(State.Alaska);
            }
        }
    
        public enum State
        {
            Alaska,
            NewYork,
            Florida
        }
    
        public class ShippingInfo : IShippingInfo
        {
            private IDictionary<State, IShippingCalculation> ShippingCalculations { get; set; }
    
            public ShippingInfo(IEnumerable<IShippingCalculation> shippingCalculations)
            {
                ShippingCalculations = shippingCalculations.ToDictionary(calc => calc.State);
            }
    
            public decimal CalculateShippingAmount(State shipToState)
            {
                return ShippingCalculations[shipToState].Calculate();
            }
        }
    
        public interface IShippingCalculation
        {
            State State { get; }
            decimal Calculate();
        }
    
        public class AlaskShippingCalculation : IShippingCalculation
        {
            public State State { get { return State.Alaska; } }
    
            public decimal Calculate()
            {
                return 15m;
            }
        }
    
        public class NewYorkShippingCalculation : IShippingCalculation
        {
            public State State { get { return State.NewYork; } }
    
            public decimal Calculate()
            {
                return 10m;
            }
        }
    
        public class FloridaShippingCalculation : IShippingCalculation
        {
            public State State { get { return State.Florida; } }
    
            public decimal Calculate()
            {
                return 3m;
            }
        }
    } 

    总结:这种重构在设计模式当中把它单独取了一个名字——策略模式,这样做的好处就是可以隔开耦合,以注入的形式实现功能,这使增加功能变得更加容易和简便,同样也增强了整个系统的稳定性和健壮性。

    13:分解依赖

    无抽象、静态类、静态方法都是不可单元测试的。那么,如果我们要写出可测试的代码,又要用到这些静态类等,该怎么办,实际上我们需要两个步骤:

    1:为它们写一个包装类,让这个包装类是抽象的(继承自接口,或者抽象类,或者方法本身是Virtual的);

    2:通知客户端程序员,使用包装类来代替原先的静态类来写业务逻辑;

    FCL 中的典型例子是:HttpResponseWrapper。

  • 相关阅读:
    Docker入门
    15个Docker基本命令及用法
    Docker系列
    docker
    Docker 常用命令
    查看用户列表在Linux
    Spring boot Mybatis
    CountDownLatch和CyclicBarrier 专题
    Spring Boot MyBatis 连接数据库
    Spring Boot MyBatis 通用Mapper插件集成 good
  • 原文地址:https://www.cnblogs.com/viviancc/p/3970134.html
Copyright © 2011-2022 走看看