继续说一说昨天提到的那个代码重构
昨天我给了一段.NET Framework 2.0中的一段代码,希望大家能重构一下。结果大家没有对代码本身提出什么积极的看法,倒是有几位热心人代码都没有看清,就大肆予以评论(因为那段代码是错的,现已修正)。下面再说一下这段代码的特点和问题,欢迎有兴趣并能看到诗的博友斧正。
还是说一下代码的来源,这段代码确实是使用Reflector反编译出来的,不过从代码的优化痕迹来看,是比较忠实于最初的源代码的。另一个反证是新的源代码编译后再反编译,结果仍是很相近。上次给出的那段代码,一眼看上去确实是挺让人郁闷的,正如robaggio同学搞笑的说:也许是某种inline机制吧。。。
而仔细看昨天那段代码,除了行数多外,从效率上来说基本已是无懈可击。这么说有什么依据,凭什么说这个代码很优化?那么我们来看一下.NET Framework 1.1中QuoteJScriptString(string)方法的实现:
要想知道自己写的代码是否是业余水平,我认为可以参考一下上面这段代码。这段代码我觉得唯一有意义的地方就是创建StringBuilder对象的时候,给出了Capacity的大小,而且也仅此这一点显得和业余水平之间有了一点点的差距。至于为什么判断ch <= '"',我没有太明白,是因为swith中case太多会有什么效率问题吗?虽然'"'是ASCII中顺序里的第二个可打印字符(不算Space),但是这样将ch的检测路径分开,我没有觉得有什么大的意义。
那么2.0中的QuoteJScriptString有什么特点呢?一是:修改了bug,给QuoteJScriptString添加了一个forUrl的bool参数,指示转换时是否转换"%"为其escape(JScript中的全局函数)形式。因为如果当你把脚本代码使用到link的href中时,IE会自动的unescape这个字符串,再作为代码解析执行。看下面的示例:
如果在IE中你点击了click后,你觉得MsgBox里会显示啥?是"%25%"吗?实际上显示的是:"%%"。这就是为什么新版的方法要添加forUrl开关参数的原因,是为了修复bug。二是:优化了QuoteJScriptString方法的效率,除了继续沿用了创建StringBuilder那个地方外,还有三点。1) 做了参数合法性检查;2) 消除了不必要的StringBuilder实例的创建;3) 对于不用替换的字符,使用区段处理代替了逐字处理。
既然都说昨天那段挺不错了,那么还重构个头啊?其实重构只是一个镢头了。至于考证代码是不是微软原版的,我只能说:别人看到了诗,有的人看到屎。真的要重构,也就主要是节省点代码了,我相信程序员都是很懒的,即使打字再快,毕竟我们也不是打字员哈。给一个重构后的版本,代码如下:
最后有一个有趣的问题,你认为Fx 1.1和2.0中两个QuoteJScriptString是同一个人写的吗?你觉得这种重构方式好不好?是你会怎么做呢?
还是说一下代码的来源,这段代码确实是使用Reflector反编译出来的,不过从代码的优化痕迹来看,是比较忠实于最初的源代码的。另一个反证是新的源代码编译后再反编译,结果仍是很相近。上次给出的那段代码,一眼看上去确实是挺让人郁闷的,正如robaggio同学搞笑的说:也许是某种inline机制吧。。。
而仔细看昨天那段代码,除了行数多外,从效率上来说基本已是无懈可击。这么说有什么依据,凭什么说这个代码很优化?那么我们来看一下.NET Framework 1.1中QuoteJScriptString(string)方法的实现:
public static string QuoteJScriptString(string value)
{
StringBuilder strbQuoted = new StringBuilder(value.Length + 5);
for ( int i = 0; i < value.Length; i++)
{
char ch = value[i];
if (ch <= '"')
{
switch(ch)
{
case '\t':
{
strbQuoted.Append(@"\t");
continue;
}
case '\n':
{
strbQuoted.Append(@"\n");
continue;
}
case '\v':
case '\f':
{
strbQuoted.Append(value[i]);
continue;
}
case '\r':
{
strbQuoted.Append(@"\r");
continue;
}
case '"':
{
strbQuoted.Append("\\\"");
continue;
}
}
}
else
{
if (ch == '\'')
{
strbQuoted.Append(@"\'");
continue;
}
if (ch == '\\')
{
strbQuoted.Append(@"\\");
continue;
}
}
strbQuoted.Append(value[i]);
}
return strbQuoted.ToString();
}
{
StringBuilder strbQuoted = new StringBuilder(value.Length + 5);
for ( int i = 0; i < value.Length; i++)
{
char ch = value[i];
if (ch <= '"')
{
switch(ch)
{
case '\t':
{
strbQuoted.Append(@"\t");
continue;
}
case '\n':
{
strbQuoted.Append(@"\n");
continue;
}
case '\v':
case '\f':
{
strbQuoted.Append(value[i]);
continue;
}
case '\r':
{
strbQuoted.Append(@"\r");
continue;
}
case '"':
{
strbQuoted.Append("\\\"");
continue;
}
}
}
else
{
if (ch == '\'')
{
strbQuoted.Append(@"\'");
continue;
}
if (ch == '\\')
{
strbQuoted.Append(@"\\");
continue;
}
}
strbQuoted.Append(value[i]);
}
return strbQuoted.ToString();
}
要想知道自己写的代码是否是业余水平,我认为可以参考一下上面这段代码。这段代码我觉得唯一有意义的地方就是创建StringBuilder对象的时候,给出了Capacity的大小,而且也仅此这一点显得和业余水平之间有了一点点的差距。至于为什么判断ch <= '"',我没有太明白,是因为swith中case太多会有什么效率问题吗?虽然'"'是ASCII中顺序里的第二个可打印字符(不算Space),但是这样将ch的检测路径分开,我没有觉得有什么大的意义。
那么2.0中的QuoteJScriptString有什么特点呢?一是:修改了bug,给QuoteJScriptString添加了一个forUrl的bool参数,指示转换时是否转换"%"为其escape(JScript中的全局函数)形式。因为如果当你把脚本代码使用到link的href中时,IE会自动的unescape这个字符串,再作为代码解析执行。看下面的示例:
<a href="JavaScript: alert('%25%');">click</a>
如果在IE中你点击了click后,你觉得MsgBox里会显示啥?是"%25%"吗?实际上显示的是:"%%"。这就是为什么新版的方法要添加forUrl开关参数的原因,是为了修复bug。二是:优化了QuoteJScriptString方法的效率,除了继续沿用了创建StringBuilder那个地方外,还有三点。1) 做了参数合法性检查;2) 消除了不必要的StringBuilder实例的创建;3) 对于不用替换的字符,使用区段处理代替了逐字处理。
既然都说昨天那段挺不错了,那么还重构个头啊?其实重构只是一个镢头了。至于考证代码是不是微软原版的,我只能说:别人看到了诗,有的人看到屎。真的要重构,也就主要是节省点代码了,我相信程序员都是很懒的,即使打字再快,毕竟我们也不是打字员哈。给一个重构后的版本,代码如下:
public static string QuoteJScript(string value, bool forUrl)
{
if (StringHelper.IsEmpty(value))
{
return string.Empty;
}
StringBuilder strbQuoted = null;
int i, position;
i = position = 0;
const string QUOTED_CHARS= "\t\n\v\f\r\"%'\\";
string [] QUOTED_STRINGS =
{ @"\t", @"\n", @"\v", @"\f", @"\r", @"\""", @"%25", @"\'", @"\\" };
for ( ; i < value.Length; i++)
{
char ch = value[i];
int index = QUOTED_CHARS.IndexOf(ch);
if ( index >= 0 )
{
if ( ch != '%' || ( ch == '%' && forUrl ) )
{
if ( strbQuoted == null )
{
strbQuoted = new StringBuilder(value.Length + 5);
}
if ( i-position >= 1 )
{
strbQuoted.Append(value, position, i-position);
}
strbQuoted.Append(QUOTED_STRINGS[index]);
position = i + 1;
}
}
}
if (strbQuoted == null)
{
return value;
}
if ( i-position >= 1 )
{
strbQuoted.Append(value, position, i-position);
}
return strbQuoted.ToString();
}
{
if (StringHelper.IsEmpty(value))
{
return string.Empty;
}
StringBuilder strbQuoted = null;
int i, position;
i = position = 0;
const string QUOTED_CHARS= "\t\n\v\f\r\"%'\\";
string [] QUOTED_STRINGS =
{ @"\t", @"\n", @"\v", @"\f", @"\r", @"\""", @"%25", @"\'", @"\\" };
for ( ; i < value.Length; i++)
{
char ch = value[i];
int index = QUOTED_CHARS.IndexOf(ch);
if ( index >= 0 )
{
if ( ch != '%' || ( ch == '%' && forUrl ) )
{
if ( strbQuoted == null )
{
strbQuoted = new StringBuilder(value.Length + 5);
}
if ( i-position >= 1 )
{
strbQuoted.Append(value, position, i-position);
}
strbQuoted.Append(QUOTED_STRINGS[index]);
position = i + 1;
}
}
}
if (strbQuoted == null)
{
return value;
}
if ( i-position >= 1 )
{
strbQuoted.Append(value, position, i-position);
}
return strbQuoted.ToString();
}
最后有一个有趣的问题,你认为Fx 1.1和2.0中两个QuoteJScriptString是同一个人写的吗?你觉得这种重构方式好不好?是你会怎么做呢?
posted on 2005-07-16 00:51 birdshome 阅读(4446) 评论(24) 编辑 收藏 举报