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。
C#笔记2:重构的更多相关文章
- ITEYE中的读书笔记:重构其实就是持续改进
原文地址:http://hawkon.iteye.com/blog/2093338#bc2355877 前段时间同事参加ITEYE的试读有奖, 没想到得了个奖,拿到一本书.由于同事的推荐我也认真读了一 ...
- CloudNotes:一个云端个人笔记系统
很长时间没有更新博客了,一直在忙着工作和生活琐事,虽然偶尔也有闲暇之时,但短短的几个小时空闲又未必能够静下心来.最近一个多月突发奇想,将自己在近一年前做的一个自己用的云端个人笔记系统重构美化了一下,增 ...
- Python与Golang对比
一:前言 刚看了一篇软文,说什么“才华是改变人生最有效的途径”,反正呢,大体就是科技进步,要想一直在车上,就得不断的学习,刚好最近也准备学习Golang,最近火的不能在火了吧,刚好也有些Python基 ...
- MySQL数据库学习笔记(九)----JDBC的ResultSet接口(查询操作)、PreparedStatement接口重构增删改查(含SQL注入的解释)
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- PBRT笔记(6)——采样和重构
前言 本文仅作为个人笔记分享,又因为本章涉及多个专业领域而本人皆未接触过,所以难免出错,请各位读者注意. 对于数字图像需要区分image pixels(特定采样处的函数值)和display pixel ...
- 31天重构学习笔记(java版本)
准备下周分享会的内容,无意间看到.net版本的重构31天,花了两个小时看了下,可以看成是Martin Fowler<重构>的精简版 原文地址:http://www.lostechies.c ...
- 重构(Refactoring)技巧读书笔记(General Refactoring Tips)
重构(Refactoring)技巧读书笔记 之一 General Refactoring Tips, Part 1 本文简要整理重构方法的读书笔记及个人在做Code Review过程中,对程序代码常用 ...
- 《HTML重构》读书笔记&思维导图
最近读了<HTML重构>这本书,以下做出自己的总结归纳,大家可以一起学习交流. 什么是重构?重构是在不改变程序行为的基础上进行小的改动是代码基本逐渐完善的过程,通常需要一些自动化工具的帮助 ...
- 重构与模式(Refactoring to Patterns)-读书笔记
第一章 ☛过度设计:是指代码的灵活性和复杂性超出所需. 第二章 ☛重构是一种保持行为的转换. 第三章 ☛每一个模式都是由三部分组成的规则,他表达的是某一环境,一个问题以及解决问题的方案之间的关系. ☛ ...
随机推荐
- 关于GrideView Item点击后出现错乱重叠的情况
我在一个搜索页做了一个筛选信息的功能 大概思路如下:在根布局中用Include 引入一个筛选框(如图), 然后把边距设置为 android:layout_marginBottom="-250 ...
- 面向对象的异常处理之深入理解java异常处理机制
什么是异常? 异常是对问题的描述,将问题的对象进行封装: 异常体系的特点:异常体系中的所有类以及建立的对象: 都具有可抛性,也就是说可以被throw和throws关键字所操作,只有异常体系具有该特点: ...
- JSP之AJAX
伴随着Web开发越来越广泛,越来越多网站开始应用AJAX.事实上,Ajax在Web应用带来的变化,我们已经在不知不觉中体验过了.例如,百度搜索提示,会员注册…… 在传统Web应用模式中,页面中用户的每 ...
- 理解C#系列 / .NET体系结构
.NET体系结构 索引 前提条件 编程 编程语言 编程语言之一:C# C#依赖.NET平台 .NET平台下的公共语言运行库 .NET平台下的基础类库 C#可以开发什么? 前提条件 [最低配置]知道什么 ...
- (转)分布式缓存GemFire架构介绍
1什么是GemFire GemFire是一个位于应用集群和后端数据源之间的高性能.分布式的操作数据(operational data)管理基础架构.它提供了低延迟.高吞吐量的数据共享和事件分发.Gem ...
- Cstring使用说明
CString::Left(intnCount)const; //从左边1开始获取前 nCount个字符 CString::Mid(intnFirst)const; //从左边第 nCount+1个字 ...
- 济南学习 Day 3 T3 pm
仙人掌(cactus)Time Limit:1000ms Memory Limit:64MB题目描述LYK 在冲刺清华集训(THUSC) !于是它开始研究仙人掌,它想来和你一起分享它最近研究的结果. ...
- Linux 伙伴算法简介
本文将简要介绍一下Linux内核中的伙伴分配算法. Technorati 标签: 伙伴算法 算法作用 它要解决的问题是频繁地请求和释放不同大小的一组连续页框,必然导致在已分配 ...
- NFS网络操作系统介绍以及相关应用
1. NFS服务简介 NFS是Network File System 的缩写,中文名称为网络文件系统,由Sun公司开发,功能是通过网络让不同的机器.不同的操作能够彼此分享数据,让应用程序在客户端通过网 ...
- Linux驱动编程--基于I2C子系统的I2C驱动
代码中,我添加了很多注释,应该不难理解,有错误大家可以指出来,我再改正 #include <linux/kernel.h> #include <linux/module.h> ...