记一次.NET代码重构

 

好久没写代码了,终于好不容易接到了开发任务,一看时间还挺充足的,我就慢慢整吧,若是遇上赶进度,基本上直接是功能优先,完全不考虑设计。你可以认为我完全没有追求,当身后有鞭子使劲赶的时候,神马设计都是浮云,按时上线才是王道,毕竟领导是不会关注过程和代码质量的,领导只看结果,这也许就是我等天朝码农的悲哀。

需求:是这样的,要开发一个短信发送的模板,不同客户可能会使用不同的模板,而不同的客户使用的变量参数也是不同的。之前为了应急,线上已经完成了一个短信模板发送短信的功能,短信模板表也创建了,而且在表中已经新增了一条记录。我只需要做一个短信模板的增删改查界面就可以了,看上去我的任务挺简单的,老司机应该知道,接了个烂摊子。

下图所示是原来已经创建好了的表

SQL创建脚本如下:

SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO CREATE TABLE [dbo].[MessageModule](
[Id] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](50) NULL,
[Type] [nvarchar](50) NULL,
[TypeNo] [nvarchar](50) NULL,
[Channel] [nvarchar](50) NULL,
[Param] [nvarchar](50) NULL,
[Content] [nvarchar](max) NULL,
[CreatedBy] [uniqueidentifier] NULL,
[CreatedOn] [datetime] NULL,
[ModifiedBy] [uniqueidentifier] NULL,
[ModifiedOn] [datetime] NULL,
[IsDeleted] [bit] NULL,
[TypeId] [uniqueidentifier] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO

在这之前是已经开发了一个发送短信的API接口供客户调用了的,也就是说调用方(客户),不会修改代码,只能我这边来修改。虽然极不情愿接做了一半的任务,但是没办法,不可能给你的开发任务都是从头开始的。

实体类代码如下:

     [Table("dbo.MessageModule")]
public class MessageModule : DTO
{
public string Type { get; set; } //业务类型
public string TypeNo { get; set; } //业务编号
public string Channel { get; set; } //使用渠道
public string Name { get; set; } //名称模版
public string Content { get; set; } //短信内容
}

DOT类:

    public class DTO
{
public virtual Guid Id { get; set; }
public virtual DateTime? CreatedOn { get; set; }
public virtual Guid? CreatedBy { get; set; }
public virtual DateTime? ModifiedOn { get; set; }
public virtual Guid? ModifiedBy { get; set; }
public virtual bool IsDeleted { get; set; }
}

这是之前的代码,业务实体类MessageModuleBusiness.cs代码如下:

    public class MessageModuleBusiness : GenericRepository<Model.MessageModule>
{
private UnitOfWork.UnitOfWork unitOfWork = new UnitOfWork.UnitOfWork(); #region old code
/// <summary>
/// 获取模版内容
/// </summary>
/// <param name="crowd"></param>
/// <returns></returns>
public string GetContent(MessageContext messageContext)
{
string messageContent = "";
string TypeCode = string.IsNullOrEmpty(messageContext.serviceCode) ? "001" : messageContext.serviceCode;
try
{
var Module = unitOfWork.MessageModule.Get(c => c.Type == messageContext.channel && c.TypeNo == TypeCode).FirstOrDefault();
//Content的内容:【一应生活】您有一件单号为expressNumbers company,已到communityName收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life
if (!string.IsNullOrEmpty(Module.Content))
{
var content = Module.Content;
content = content.Replace("company", messageContext.company);
content = content.Replace("expressNumbers", messageContext.expressNumbers);
content = content.Replace("communityName", messageContext.communityName);
content = content.Replace("Id", messageContext.Id);
content = content.Replace("receiveTime", messageContext.receiveTime);
content = content.Replace("fetchCode", messageContext.fetchCode);
messageContent = content;
} return messageContent;
}
catch (Exception ex)
{ }
return "";
}
#endregion
}

MessageContext类,这个是客户端传输过来调用的一个实体对象。对象里面存在许多类似于短信的动态标签变量。

    public class MessageContext
{
/// <summary>
/// 手机号码
/// </summary>
public string phone { get; set; }
/// <summary>
/// 发送信息
/// </summary>
public string message { get; set; }
/// <summary>
/// 签名
/// </summary>
public string sign { get; set; }
/// <summary>
/// 渠道
/// </summary>
public string channel { get; set; }
/// <summary>
/// 内容
/// </summary>
public string content { get; set; }
/// <summary>
/// 取件码
/// </summary>
public string fetchCode { get; set; }
/// <summary>
/// 快递公司
/// </summary>
public string company { get; set; }
/// <summary>
/// 快递单号
/// </summary>
public string expressNumbers { get; set; }
/// <summary>
/// 社区名称
/// </summary>
public string communityName { get; set; }
/// <summary>
/// 到件时间
/// </summary>
public string receiveTime { get; set; }
/// <summary>
/// 序号
/// </summary>
public string Id { get; set; }
/// <summary>
/// 业务代码
/// </summary>
public string serviceCode { get; set; }
}

控制器方法externalMerchantSendMessage,这是供外部调用的

        /// <summary>
/// 外部商户发送信息
/// </summary>
/// <returns></returns>
public ActionResult externalMerchantSendMessage(MessageContext param)
{
logger.Info("[externalMerchantSendMessage]param:" + param);
bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);
if (!isAuth)
{
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.NoPermission).ToString(),
resultMsg = "签名或无权限访问"
}, JsonRequestBehavior.AllowGet);
}
var meaage = messageModuleBusiness.GetContent(param); if (string.IsNullOrEmpty(meaage))
{
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.failure).ToString(),
resultMsg = "发送失败"
}, JsonRequestBehavior.AllowGet);
} SMSHelper helper = new SMSHelper();
helper.SendSMS(meaage, param.phone);
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.success).ToString(),
resultMsg = "发送成功"
}, JsonRequestBehavior.AllowGet);
}

以上是我接收开发任务之前已经实现了的功能。看上去我的任务挺简单的,可是多年的开发经验告诉我,这里需要重构,如果我现在啥都不管,就只管做一个短信模板的增删改查界面的话,后面维护的人一定会抓狂。

看出什么问题没有?

这个接口方法externalMerchantSendMessage是给所有客户调用,而不同客户使用不同的短信模板,不同的模板,又存在不同的变量参数。而现在所有的变量参数都封装在了类MessageContext中,问题是我们无法一下子把所有的变量参数全部确定下来,并保持不变。那么,也就是说一旦需要添加变量参数,类MessageContext中的代码就必须修改,而且GetContent方法中的代码是硬编的,一样需要跟着修改。这样就形成了一个循环,不断加变量参数,不断改代码,不断发布接口版本.......

时间充裕的情况下,我自然是一个有节操的程序猿,那么就开始重构吧。

在重构之前,在脑海浮现的并不是各种设计模式,而是面向对象设计的基本原则。各种设计模式就好比各种武学套路或者招式,习武之人应该像张无忌练习太极剑一样,先学会各种套路,然后忘记所有套路,从而融会贯通。因为招式是死的,人是活得,有招就有破绽,根本没有必胜招式存在,就好像没有万能的设计模式一样,任何设计模式都存在缺点。

面向对象设计的核心思想就是封装变化,那么先找出变化点。从上面的分析中,我们已经发现了变化点,那就是短信模板中的变量参数,而这些变量参数都是客户调用方传过来的,不同客户传递的参数变量又可能是不一样的。我们先来看一下,客户传递过来的是什么?我们看下客户调用代码,这里有Get和Post两种调用方式。

        function sendMsg() {
//var appParam ="phone=15914070649&sign=78a7ce797cf757916c2c7675b6865b54&channel=weijiakeji&content=&fetchCode=1
&company=%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92&expressNumbers=123456&communityName=%E9%95%BF%E5%9F%8E%E4%B8%80%E8%8A%B1%E5%9B%AD&receiveTime=5&Id=1231";
//Get("/Message/externalMerchantSendMessage?" + appParam, {}); var data = {
"phone": "15914070649", "sign": "78a7ce797cf757916c2c7675b6865b54", "channel": "weijiakeji",
"fetchCode": 1, "company": "%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92", "Id": "1231"
};
Post('/Message/externalMerchantSendMessage', data);
}
//WebAPI Post方法
function Post(url, data) {
$.ajax({
url: url,
contentType: "application/json",
type: "POST",
dataType: "json",
async: true,
cache: false,
data: JSON.stringify(data),
success: function (response) {
$('#response').text(JSON.stringify(response));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(textStatus);
}
});
};
//// WebApi Get方法
function Get(url, data) {
$.ajax({
url: url,
contentType: "application/json",
type: "GET",
dataType: "json",
async: true,
cache: false,
//data: JSON.stringify(data),
success: function (response) {
$('#response').text(JSON.stringify(response));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(textStatus);
}
});
};

可见客户传递的是一个键值对集合,就是一个JSON格式的对象。根据前面的代码 bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);,可以分析出有三个参数是所有调用客户都必须传递过来的,那就是:channel,phone,sign,而其它的参数就是短信模板的变量参数和参数值。那么方法externalMerchantSendMessage(MessageContext param)中的参数就是一个可变对象。在C#4.0种存在一个dynamic不正是用来描述可变对象吗?

那么第一步修改传入参数类型,之前是硬编码的强类型MessageContext,现在不依赖此类,而是动态解析,修改externalMerchantSendMessage方法代码如下:

                dynamic param = null;
string json = Request.QueryString.ToString(); if (Request.QueryString.Count != 0) //ajax get请求
{
//兼容旧的客户调用写法,暂时硬编了
if (json.Contains("param."))
{
json = json.Replace("param.", "");
}
json = "{" + json.Replace("=", ":'").Replace("&", "',") + "'}";
}
else //ajax Post请求
{
Request.InputStream.Position = 0; //切记这里必须设置流的起始位置为0,否则无法读取到数据
json = new StreamReader(Request.InputStream).ReadToEnd();
}
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
param = serializer.Deserialize(json, typeof(object));

DynamicJsonConverter的作用是将JSON字符串转为Object对象,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization; public sealed class DynamicJsonConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary"); return type == typeof(object) ? new DynamicJsonObject(dictionary) : null;
} public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
} public override IEnumerable<Type> SupportedTypes
{
get { return new ReadOnlyCollection<Type>(new List<Type>(new[] { typeof(object) })); }
} #region Nested type: DynamicJsonObject private sealed class DynamicJsonObject : DynamicObject
{
private readonly IDictionary<string, object> _dictionary; public DynamicJsonObject(IDictionary<string, object> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
} public override string ToString()
{
var sb = new StringBuilder("{");
ToString(sb);
return sb.ToString();
} private void ToString(StringBuilder sb)
{
var firstInDictionary = true;
foreach (var pair in _dictionary)
{
if (!firstInDictionary)
sb.Append(",");
firstInDictionary = false;
var value = pair.Value;
var name = pair.Key;
if (value is string)
{
sb.AppendFormat("{0}:\"{1}\"", name, value);
}
else if (value is IDictionary<string, object>)
{
new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
}
else if (value is ArrayList)
{
sb.Append(name + ":[");
var firstInArray = true;
foreach (var arrayValue in (ArrayList)value)
{
if (!firstInArray)
sb.Append(",");
firstInArray = false;
if (arrayValue is IDictionary<string, object>)
new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
else if (arrayValue is string)
sb.AppendFormat("\"{0}\"", arrayValue);
else
sb.AppendFormat("{0}", arrayValue); }
sb.Append("]");
}
else
{
sb.AppendFormat("{0}:{1}", name, value);
}
}
sb.Append("}");
} public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!_dictionary.TryGetValue(binder.Name, out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
} var dictionary = result as IDictionary<string, object>;
if (dictionary != null)
{
result = new DynamicJsonObject(dictionary);
return true;
} var arrayList = result as ArrayList;
if (arrayList != null && arrayList.Count > 0)
{
if (arrayList[0] is IDictionary<string, object>)
result = new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)));
else
result = new List<object>(arrayList.Cast<object>());
} return true;
}
} #endregion
}

接下来是GetContent方法,此方法的目的很简单,就是要根据客户传递的模板变量参数键值对和短信模板内容,拼装成最后的短信发送内容,之前此方法里面是硬编码的,现在我们需要变成动态获取。

短信模板的内容示例:

【一应生活】您有一件单号为expressNumbers company,已到communityName收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life

我发现这样的模板内容有问题,模板中的变量参数是直接用的英文单词表示的,而我们的短信内容中可能有时候也会存在英文单词,那么我就给所有的变量参数加上{}。修改后如下:

【一应生活】您有一件单号为{expressNumbers} {company},已到{communityName}收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life

我们需要根据客户传递过来的对象,将短信模板中的变量参数,替换成变量参数对应的值。那么我们首先就要解析这个对象中的键值对信息。

        /// <summary>
/// 把object对象的属性反射获取到字典列表中
/// </summary>
/// <param name="data">object对象</param>
/// <returns>返回Dictionary(属性名,属性值)列表</returns>
static Dictionary<string, string> GetProperties(object data)
{
Dictionary<string, string> dict = new Dictionary<string, string>(); Type type = data.GetType();
string[] propertyNames = type.GetProperties().Select(p => p.Name).ToArray();
foreach (var prop in propertyNames)
{
object propValue = type.GetProperty(prop).GetValue(data, null);
string value = (propValue != null) ? propValue.ToString() : "";
if (!dict.ContainsKey(prop))
{
dict.Add(prop, value);
}
}
return dict;
}

接下来是通过正则表达式来匹配短信模板内容。

        /// <summary>
/// 多个匹配内容
/// </summary>
/// <param name="sInput">输入内容</param>
/// <param name="sRegex">表达式字符串</param>
/// <param name="sGroupName">分组名, ""代表不分组</param>
static List<string> GetList(string sInput, string sRegex, string sGroupName)
{
List<string> list = new List<string>();
Regex re = new Regex(sRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
MatchCollection mcs = re.Matches(sInput);
foreach (Match mc in mcs)
{
if (sGroupName != "")
{
list.Add(mc.Groups[sGroupName].Value);
}
else
{
list.Add(mc.Value);
}
}
return list;
}
public static string ReplaceTemplate(string template, object data)
{
var regex = @"\{(?<name>.*?)\}";
List<string> itemList = GetList(template, regex, "name"); //获取模板变量对象 Dictionary<string, string> dict = GetProperties(data);
foreach (string item in itemList)
{
//如果属性存在,则替换模板,并修改模板值
if (dict.ContainsKey(item))
{
template = template.Replace("{"+item+"}", dict.First(x => x.Key == item).Value);
}
} return template;
}

这样就讲客户传递的对象和我们的解析代码进行了解耦,客户传递的对象不再依赖于我们的代码实现,而是依赖于我们数据表中模板内容的配置。

这几个方法我是写好了,顺便弄个单元测试来验证一下是不是我要的效果,可怜的是,这个项目中根本就没用到单元测试,没办法,我自己创建一个单元测试

    [TestClass]
public class MatchHelperTest
{
[TestMethod]
public void ReplaceTemplate()
{
//模板文本
var template = "【一应生活】您有一件单号为{expressNumbers} {company},已到{communityName}收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
//数据对象
var data = new { expressNumbers = "2016", company = "长城", communityName = "长怡花园"};
string str = "【一应生活】您有一件单号为2016 长城,已到长怡花园收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
string str1=MatchHelper.ReplaceTemplate(template, data); Assert.AreEqual(str1,str); //重复标签的测试
template = "【一应生活】您有一件单号为{expressNumbers} {company},已到{communityName}收发室,单号:{expressNumbers}";
str = "【一应生活】您有一件单号为2016 长城,已到长怡花园收发室,单号:2016";
str1=MatchHelper.ReplaceTemplate(template, data);
Assert.AreEqual(str1, str);
}
}

说到单元测试,我相信在许多公司都没有用起来,理由太多。我也觉得如果业务简单的话,根本没必要写单元测试,国内太多创业型公司项目进度都非常赶,如果说写单元测试不费时间,那绝对是骗人的,至于说写单元测试能提高开发效率,减少返工率,个人感觉这个还真难说,因为即便不写单元测试也还是可以通过许多其它手段来弥补的,个人观点,勿喷。

接下来修改GetContent方法如下:

        public string GetContent(dynamic messageContext)
{
string strMsg = "";
string TypeCode = string.IsNullOrEmpty(messageContext.serviceCode) ? "001" : messageContext.serviceCode;
string channel = messageContext.channel;
try
{
var Module = unitOfWork.MessageModule.Get(c => c.Type == channel && c.TypeNo == TypeCode).FirstOrDefault();
if (!string.IsNullOrEmpty(Module.Content))
{
var content = Module.Content;
strMsg = MatchHelper.ReplaceTemplate(content, messageContext);
} return strMsg;
}
catch (Exception ex)
{
strMsg = ex.Message;
}
return strMsg;
}

(话外:先吐槽一下之前这个变量命名,MessageContext messageContext 和string messageContent,长得太像了,一开始我重构的时候害我弄错了,建议不要在同一个方法中使用相似的变量名称,以免弄混淆。妈蛋,老司机的我又被坑了,愤怒,无可忍受,果断重命名。)

原来控制器调用业务逻辑代码是直接这样的

MessageModuleBusiness messageModuleBusiness = new MessageModuleBusiness()

依赖于具体类的实现,而我们知道,具体是不稳定的,抽象才是稳定的,我们应该面向接口编程。今天是发送短信,明天可能就是发邮件,又或者要加日志记录等等等。

    public interface IMessageModuleBusiness
{
/// <summary>
/// 组装消息内容
/// </summary>
/// <param name="messageContext">动态参数对象</param>
/// <returns>组装后的消息内容</returns>
string GetContent(dynamic messageContext);
}

然后调用的代码修改为:

   private IMessageModuleBusiness messageModuleBusiness = new MessageModuleBusiness();

最终的externalMerchantSendMessage代码为:

        /// <summary>
/// 外部商户发送信息
/// </summary>
/// <returns></returns>
public ActionResult externalMerchantSendMessage()
{
try
{
dynamic param = null;
string json = Request.QueryString.ToString(); if (Request.QueryString.Count != 0) //ajax get请求
{
//兼容旧的客户调用写法,暂时硬编了
if (json.Contains("param."))
{
json = json.Replace("param.", "");
}
json = "{" + json.Replace("=", ":'").Replace("&", "',") + "'}";
}
else //ajax Post请求
{
Request.InputStream.Position = 0;//切记这里必须设置流的起始位置为0,否则无法读取到数据
json = new StreamReader(Request.InputStream).ReadToEnd();
}
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
param = serializer.Deserialize(json, typeof(object)); logger.Info("[externalMerchantSendMessage]param:" + param);
bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);
if (!isAuth)
{
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.NoPermission).ToString(),
resultMsg = "签名或无权限访问"
}, JsonRequestBehavior.AllowGet);
} var meaage = messageModuleBusiness.GetContent(param); if (string.IsNullOrEmpty(meaage))
{
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.failure).ToString(),
resultMsg = "发送失败"
}, JsonRequestBehavior.AllowGet);
} SMSHelper helper = new SMSHelper();
helper.SendSMS(meaage, param.phone); //发送短信
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.success).ToString(),
resultMsg = "发送成功"
}, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new Result<string>()
{
resultCode = ((int)ResultCode.failure).ToString(),
resultMsg = "发送失败"+ex.Message
}, JsonRequestBehavior.AllowGet);
}
}

这样的话,即便日后通过反射或者IOC来再次解耦也方便。

好了,通过这样一步一步的重构,在不修改原有表结构和不影响客户调用的情况下,我已经将变化点进行了封装,当客户的模板参数变量变化的时候,再也不需要变更代码,只需要修改表中的模板内容就可以了。

重构时,画类图是一个非常好的习惯,代码结构一目了然,这里我附上类图。

NET代码重构的更多相关文章

  1. 让代码重构渐行渐远系列(3)——string.Equals取代直接比较与非比较

    重构背景及原因 最近由于项目组的人员在不断扩充,导致项目中代码风格各异,大有百花齐放甚至怒放之势.考虑到团队的生存与发展,经过众人多次舌战之后,最终决定项目组根据业务分成几个小分队,以加强团队管理与提 ...

  2. C++代码重构——从C global到C++ template

    在学数据结构的时候,我常有这样目标--写出能够最大程度复用的代码(算法正确,封装优秀).我常想--如何能在短时间内达成"算法正确,封装优秀"这样的目标.经过一段时间的摸索,我的结论 ...

  3. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十二) 代码重构使用反射工厂解耦(一)缓存切换

    前言 上一篇中,我们用了反射工厂来解除BLL和UI层耦合的问题.当然那是最简单的解决方法,再复杂一点的程序可能思路相同,但是在编程细节中需要考虑的就更多了,比如今天我在重构过程中遇到的问题.也是接下来 ...

  4. CSS代码重构与优化之路

    作者:@狼狼的蓝胖子 网址:http://www.cnblogs.com/lrzw32/p/5100745.html 写CSS的同学们往往会体会到,随着项目规模的增加,项目中的CSS代码也会越来越多, ...

  5. 代码重构 & 常用设计模式

    代码重构 重构目的 相同的代码最好只出现一次 主次方法 主方法 只包含实现完整逻辑的子方法 思维清楚,便于阅读 次方法 实现具体逻辑功能 测试通过后,后续几乎不用维护 重构的步骤 1  新建一个方法 ...

  6. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十一) 代码重构使用反射工厂解耦

    前言 自从此博客发表以及代码开源以来,得到了许多人的关注.也没许多吧,反正在我意料之外的.包括几位大牛帮我做订阅号推广,真的很感谢他们.另外,还有几个高手给我提了一些架构上的问题.其实本身这个项目是没 ...

  7. CSS代码重构

    CSS代码重构的目的 我们写CSS代码时,不仅仅只是完成页面设计的效果,还应该让CSS代码易于管理,维护.我们对CSS代码重构主要有两个目的:1.提高代码性能2.提高代码的可维护性 提高代码性能 提高 ...

  8. 多线程的练习----妖,等待唤醒,代码重构,lock到condition

    × 目录 [1]需求 [2]妖的出现和解决 [3]等待唤醒 [4]代码重构 [5]改成Lock Condition ------------------------------------- 1,需求 ...

  9. 推荐五款优秀的PHP代码重构工具

    在软件工程学里,重构代码一词通常是指在不改变代码的外部行为情况下而修改源代码.软件重构需要借助工具完成,而重构工具能够修改代码同时修改所有引用该代码的地方.本文收集了五款出色的PHP代码重构工具,以帮 ...

随机推荐

  1. iOS中的过期方法和新的替代方法

    关于iOS中的过期方法和新的替代方法 1.获取某些类的UINavigationBar的统一外观并设置UINavigationbar的背景 注:方法名改了但是基本使用方法不变 + (instancety ...

  2. Photo Shop切图

    切图之前 哪些是需要切出来的? 修饰性的 (一般用在background属性) 图标.logo 有特殊效果的按钮  文字等 非纯色的背景 内容性的 (一般用在img标签) Banner.广告图片 文章 ...

  3. Spring Boot项目使用Flyway

    Purpose 开发人员在合作的时候经常遇到以下场景: 1.开发人员A在自己的本地数据库做了一些表结构的改动,并根据这些改动调整了DAO层的代码,然后将代码上传到svn或git等版本控制服务器上.此时 ...

  4. lumen Console Commands

    1.在app->Console->Commands中新增类 继承  Illuminate\Console\Command <?php namespace App\Console\Co ...

  5. ListView的监听器中OnItemClick各个参数的作用

    方法的原型如下 public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3){ } 后面有4个参 ...

  6. asp.net 导出Excel

    分享一个asp.net 导出假Excel代码.优点,不用借助于任何插件比如(NPOI),复制代码,修改grid.DataSource直接导出. 先看导出后的效果图 System.Web.UI.WebC ...

  7. Linux文件和目录

    access() //检查是否调用进程有Access这个文件的权限,如果文件是一个符号链接,会将它解引用,成功返回0,失败返回-1设errno #include <unistd.h> in ...

  8. shell流程控制

    if语句 判断条件用[]包裹起来 if [ $a > $b ] then echo ‘a > b’ elif [ $a < $b ] then echo ‘a < b’ els ...

  9. mysql 将列值转变为列的方法(转)

    转自http://www.jb51.net/article/36497.htm -- 创建库CREATE TABLE `rate` ( `uname` VARCHAR (300), `object` ...

  10. 证明你是你——快速开启Windows Azure多重身份验证

    中国版Windows Azure的多重身份验证(Multi-Factor Authentication)功能已经开放.这个功能说白了就是要“证明你是你”.目前可以支持以下几种验证方式: 手机,短信验证 ...