首先说明这里的代码复用指的是狭义的源代码的复用,而不是广义的黑盒复用和白合复用所指的代码复用,所以文中所说的代码复用都默认都是指前者所指的源代码的复用。
我们知道在程序设计中复用代码并不是一件容易的事情,其实每段代码的编写,都是通过一定的思考的,当然考虑的程度与其解决的问题的难度有关。我们很忌讳在一个工程中重复使用相同的源代码,如果出现这样的情况很多时候就是程序结构设计本身的不合理了。可是对于一个developer或一个product team,源代码的逻辑(主要是执行逻辑,而不是业务逻辑)在不同项目或同一项目不同版本中很可能需要复用,而这种复用很多时候又是很难抽象成广义的代码复用的。于是我们不得不ctrl+c & ctrl+v,这是一个好办法,也确实能为我们节省很多的时间,可是这样同时也会带来很多的问题。如果被复用的代码是近期所写的,我们一般对其很熟悉,ctrl+v时心理也很有底。可是对于编写了很久的代码,我们在ctrl+v就不得不考虑一下代码是不时完全合适被复用了。更多的时候我们还需要对复用代码做一些小的修改,最简单的修改莫过于把变量名改来适合当前代码的上下文。
如果有这样复用过代码的人可能会有一种感觉,我总是不知道哪个源码文件里的代码snippet是我觉得解决这个逻辑最好的@_@,因为每次ctrl+v后或多或少又会把代码做一些小的改动,或是修补不足或是增加功能。特别是修补了不足后如果能保留下来以做再次复用就好了,可是很多时候这些越来越优秀的代码snippet却仍然分布在某个项目的某个文件中。于是我们发现有些小工具可以用来帮助我们保存代码snippet,比如前些天有人开发的类似CodeLib什么的。
今天在用VS.NET 2005的时候,发现tools菜单里有一个Code Snippets Manager(或者用Ctrl+k,Ctrl+b呼出)。打开看了一下,第一感觉就是一个代码模版管理嘛,看看了些预置的snippet,居然都是些很简单的类似if语句结构、for语句结构、do语句结构、while语句结构等,觉得真是没有意思,难道微软要我们用鼠标来编程吗?继续往后翻,发现了几个比较复杂的snippets,有的有一屏那么多的代码。其实代码多少不是snippet的重要的问题了,而是这些snippet在插入IDE后,可以根据其在文件里的命令定义,产生如下图所示的自动添补提示功能:
在图中黄底黑字区域内编辑完后按Tab,其下面的虚线框里的内容会同步的更新。这样的功能为我们收集并复用源代码提供了很便利的支持,这个snippet是以xml格式保存的,只是whidbey beta1里面还没没有提供snippet文件的可视化编辑器,不过xml本身在VS.NET里也不难写。上图示例的代码文件如下:
<CodeSnippet Format="1.0.0"
<Header
<Title"named" iterator / indexer pair</Title
<Shortcutiterindex</Shortcut
<DescriptionImplement a "named" iterator / indexer pair, using a nested class</Description
<SnippetTypes
<SnippetTypeExpansion</SnippetType
</SnippetTypes
</Header
<Snippet
<Declarations
<Literal
<IDtype</ID
<DefaultElementType</Default
<ToolTipType to return from iterator / indexer</ToolTip
</Literal
<Literal
<IDname</ID
<DefaultMyView</Default
<ToolTipName of the iterator/indexer pair</ToolTip
</Literal
<Literal Editable="false"
<IDouter</ID
<DefaultMyOuterClass</Default
<FunctionClassName()</Function
</Literal
<Literal Editable="false"
<IDSystemCollectionsGenericIEnumeratorG</ID
<!-- FunctionShortName(System.IEnumerator)</Function --
<DefaultSystem.Collections.Generic.IEnumerator</Default
</Literal
<Literal Editable="false"
<IDSystemNotImplementedException</ID
<!-- FunctionShortName(System.NotImplementedException)</Function --
<DefaultSystem.NotImplementedException</Default
</Literal
</Declarations
<Code Language="csharp" Format="CData"<![CDATA[public $name$Impl $name$
{
get
{
return new $name$Impl(this);
}
}
public class $name$Impl
{
readonly $outer$ _outer;
internal $name$Impl($outer$ mc)
{
this._outer = mc;
}
// A Length property isn't required, but it's often useful.
public int Length { get { return 1; } }
public $type$ this[int index]
{
get
{
//
// TODO: implement indexer here
//
// you have full access to $outer$ privates.
//
$end$throw new System.NotImplementedException();
return default($type$);
}
}
public $SystemCollectionsGenericIEnumeratorG$<$type$ GetEnumerator()
{
// TODO: provide an appropriate implementation here
for (int i = 0; i < this.Length; i++)
{
yield return this[i];
}
}
}]]
</Code
</Snippet
</CodeSnippet
有了这个snippet的管理支持和方便的使用方法后,基本就解决了我前面提到源代码复用里面版本控制和变量名修改的问题。
其实这个snippet的使用还有一个积极的意义,我们知道编写相同的逻辑过程是枯燥的,我们都喜欢编写新的逻辑过程,即使新的逻辑更难更复杂,反而更有挑战。但是我们又不能避开很多程序逻辑的复用,在我们以一种类似"厌恶"的情绪重复着那些程序逻辑时,保证代码的正确性真是一场噩梦。