下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
换行的讲究5寻找最佳的断行位・5每行只写一条语句 ?.1.3分行定义变量 8避免代码过于拥挤9使用空行分隔代码块!0使用空格降低代码密度13如何缩进153.1嵌套或包含关系引起的缩进!7因换行而产生的缩进20使用空格还是Tab键21大括号22大括号的位*22空的大括号结构23仅包含单个语句的结构体26保持项目文件的条理性29解决方案的结构呼应29代码文件的结构29.5.3使用#region标记32第2章养成良好的注释习惯33何时需要注释33.1.I解释代码的意图34.1.2对局部变量的说明35.1.3充当代码标题36.1.4指出例外情况40.1.5开发提示40.2注释的格式41单行注释42多行注释442.3正确使用XML文档注释45
结构与类的XML文档注释46属性的XML文档注释47方法的XML文档注释49构造函数的XML文档注释50事件的XML文档注释51枚举类型的XML文档注释53泛型的XMし文档注释53其他标记54总的来说,如果多看看MSDN自身的类库参考,你会发现其实XML文档注释最终形成的就是这样的结果。MSDN本身就是ー个最好的XML文档注释的范例,我们可以在实践过程中不断地进行模仿和学习。第7章如何使用函数57为什么要使用函数57TOC\o"1-5"\h\z\o"CurrentDocument"函数与方法 57\o"CurrentDocument"代码复用 59\o"CurrentDocument"隐藏细节 61函数重载65.2.1 重载的语义65.2.2 保持核心代码唯一 70参数的设计?4参数的命名 743.2 不要使用保留项743.3 变长参数列表753.4 何时使用ref参数和ou1参数773.5 参数的顺序78.6 重载函数的参数一致797.4参数检查82检查零值及空引用83检査枚举类型84防止数据被篡改854.4 在何处检査合法性 87
返回值的用法88离开函数的时机89假设我们写的是文章而不是程序,那么你ー定觉得诸如文章应该分为若干个自然段、每段开头空两格之类的规则是理所当然的。如果段落的开头不空两格,或者干脆把整个文章写成单独的一段,仔细想来似乎也不会影响文章实质内容的表达。既然如此,我们为什么还要在形式上下功夫呢?设想一下,如果你手中的这本书既无章节也无目录,正文中的不同内容都使用同样的字体字号印刷,几百页纸从头至尾洋洋洒洒如念经般地‘‘ー气呵成”,你还有耐心看下去吗?这是ー个人人都能理解的道理,可是当文章变成程序的时候,就不是每个人都能想得通的了。不仅仅是初学者,甚至一些熟练的开发人员,也会写出凌乱不堪的代码。许多人一定有过这样的经历:一年半载之后,自己原来写的程序就完全看不懂了。如果这段程序只是为了交作业,或者临时ー用,那还可以不去追究,但如果这是一个商业软件,现在需要根据客户的要求进行修改的话,工作量可就大了——你不得不先花时间把你原来的思路看懂。肯定会有人反驳:代码是给机器运行的,又不是给人看的,写那么好看有什么用?他的话只对了前半句:代码确实是给机器运行的,可是机器总共オ需要看它儿分钟?你花ー个月编写的程序,机器顶多两三分钟就编译好了——在这两三分钟之前,这代码不都是你在看吗?开发软件编写代码不是一朝一タ的事情,更多的情况下,ー个软件的开发要经历很长的时间,并且常常由多人合作完成。一个庞大的软件项目,可能会动用上千名程序员工作数年!如果把代码写得连自己都看不明白,怎么与别人交流?同一个开发团队内,一定要保持良好且一致的代码风格,才能最大化地提高开发效率。有的初学者会问:我现在只是一个人写程序,并不需要和其他人合作,这些条条框框还有什么必要吗?要知道,团队协作只是ー个方面。我经常遇到这类情况,ー些初学者拿着他的程序来说:“这个怎么不能编译?”我帮他把代码整理了半天,发现有一个地方丢了半个大括号。如果他写程序的时候能够稍加注意一些的话,相信此类错误完全可以避免。保持良好的编程习惯,能够避免的错误还远不止这些。如果说程序代码中对算法的清晰表述是通过长期训练而获得的,那么本章要介绍的这些方法则无需伤神,你不必对代码做任何实质性的改动,只需要添加一些空行与空格,就可以使其可读性大大提高——这些规则就像写文章应该分段ー样简单,只要愿意遵守,那么别人在第一眼看你的代码时,必能感觉到你那良好的编程修养,即所谓“见字如见大”。1.1换行的讲究虽然你完全可以在C#里将所有的代码都连在一行里书写,但想必没有人愿意这么做,谁也不会自己折磨臼己的眼睛,何况大多数鼠标对于上下翻页的支持都比左右翻滚好得多。我相信,这也是大多数人接受将每条语句分行书写的原因,很少有人会怀疑这ー点的合理性。例如下面这行代码,虽然结构很简单,但是它实在太长了,所以被分成了两行:代码示例1T:由于代码过长而进行断行bitmap=newBitmap(size.Width,size.Height,System.Drawing.Imaging.PixelFormat.Format32bppArgb);这一点我相信大家都能理解并愿意遵循,然而问题的焦点并不在于要不要换行,而在于在什么位置换行。写程序不能像写文章那样,什么时候顶到了边界就换,而必须按照其语法规则,在可以换行的位置断开。例如,对于包含ー个超长表达式的语句来说,我们可以在某两个表达式项之间将其断开,如下所示:代码示例1-2:通过断行使代码更加清晰if(f==ImageFormat.Jpeg.Guid|f==ImageFormat.Tiff.Guid|If==ImageFormat.Png.Guidf==ImageFormat.Exif.Guid){supportsPropertyIterns=true;}else(supportsPropertylterns=false;)原本一个很长的条件表达式,通过在“|ド运算符处换行,显得更加地清晰。有一点需要我们注意的是,当我们进行折行时,要将折行位置处的分隔符(如前一例中的逗号,这一例中的"丨运算符等)留在上一行的行末,给人以“此行并未结束”的直观印象。这就好像在英文书写中,如果你需要将一个单词拆开,就需要在前一行末尾加上连字符,以表示那个单词并没有结束。可以看出,换行在防止代码超出屏幕边界的同时,还影响着代码的表达。因此如何选择合适的换行位置也是很有讲究的。有的时候,我们并不一定非要在临近右边界的时候オ去换行,如果存在更为合理的分法,就应当采用,例如下面的情况:doublecontainerAspectRatio=(double)container.Clientwidth/container.ClientHeight;按理说这样的断行并没有什么问题,它在表达式的两项之间断开,并将运算符留在了上一行的行末。但是,我相信如果换ー种断行方式的话,能够更加清楚地表达出原来的逻辑:
doublecontainerAspectRatio=(double)container.ClientWidth/container.ClientHeight;如此一来,这个除法算术表达式就显得较为完整,相比前ー种写法而言更能体现其内在的逻辑关系。通常我们会选择整个表达式中最高的关系层次进行断行,例如上述代码中的“赋值号”和“除号”都是可以考虑的断行点,但相比较而言,除号连接的这个算术表达式只是整个赋值表达式的右半部分,如果在除号处断行,那么不但整个表达式会被截断,连局部的这个除法表达式也会被截断;反之,我们选择在赋值号处换行,可以保持除法表达式的完整,最大限度地减少换行对语句整体结构的破坏。同样的道理,为了将逻辑体现得更为清晰,我们甚至可以将函数调用中的每一个参数都分行书写,如同下面这样:代码示例『4:将函数调用中的每一个参数都分行书写RectangleimageBounds=newRectangle(itemBounds.X+padding,itemBounds.Y+padding,itemBounds.Width-padding*2,itemBounds.Height-padding*2);当参数数量较多,参数较长或者包含表达式的时候,这种排版比起单独写成一行更为直观醒目。对于LINQ查询表达式来说,将每个子句单独写成一行也是好的习惯。因为这同时符合了T-SQL语言的文化传统。例如:代码示例1-5:将LINQ查询表达式中的每个子句单独写成一行IEnumerable<int>highScoresQuery=fromscoreinscoreswherescore>80orderbyscoredescendingselectscore;
如果说换行是为了防止屏幕左右滚动的话,那么当这个情况不存在的时候,•些人就开始打算充分利用屏幕空间了:privatestaticvoidSwap(objecta,objectb){objecttemp;temp=a;a=b;b=temp;}看起来好像确实没有占据多少屏幕空间,这只是把三条很短的语句凑在一行了而已ーー关键的理由是:它不会引起屏幕的左右滚动。但是当人们已经习惯于一行一条语句的时候,很可能就会忽视这里有三条语句这个事实(不要指望每次遇到的都像这个例子一样地简单)。更为重要的一点是,编译器总是按行来进行设计的,将多条语句写在一行会引起不必要的麻烦,例如:你将永远无法把断点设置在后面的语句上:A privatesttlievoidSvtp(objectt,objectb)(objeCttfp;。 37133•ib;bstep.)图!-i:一行代码包含多条语句时的断点设置しU有的读者会觉得,如果代码复杂,当然应该分开书写,没有必要去节省那点屏幕,但是如果像这个例子中这么简单,写在ー起也不会带来什么麻烦。单纯地看来,他的话不无道理,可是,对于一个开发团队,或者将要进入开发团队的人来说,最重要的是“统ー”。如果我们允许将多条语句合并到同一行代码内,那么怎样的代码オ算“简单”到可以合并的程度?是宽度小于50个字符的可以合并,还是宽度小于51个字符的可以合并?当一条规定无法被准确地定义的时候,它也就无法执行,从而必将在整个开发团队中产生不一致性,最终导致更多的混乱。1.3分行定义变量我们再来看ー种情况,这类代码出现的机率更为频繁,它是将相同数据类型的几个变量声明放在了同一•条语句中:intnum,factor,index,length;如果我说我反对这种写法,一定会有读者大叫起来:这明明是单独的一条语句,何况C#允许我们在一条语句内声明多个变量,如此一来还可以少写几个‘'int",为什么不行?这种写法,显而易见会给注释帯来很大的麻烦。把它们都写在ー起以后,我怎么给每个变量添加注释呢?如果是分开书写的,那么我可以很容易地为每ー个变量添加单独的注释,就像这样:代码示例1-6:将每个变量分行定义将有助于单独注释/Z要计算的数值intnum;//表示影响因子intfactor;/Z元素所在的索引号intindex;/Z数据列表的总长intlength;如果觉得这种写法较为繁琐,一定要节约那几个''int”,以强调它们的数据类型相同的话,也可以采取下面的写法:代码示例『7:变量分行定义的折衷方案num,/Z要计算的数值factor,//表示影响因子index,/Z元素所在的索引号length;/Z数据列表的总长这种方式只使用了一条声明语句,但是每个变量都书写在单独的行上,便于有针对性的注释。避免代码过于拥挤想想人们为什么喜欢为文章添加各级标题以及其他复杂的格式,是因为美观吗?也许是的,但我相信这些格式可以更容易地让人们理清思路。可是在程序中,我们无法使用这些手段,所有的代码都是纯文本的,即使VisualStudio的代码高亮功能uj•以为代码的不同部分标上不同的颜色,但这并不能真正影响到代码本身。因此,光是换行还是不够的,我们还需要更多的手段。
.1使用空行分隔代码块适当地添加空行则是一个非常有效的代码整理方式——有点像文章中的分段,一段意思也许需要若干个句子才能表达清楚,在表达下一段意思之前,应当另起一段。首先,每个C#代码文件是从命名空间引用开始的,ー组引用结束之后,则是命名空间的声明及类型的声明。很显然地,在命名空间引用与命名空间声明之间,应该留有一个空行以示区隔:代码示例!-8:在命名空间引用之后添加空行usingSystem;usingSystem.Collections.Generic;usingSystem.Drawing;usingSystem.Drawing.Imaging;usingSystem.10;usingSystem.Text;usingAvilla.Metadata;usingAvilla.Searching;/Z这里用空行隔开namespaceAvilla{//下面的内容省略ー个空行,意味着不同的功能块的分隔,如果读者稍加留心,就会发现VisualStudio自动生成的代码,总是在类型的各个成员之间留有一个空行。我们在书写代码的时候,也可以模仿这一格式:代码示例『9:在类型的各个成员之间添加空行IIIくsummary〉//Z表示一条搜索条件的抽象基类///〈/summary〉publicabstractclassSearchCondition
Ill<summary>II!初始化ー个くseecref="SearchCondition*/>类型的实例,并指明是否区分大小写III</summary>III<paramname="caseSensitive”>是否区分大小写く/param>protectedSearchCondition(boolcaseSensitive)(this.caseSensitive=caseSensitive;)/Z这里用空行隔开protectedboolcaseSensitive=false;III<summary>II!获取或设置ー个<seecref-Booleanン)类型的值,以指示是否区分大小写III</summary>publicboolCaseSensitive{get{returncaseSensitive;}set{caseSensitive=value;})/Z这里用空行隔开III<summary>II!获取表示此搜索条件的SQL筛选条件表达式III</summary>III<returns>II!返回一个字符串形式的条件表达式,可直接用于SQL语言中的WHERE子句///</returns>publicabstractstringGetFilterExpressionO;
这样排版无疑会使得每个成员的代码段更富独立性,绝大多数的编译器,在自动生成代码时都会遵照此方式排版。您可能会发现,上例中的caseSensitive字段与CaseSensitive属性之间并未留有空行,这是为了强调字段与其对应用于公开访问的属性之间的联系,关于类似情况,我们将在后面的章节详细讨论。然而,ー个空行意味着的不仅仅是功能模块的界限,它更是对代码逻辑块的划分。我们无法期望每个操作都只通过一行代码一条语句来完成,大多数情况下,它们都需要许多行代码来执行一个完整的操作。例如,你想查询数据库,那么你需要先生成SQL代码,建立命令,然后执行这个命令并填充至数据集。这中间大约需要使用四五行代码,而这四五行代码便组成了一个相对紧密的逻辑块,它与其后面的其他逻辑块即可以通过ー个空行来进行分隔。请看下面的一个例子:代码示例1T0:用空行分隔逻辑块publicstaticstring[]GetPhotoIds(stringfilterExpression,stringsort,boolcaseSensitive)(/Z第一个逻辑段代码根据处理后的参数取得数据行xml.Photos.CaseSensitive=caseSensitive;DataRow[]rows=xml.Photos.Select(filterExpression,sort??string.Empty);/Z遍历数据行,取出需要的元素string1]ids=newstring[rows.Length];for(inti=0;i<rows.Length;i++){ids[i]=(string)rows[i][*Id*];)//返回结果returnids;
这个函数的目的是根据指定的筛选条件和排序规则返回照片的标识号(PhotoIDs),函数内部自然形成了三个逻辑段:先是根据要求取得原始数据,然后从原始数据中提取我们需要的部分,最后将结果返回。用空行将这三个逻辑区分隔开来将会更加有利于我们理解其思路。关于注释的合理使用,我们会在后面的章节中再专门介绍。既然空行可以起到分隔代码,提高清晰度的作用,那么有的朋友也许会为了强调这种分隔效果,多加几个空行。可事实的效果是,连续的多个空行,在并未提高多少清晰度的同时,浪费了屏幕的空间,而且会让人觉得前后两个代码段并不相关——事实上它们应该是相继执行的。空行的意义和文章的段落ー样,仅在于表示一个停顿,而并非结束。1.2.2使用空格降低代码密度Basic、Pascal与C这三种早期高级程序设计语言的语法,至今仍在发挥着其重要的作用。VisualBasic仍然保留着Basic的句法简单,多用完整英文单词,贴近自然语序的习惯(如And、Not、!nherits,Implements,Handles等等关键字):而Delphi更是沿续着Pascal语言那标志性的BEGIN-END作风。C语言由于在操作系统开发上取得了成功,使得它在软件开发历史上占据了绝对的优势,相比而言,它的语法更加具有影响力,广泛被C++、Java、C#,乃至用于编写网页的ECMAScript/JavaScript和Flash的脚本语言ActionScript所吸纳,因此也变化丰富。但是它那种善用符号的古老特色一直被保留了下来,有理由相信,C语言是使用符号最多的语言。当其他语法体系都采用AND、OR等关键字作为运算符时,C语言却使用了“&&”、,ド’这样的符号,虽然在语法上并没有增加任何复杂性,但各种奇形怪状难以记忆的符号还是会令初学者望而却步。让我们来比较一下面的几行代码:BASIC: Ifa>bAndcOdOrNote>fThen...PASCAL: If(a>b)And(cOd)Or(Not(e>f))Then...C: if(a>b&&c!=d||!(e>f))...这三行的意义是完全相同的,但明显可以让人感觉到淸晰程度的差异,Basic和Pascal的代码看上去・很容易明白,而C语言的代码却像蚂蚁一般缩成一团。重要的原因在于:C语言的运算符几乎都只由’‘符号”构成,与变量名之间不需要用空格充当分隔符。这样ー来,由于缺少空格的稀释,C语言的代码就像被浓缩过似的——现如今它除了影响我们阅读以外,
没有什么好处。因此我们有必要人为地添加一点空格,帮它降低代码的“密度”。这里,我总结了一些关于如何在运算符两侧添加空格的基本规则:单目运算符(UnaryOperators)与它的操作数之间应紧密相接,不需要空格。例如:代码示例1T1:单目运算符的空格规则示例y=++x; //++在这里是前缀单目运算,它与x之间无空格在双目、三目运算符(Binary/TernaryOperators)的左右两侧分别添加空格。例如:代码示例1T2:双目、三目运算符的空格规则示例inta=3+5; //在双目运算符左右添加空格intb=a*6+7;intc=a&b;intd=b++*c-: /Z虽然有单目运算符,但双目运算符两侧仍应添加空格inte=a>0?1:0; //在三目运算符左右添加空格括号(包括小括号、中括号与大括号)的内侧应该紧靠操作数或其他运算符,不需要添加额外的空格。例如;代码示例1T3:括号的空格规则示例intf=(a+b)*c;/Z括号内侧紧靠操作数,因其他运算符添加的空格留在外侧intg[MAX]={1,2,3); //中括号与表达式中的大括号也同样处理不要使用连续的两个或多个空格。其实,如果理解了这些规则,在实际书写的时候很容易遵循。对于任何・个表达式,我们先把单目运算符和括号去掉,然后在双目、三目运算符的左右两侧分别添加一个空格,再将单目运算符和括号填回去,放在靠近自己操作数的ー边即可。关于函数调用时,要不要在函数名和其后的括号之间添加空格的问题已经讨论了很久。其实这个是ー个无伤大雅的事情,无论使用何种方式,都不会对代码的可读性产生多少实质性的影响,纯粹是各人喜好罢了。不过在这里,我建议采用VisualStudio中的默认规则:在函数调用时不添加空格,而在ー些类似的带括号的语法结构中添加空格。请看下面这段代码:代码示例1T4:函数调用时的空格规则示例
stringcmd=string.Empty;/Z函数形式的调用,括号前没有空格cmd=Console.ReadLineO;/Z语句结构,括号前有空格if(cmd.Length>0)(Console.WriteLine(cmd.ToUpper());)else{Console.WriteLine("(Empty)*);}这段代码中的ReadLine、WriteLine都是函数调用,因此与其后面的括号紧密相连,不需要添加空格。而if结构虽然同样带有类似的括号结构,但是它属于C#的内部语法,为了以示区别,在if与括号之间添加了一个空格。除if外,switch、for、while等都应做同样地处理。3如何缩进在有关代码风格的问题中,最为显眼的可以说就是代码的缩进(Indent)了。所谓缩进,是通过在每一行的代码左端空出一部分长度,更加淸晰地从外观上体现出程序的层次结构。为了更好地描述这一点,先请读者欣赏下列这段代码:intkmpmatch(char[]t,char[]p,int[]flink,intn,intm)(inti=0,j=0;while(i<n)while(j!=-1&&p[j]!=t[i])
j=flink[j];if(j==m-1)(returni-m+1;}i++;j++;)return-1;}我想,就算让你检查ー下它里面有没有大括号配对错误恐怕都很困难,更不用说这段代码有什么功能了——你能一眼看清楚每个while循环的内容是什么吗?让我们换个方式,看看另一段程序:代码示例1T5:正确缩进的例子intkmp_match(char[]t,chart]p,int[]flink,intn,intm){inti=0,j=0;while(i<n){while(j!=-1&&p[j]!=t[i]){j=flink[j];if(j=m-1)
returni-m+1;i++;jf)return-1;}两段程序,除了缩进的区别以外,一字不差。孰是孰非,相信大家都能看得出来,缩进的必要性不难理解。接下来的问题就是:应该如何缩进。1嵌套或包含关系引起的缩进当遇到有关命名空间、类、结构、函数、以及枚举等等复杂程序结构的定义的时候,我们通常需要将它的内容缩进ー层。在C#语言中,大括号是一个非常明显的标志,凡是遇到大括号,都应该直接联想到缩进。请看下面的示例:代码示例1T6:包含关系下的缩进namespaceMyNamespace(/Z命名空间内的内容应缩进publicclassMyClass{/Z类的成员应缩进publicstringMyMethodO(//方法函数的函数体应缩进return"Hello!;}privateMyEnummyProperty=MyEnum.Alpha;publicMyEnumMyProperty
//属性的内容应缩进get(//属性的get部分函数体应缩进returnmyProperty;)set{//属性的set部分函数体应缩进myProperty=value;publicenumMyEnum{/Z枚举类型内容应缩进Alpha,Beta,Gamma)}分支结构(包括if…else结构、switch结构等)和循环结构(包括for结构、while/d〇…while结构等)都是存在嵌套关系的,因此从条理清晰的角度来说,它同样应该进行缩进ザ写:代码示例1T7:嵌套关系下的缩进//if...else结构
if(a>b)//if子句的结构体内容应缩进max-a;min=b;}else{//else子句的结构体内容应缩进max-b;min=a;}//switch结构switch(n){//switch结构的内容应缩进case0://case子句内容也应缩进//...break;case1://...break;default://...break;
//for结构for(inti=0;i<100;i++)(//for的循环体应缩进s+=data[i];t*=data[i];}//while结构i=0;while(data[i]!=0)(//while的循环体应缩进s+=data[i];t*=data[i];i++;}缩进时,应将内部结构中的所有语句都统一向右退一格,大括号则与原来的语句保持在同一个垂直位置上。每层缩进的长度应该一致,通常为ー个制表符宽或四个空格。还有一些细节的地方也与换行相关,例如if、switch、for这类具有嵌套结构的语句,在书写的时候都应避免将结构体与语句本身写在同一行上,关于嵌套结构的书写方法,我们会在后面的章节中详细讨论。因换行而产生的缩进我们在前面提到过,当一条语句太长而超出一定的宽度时,应该折行书写。此时,从第二行起到该语句结束之间的各行应该缩进ー层,至下一条语句时再恢复原来的缩进位置。代码示例1T8:因换行而产生的缩进intmyVar=0;
/Z这是一条很长的语句,因而出现了换行,从第二行起都缩进了一格:myVar=myVari+myVar2+myVar3-myVar4-myVar5*myVar6*myVar7/myVar8/myVar9+myVarlO+myVar11-myVar12-myVar13*myVarl4*myVarl5/myVar16;//后面的语句恢复正常的缩进位置Console.Write(myVar);如果该语句是进行函数调用,由于参数太多而造成的换行,那么在缩进规则上有一些微小的差别:代码示例1T9:函数调用时分行书写参数而引起的缩进RectangleimageBounds=newRectangle(itemBounds.X+padding,itemBounds.Y+padding,itemBounds.Width-padding*2,itemBounds.Width-padding*2);注意最后一行的括号与分号并没有缩进,因为这种结构其实是对类似if的大括号嵌套结构的模拟。使用空格还是Tab键如何缩进一向是ー个有争议的问题。使用Tab及Shift+Tab键缩进在操作上非常方便,而使用空格可以保证代码在任何编辑器下都能正确显示缩进格式。现在,我们依靠VisualStudio开发环境则可以轻松解决这个矛盾:在“选项”对话框中对C#编辑器的代码缩进方式进行设置(如图!-2),选择“插入空格”模式。
图!-2:VisualStudio中关于代码缩进的设置⑵.这样一来,我们就可以在书写代码时使用Tab键和Shift+Tab键来调整缩进,而VisualStudio会将其转换为空格保存至代码文件中。1.4大括号从外观上来看,类C语言的最大标志就是它无处不在的大括号了。在C#中,大括号仍然扮演着几种不同的角色:表示层次关系(如定义命名空间、类时使用的大括号)、表示复合语句(如if、for中的大括号)、表示数组元素。本节讨论了有关大括号书写的几个基本问题。1.4.!大括号的位置代码风格中,如何摆放大括号一宜是大们热衷的话题。其实,我更愿意用开放的态度去看待它,具体使用何种方式并不重要,重要的是,要保持方式风格的统ー,不能在同一个项目中出现不同的风格。最早出现的,也较为传统的是K&R风格。所谓K&R即指《TheCProgrammingLanguage》一书的作者Kernighan和Ritchie二人,这是世界上第一本介绍C语言的书,而K&R风格即指他们在该书中书写代码所使用的风格。K&R风格在处理大括号时,使用了・种较为紧凑的格式,将左括号留在前一行的末尾,并尽可能地压缩:代码示例120:K&R大括号位置风格publicintMax(intx,inty){returnx;}else{returny;)}据说,微软公司内部使用的就是这种K&R风格,然而它在对外公开的文档中,却使用更为大家所熟知的ー种风格,它将大括号单独写成一行。本书所有示例代码采用的都是这样•种格式:代码示例『21:C#默认使用的大括号位置风格publicintMax(intx,inty)(if(x>y)(returnx;)else{returny;)}虽然生硬地规定应该使用哪・种是不提倡的,而且也没有必要,但是我们仍然建议读者尽量选择上述两种风格中的一种,并在自己的程序中保持风格的统ー。空的大括号结构所谓复合体,即指用于充当某个语法结构成份的,被大括号括起来的多条语句。C#的很多语法结构中都可以见到复合体的使用,如命名空间、类、结构、接口、枚举、方法、属性等的定义,以及if、switch,for、while-d〇…while、try…catch…finally结构等等。对于应该如何处理大括号的摆放以及内容的缩进排版等问题,我们都已经详细讨论过了,现在要考虑的是复合体自身的ー种特殊情况:空的复合体。
接下来的问题是:为什么会出现空的复合体?有的时候来自于废弃的空函数。开发过程中,很可能只写了某个类或者函数的空声明,打算待日后再细化。然而由于设计上的变动,这个函数不再使用,而躲在代码某个角落的这个空函数又未能被及时删除,结果一直保留到产品发布。在这种情况下,建议在空复合体中添加“TODO”的注释:代码示例1-22:为未实现的空函数体中添加TODO注释publicvoidUnusedMethod()(//TODO:未实现的方法)因开发时的疏忽造成的空复合体还不仅仅是上面这ー种情况,我曾见到过有人将if结构中的if段留空,却在else里写上一堆代码,类似下面这样:if(table.Rows.Count—0)(}else(foreach(DataRowrowintable.Rows)(Console.WriteLine(row["Name"]);}}也许他本来在if中是有代码的,后来程序不断地修改,结果在if段中无事可做了。这种情况也很容易避免,只需很简单地将if中的条件表达式反转过来即可:代码示例123:反转if条件表达式以避免空的if子句if(table.Rows.Count>0)foreach(DataRowrowintable.Rows)
Console.WriteLine(row["Name"]);如果不是开发时的疏忽,那么空复合体的出现就是有意而为之了。在循环结构中,它出现的频率相对较高。早期,人们会采用空循环的方法来达到“延时”的效果,那个时候常常会看到类似这样的代码:printf("Waiting...\n");for(i=0;i<10000;i++){)printf("Done.");或者干脆连大括号结构都没有,直接就是printf("Waiting...\n");for(i=0;i<10000;i++);printf("Done.");且不说现如今这种“延时”方式完全不可取,就从代码的外观来看,它也极易让人将for下面的那一行printf输出代码当成是循环的内容。由于C#语言继承了C语言语法灵活的特点,因此循环语句本身就能承担很多复杂的工作,以至于根本就不需要循环体。下面的代码反映了一种典型的状况:intsum=0;for(inti=0;i<table.Rows.Count;sum+=(int)table[i++]["Amout"])(}这个for语句本身就把累加工作给做完了,使得循环体内已经无事可做。虽说从语法角度来说没有多少问题,但是它的可读性并不好,我们仍然希望for仅仅是做ー些“循环”本身的事情,将具体的数据和逻辑操作放在循环体内,就像下面这样:代码示例1-24:让for循环语句本身仅控制循环,不要涉及具体事务intsum=0;
for(inti=0;i<table.Rows.Count;i++)sum+=(int)table[i]["Amout"];)这看起来更像ー个for循环所体现出来的含义。这样总结下来,似乎空复合体完全不应该出现在代码中,然而有一种情况下它的确可以而且应当存在,这就是构造函数。构造函数与其他的函数不同,即使没有任何一行代码内容,它本身的存在与否也有着极其重要的意义——直接决定这个类型是否能够被实例化。因此,当我们不希望某个类被外部实例化的时候,哪怕我们并不需要自定义构造函数,我们仍需要为该类型添加一个非公开的空构造函数。为了避免日后将这个空函数当成是没有用的废弃函数,我们有必要特别加以说明:代码示例1-25:通过注释强调空构造函数publicclassPhotoCollection:lEnumerable,IEnumerable<Photo>(internalPhotoCollection()(/Z空构造函数)/Z其他类成员已省略)其他情况下,如果确实有必要使用空的赁合体,也应当仿照上例书写,添加相应的注释说明,以防止产生误解。绝不能省略大括号结构,仅以ー个分号代替。仅包含单个语句的结构体我们再来讨论另ー种特殊的复合体。按照C#的语法,如果if、while、for等等的结构体仅为一条语句,那么大括号是uj"以不写的——其实复合体就是将多条语句合并起来充当单条语句来用的。甚至我们可以将结构体与if、while,for写成一行,例如:if(a>b)x++;
elsey++;或者是:for(inti=0;i<10;i++)dest[i]=source[i];虽然这在语法上没有什么问题,但当代码数量增加,上下文的代码变得熨杂时,我们有可能会对if、while,for的结构体究竟包含哪些语句产生误解。因此,我们建议,即使if、while、for结构的内容只有一条语句,也应该像处理多条语句那样,写在大括号内。因此,刚オ那两段代码就应该写成下面这样:代码示例1-26:即使结构体内只有一条语句,也必须写在大括号内if(a>b)(x++;}else(y++;}for(inti=0;i<10;i++)(dest[i]=source[i];}有的读者可能看不下去了:明明很简单的两三行代码,为什么要搞得这么麻烦?首先,要肯定的是,这并不会影响代码的实质。添加大括号结构是为了更加清晰地体现层次关系,使得任何人在扫一眼代码之后,不会对此处的逻辑结构产生误解。在实际的开发中,我们遇到的问题远比上面给出的要复杂,例如:代码示例1-27:ー个较为复杂的if嵌套privatestaticboolIsValidDate(intyear,intmonth,intday)
if(day<1||month<1||month>12)returnfalse;)if(month=2)(if(year%4=0&&(year%400==0|year%100!=0))(returnday〈=29;)else(returnday〈=28;))elseif(month=4month==6|month==9|month==11)(returnday<-30;)else(returnday<=31;)}这是ー个判断给定的日期是否合法的函数,它包含了多个条件判断的嵌套,如果不使用大括号,那么阅读起来,产生误解的机率就会大大增加。因此,即使if、for、while语句的结构体中只包含一条语句,也应该置于大括号内。
1.5保持项目文件的条理性如果说我们之前讨论的代码风格问题仅仅局限于单个代码文件中的一个局部,那么在本节中,我将从更高的层次来展示这种条理性。包括解决方案的结构呼应,代码文件的结构,以及#region标记的使用。1.5.1解决方案的结构呼应C#程序的最高层次是“解决方案(Solution)",每个解决方案包含ー个或多个“项目(Project)"〇这些项目也许是Windows窗体应用程序,也许是DLL程序集,或者是ー个Web站点。项目由于其自身的特点,在划分时通常不会存在什么争议図。这里我们主要讨论项目内部的组织结构。当我们在项目中建立文件夹时,Ct会自动将文件夹作为子命名空间的名称。例如,如果原项目的默认命名空间是MyProject,我们新建了一个名为SubFolder的新文件夹,那么这个文件夹下的新建代码文件就会自动以MyProject.SubFolder作为默认的命名空间。我们建议保持这样的ー个呼应规则,通过文件夹直接反映命名空间的层次结构,而不要将文件夹作为附加的给代码文件进行分类的手段,比如将同一个命名空间下的代码文件归入“类”、“接ロ”、“结构”等子文件夹中去,这不仅给开发的过程带来麻烦,也破坏了命名空间名称与文件夹结构之间的呼应关系。对于每ー个代码文件(指后缀名为cs的文件)来说,我们通常会为每个类单独建立文件,但当人们面对ー些尺寸较小的类型,尤其是枚举时,就会将其与需要它们的类型定义在同一个文件里。例如Logger类也许需要为Logged事件提供LoggedEventArgs类型的数据,而这个LoggedEventArgs类经常会被人图方便与Logger类定义在同一个文件里。我要强调的是,虽然C#允许在同一个代码文件里定义多个类,但为了使得工程结构更为清晰,请不要那样做。我们应当恪守一个原则:ー个代码文件中只定义ー个类型。这里的类型包括类、结构、接口、委托或枚举。而且,这个代码文件的文件名应当与类型的名称完全相同。例如Logger和LoggedEventArgs两个类密切相关,甚至除了Logger类以外,不会再有第二个类型会直接使用到LoggedEventArgs类型,但是即便如此,我们仍应将其分开写在不同的代码文件中,分别命名为Logger,cs和LoggedEventArgs.cs。如果一个代码文件里声明的是一个枚举,那估计它的内容也不会太长,出不了什么乱子。如果是ー个有着几十个成员的类,事实恐怕就要复杂得多了。有没有过在茫茫代码中看花了眼,怎么也找不到需要的函数的经历?我曾经花了半分多钟オ从一位朋友的代码中找到某个类中实际进行实例初始化的静态方法。改变类中各成员的前后顺序并不会对程序的运行产生任何影响,但如果能坚持以特定的顺序排列成员的话,确实会大大提高我们阅读代码的效率。至于如何排列,并没有唯一的标准,我下面给出的仅是ー种参考:总体上按照public、internal、protected,private的访问控制顺序排列;总体上按照构造函数、Finalize函数、字段、属性、方法、事件、委托的类型顺序排列;尽可能将相关性强的成员排列在ー起。上述三条表述仅仅是一种确定排列顺序的大体方式,并不可能完全遵守,具体情况则要复杂得多。出现在最前面的应该是构造函数,这是由构造函数自身的特殊地位决定的。在使用ー个类的时候,首先接触到的就是构造函数,因此我们有必要使构造函数出现在最为显眼的位置。如果有多个构造函数,则按照public、internal、protectedxprivate的顺序放置。绝不要将private构造函数放在其他成员的后面,因为对于类来说,“没有构造函数”与“没有公开的构造函数”是两个完全不的概念。如果这个类没有定义任何构造函数,那么事实上编译器会其添加一个默认的公开构造函数;相反,虽然没有公开的构造函数,却有一个private构造函数,那么这个类将不再具有默认的公开构造函数,成了一个不可从外部实例化的类。因此,即使构造函数是私有的,也要写在类的最前面。接下来是Finalize函数,这是承袭了C++中的习惯。Finalize函数的情形非常简单,要么没有,要么只有一个。构造函数与Finalize函数之后的,是所有可被公开访问的成员!J!。首先是符号常量字段。这个习惯来自于C/C++中#define位于最前的规定,这也与其他语言中对于符号常量定义的习惯相一致。其后是属性,具体的顺序以体现逻辑相关性为佳,当然也可以按照字母顺序排列。例如,同样是表示电子邮件的MaiIMessage类的几个属性,就可以采用下列两种排列方式:
From(发件人)AttachmentsTo(收件人)BccCC(抄送)BodyBcc(密件抄送)CCAttachments(附件)FromSubject(主题)SubjectBody(正文)To左边是以逻辑顺序排列的,右侧是以字母顺序排列的。当遇到具体情况时,可以根据其自身特点选择合适的排列顺序,在这个电子邮件的例子中,以逻辑顺序排列就要比字母顺序好得多。此外,很多属性的值是保存在相对应的私有字段中的,对于这种情况,我们习惯上将该私有字段与属性访问器写在ー起,类似于下面这样:代码示例1-28:私有字段与属性访问器的相对位置privateintage=0;publicintAge(get{returnage;)set(if(value<0)(thrownewArgumentOutOfRangeException("Age","Agemustbenotlessthanzero.");age=value;
这样做可以让人一眼看出age字段与Age属性的关系,它们两者之间的相关性极强。这也可以避免阅读到Age属性代码的时候,再到处寻找age的定义。方法通常是一个类的主体,因为它不仅用于向外界公开操作,还为内部提供了丰富的可反复调用的函数。排列在属性之后的即是公开的方法成员。通常静态方法在前,实例方法在后,其中各方法的相互顺序与属性情况类似,既可以根据逻辑关系排列,也可以根据字母顺序排列。对于方法的多个重载(Overload)来说,应当将同一个方法的公开的重载连续写在ー起,并根据参数的数量由简至繁排列。例如File类的Open方法具有三个公开的重载:FileStreamOpen(stringpath,FileModemode)FileStreamOpen(stringpath,FileModemode,FileAccessaccess)FileStreamOpen(stringpath,FileModemode,FileAccessaccess,FileShareshare)由于通常情况下,简单的方法重载都是对复杂重载的调用,因此这样的顺序较好地符合阅读者的思维走向。公开方法之后依次排列的应为事件、委托、对接U的显式实现等,它们的内部顺序与前面所述的大同小异,这里不再赘述。直至给出所有公开成员之后,オ是受保护成员与私有成员,其内部顺序与公开成员相似。5.3使用#region标记在C#中,有一个标记与预处理命令非常相似,然而它却并不会对程序的编译与运行产生任何影响,仅仅在外观上起到隐藏细节的作用,这就是"#region/#endregion”标记。之前,我们提到受保护成员和私有成员应排列在所有公开成员之后。我们完全可以通过将受保护成员和私有成员分别放入#region/#endregion标记中,来隐藏受保护和私有的成员,仅展示公开的成员。密切相关的ー组成员也可以通过这种方式来突显它们的整体性,使得代码结构更加清晰。对接口的显式实现便是ー个绝好的例子。用于显式实现接口的成员很少会被直接调用,必须经过强制接口类型转换,那么将一个接口的所有显式成员包含在ー起,既不会干扰视线,也更富条理性。阅读代码的人会知道这个类已经实现了这个接口,至于这个接口有什么用,包含什么成员,那是另外一回事了,可以通过查看那个接口的定义来了解。诸如窗体类型及其他UI控件类型这样的代码文件屮总会充斥着大量的事件处理过程,而它们本身往往只是对其他函数方法的简单调用,并没有什么实际的内容。如果将所有事件处理过程也用#region/#endregion标记起来的话,可以减少冗长的事件处理函数过程,使得代码结构更加清晰。#region的用途还可以扩展到其他方面,如果有一组私有函数是集中对某个功能的实现,那么为了清晰起见,也可以将其归入ー个Region结构中等等。总之,我们的终极目的是仅直接暴露那些公开的成员,尽可能地隐藏细节,并使得代码看上去整洁,避免成员过多而显得臃肿。第2章养成良好的注释习惯注释(Comments)在程序中的作用极其微妙,它既是最不重要的,也是最重要的。说它最不重要,是因为将任何程序代码中的所有注释统统删掉,丝毫不会影响软件的运行,或者说,注释根本不会被编译输出到最终的软件产品中去。而说它最重要,是因为注释是程序代码中唯一采用自然语言书写的部分,它对代码是否能被阅读者正确而快速地理解起着决定性的作用。正因为如此,这种完全不会影响程序运行的’‘代码”,往往在程序中占据了最大的比重。不同的人在编写程序是总会有不同的思路,因此在阅读别人写的代码时,常常存在理解上的障碍。何况时间久了,有时连自己的代码都不一定能看得懂。为代码添加注释可以提高效率,提高代码的可读性,便于团队合作,同时也可以增强代码的逻辑性。在C#中,注释还被赋予了更为重要的任务。ー项名为“XML文档注释(XMLDocumentation)”的技术使得C#编译器可以将C#代码中的这些特殊注释提取编译为程序集的描述文档,它的内容信息可以用于编辑器的智能感知(IntelliSense™)系统,也可进ー步转换为我们需要的参考文档格式(例如MSDN的类库参考)。摆在我们面前的第一个问题即是“何时需要注释”,其实更本质的ー个问题是‘‘为什么需要注释”。在本章的引言中,我已经提到了一些,现在需要进行更深入的讨论。理解高级语言比起理解底层的机器语言来说已经容易得多,但直至今日,计算机语言与人类自然语言之间依然存在着不小的差距。从最基本的需要来说,注释是为了填补这两者之间的空白,起到ー个思维连接的作用。可能开发人员对计算机语言的熟悉程度不够,或者它在此处选用了相对较为陌生的技术或构件,注释可以对他自己或他人起到提示的作用,从而帮助理解和开发。如果我们假定开发人员对自己所用的语言和技术都是非常熟悉的话,那么注释还需要吗?事实告诉我们:需要。虽然在绝大多数情况下,开发人员都熟知自己所用的语言和技术,但注释仍然大量存在,可见对陌生内容的“注解”并非它的主要意义。事实上,使得注释存在的最为关键的原因是:人类进行程序设计与程序代码本身并不在同一个逻辑思维层面上。人们思维的时候是“任务式”的,而程序代码则必须是“过程式”的。人们在设计的时候,也许只是考虑“将这组数据由小到大排序”,而程序代码则需要数行甚至数十行才能完成,这种细节过程在某种程度上反而会影响人们对程序整体思路的理解,此时便需要注释来体现人们的设计意图。1.1解释代码的意图代码本身已经非常详尽地体现出它将如何工作,因此注释起到的应该是一种思维抽象的作用,通过符合人类思维方式的描述性文字从更高的思维层次阐释细节代码的功能与意图。从这个角度来说,我们很容易就可以看出,下面的注释是没有意义的:/Z定义ー个整型变量intn;这个注释并没有说错什么,但是它说了一句废话。这行短短的代码本身非常清晰地表达了“定义一个整型变量”的意思,它甚至比这个注释的内容更丰富——这个变量的名称是“n”。这条注释仅仅对那些看不懂程序语法的人来说オ有价值,
而这种情况在真实的软件开发中是不存在的。相比而言,下面的注释就要有意义得多:代码示例2-1:有意义的注释/Z交换a、b变量的值temp=a;a=b;b=temp;三行代码仅仅是三个赋值操作,而这条注释给出了比三条个赋值操作更为抽象的意义,它反映出了这三个赋值操作的真正意图,因此这是一条有价值的注释。1.2对局部变量的说明如果变量的名称本身无法非常清晰地体现它的所有含义的话,就有必要通过注释来进行说明。对于成员变量来说,我们习惯上使用XML文档注释(我会在本章稍后的时候专门讨论),而普通注释则多用于说明局部变量。下面则是ー个很好的例子:代码示例2-2:对局部变量意义的注释/Z构成路径的Bezier曲线控制点集Point[]pathVectors=null;这条注释很全面地解释了变量pathVectors的作用,现在我们不但知道它是ー个Point结构数组,还知道了这个Point数组中存放的是构成一条路径的Bezier曲线控制点。相反地,如果变量名称所表达的信息已经非常完备(如currentDateTime),那么则可以不需要通过注释加以重复。很多情况下,数据的合理取值范围会小于(而且往往是远远小于)其数据类型的允许范围。对于颜色信息中的红色分量(当然也可以是绿色或蓝色分量)来说,我们也许可以猜到它的取值范围是0至255的整数,因为几乎所有的程序都会这么做。然而,如果换成透明度会怎么样?还是从〇至255的整数吗?有没有可能是从0至100的整数?或者是从0.0至U1.0的浮点数?〇究竟表示完全透明还是
完全不透明?这些我们都无法确定,因为各种情况都在实际的程序中出现过。此时,在注释中进行解释是有必要的:代码示例2-3:对局部变量取值范围的注释/Z用于控制图像的透明度,〇表示完全透明,1表示完全不透明。doubleopacity=1.0;这样ー来就避免了因不一致而导致的难以觉察的错误。除了取值范围之外,数据所使用的数量单位也是至关重要的。图像的长度宽度数据并不一定总是以“像素”为单位,它也可能是“点"、“磅”、“厘米”或者“英寸”,当数据的单位可能会存在混淆或误解的时候,就有必要在注释中加以说明:代码示例2-4:对局部变量数量单位的注释/Z图像的尺寸,以英寸为单位SizeFimageSize=newSizeF(5,3.5);对于i、j之类的循环变量,由于其作用在所有的程序中都是显然易见的,可以不必添加说明。反之,如果与普通的循环变量相比存在着一定的特殊性,那么就应该考虑使用其他的循环变量名称,我们将在第6章“循环结构”(第107页)中详细讨论这ー点。2.1.3充当代码标题注释的另ー个常见的用途是以类似小标题的方式对代码进行提纲契领地描述。这与前面我们所说的“解释代码的意图”是一致的,不过我们要从另ー个角度来考虑它。单独考虑下面这一行注释,似乎它显得有些多余,因为它并没有能给出比代码本身更丰富的信息:/Z绘图像graphics.Drawlmage(attachedPhoto.Image,imageBounds);但是如果把它放在上下文中之后,我们就会发现,此处的注释很好地起到了概括的作用:代码示例2-5:起到概括作用的标题式注释
/Z设置图像边框区域RectangleimageBorderBounds=newRectangle(imageBounds.X-borderWidth,imageBounds.Y-borderWidth,imageBounds.Width+borderWidth*2,imageBounds.Height+borderWidth*2);/Z设置文本区域RectangletitleBounds=newRectangle(itemBounds.X+padding,itemBounds.Y+itemBounds.Width,itemBounds.Width-padding*2,itemBounds.Height-itemBounds.Width-padding);/Z准备绘图graphics=Graphics.Fromlmage(bitmap);graphics.InterpolationMode=InterpolationMode.Bicubic;graphics.SmoothingMode=SmoothingMode.AntiAlias;graphics.TextRenderingHint=System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;/Z绘边框graphics.Fi1IRectangle(Brushes.White,imageBorderBounds);graphics.DrawRectangle(Pens.LightGray,imageBorderBounds);/Z绘图像
graphics.Drawlmage(attachedPhoto.Image,imageBounds);/Z绘标题graphics.DrawString(title,SystemFonts.MessageBoxFont,titleBrush,titleBounds);graphics.Dispose();一眼看过去,我们可以很清楚地掌握这段代码的总体结构,一旦发现问题需要修改的时候,也可以很快地定位到可能出现错误的两三行代码处。对于分支与循环控制结构来说,标题式的注释也很常见,需要注意的一点是,注释的表述要符合控制结构本身的特点,并且要留意注释的位置,避免混乱。例如下面的这个if结构://当日志文件不存在时,提示错误信息if(File.Exists(logfile)==true)(MessageBox.Show("找不到指定的文件。”);)看上去似乎没有什么问题,但“提示找不到信息”是在这个if条件成立时オ会发生的动作,它属于if的结构体内容,与注释目前的位置并不相称,试想如果这个结构后还有else子句,那么是否也要将它们写在一起?//当日志文件不存在时,提示错误信息,否则将其读入if(File.Exists(logfile)==true)(MessageBox.Show("找不到指定的文件。”);)elseLoadFile(logfile);
在这样短的if结构中,这也许还不至于造成麻烦。假如if结构体中的语句数量远远不止这ー两行代码,甚至整个if结构的长度超过你屏幕能显示的范围时,这种形式的注释就会产生一个基本的错误:离被注释的代码太远。因此,我们应当将注释的内容分开,采取更符合逻辑的方式,在判断还未发生之前,不要引入判断之后的事情:代码示例2-6:对if结构的注释方法/Z检查日志文件是否存在if(File.Exists(logfile)==true)E//如果不存在,则弹出错误提示MessageBox.Show("找不到指定的文件。”);)else(//如果存在,则将数据读入LoadFile(logfile);)类似地,switch结构开始处的注释应当仅仅对整个switch需要判断的内容进行说明,至于每种情况之后采取什么动作,则应留到各个case子句后再去注释。循环结构则与分支结构有所不同,由于事实上循环结构是对ー组对象分别进行相似的操作,因此位于循环语句之前的注释则应重点描述整个循环的迭代方式,而循环体内的注释则是描述如何处理每次迭代的对象。例如:代码示例2-7:for/foreach循环的注释方法/Z遍历所有选中的文件名称foreach(stringfilenameindig.Filenames)//以追加方式打开文件
StreamWriterw=File.AppendText(filename);/Z写入当前的日期和时间WriteLine("Date:{0}",DateTime.Now.ToLongDateStringO);WriteLine("Time:{0}",DateTime.Now.ToLongTimeStringO);/Z关闭文件Close();)指出例外情况有时为了达到某种特殊的效果,或者由于某种客观原因,我们需要在代码中使用ー些非常规的手段,甚至别人看起来会误认为这是一段错误的代码。如果某个“好心人”动手帮你改掉了这个“笔误”的话,那可真是帮了倒忙了。为了避免这种情况的发生,凡当我们使用了非常规的手段或技巧时,一定要加以注释,说明并不是我们大意疏忽,而是有意为之,不仅如此,还应当解释说明为什么要采用这种反常的做法。在前一章有关大括号结构的讨论中,我们就已经提到,当我们确实需要使用ー个空的结构体或函数体时,不能简单地空置,而需要在这个空的大括号结构内添加注释,表示这个空结构并不是笔误或者未完成的代码,而是它确该如此。开发提示任何一个程序的开发总是需要经历ー个过程的,而代码也是由简单粗糙逐步走向完善健全的。起初,也许我们只是实现了某个函数的基本功能,但是并没有完善它,还缺少许多诸如数据验证、异常处理之类的功能。此时,为这段代码加上ー个注释是不错的选择。VisualStudio本身支持一些特殊的注释文本,称为“任务列表”。当我们在注释中使用这些特定文本的时候,编译器可以将它们收集起来。这其中最为我们所
熟知的就是“TODO”注释,它通常可表示一切类似“还需要进ー步的工作”的含义。例如下面这个函数:代码示例2-8:TODO注释protectedQuestion(Questionnaireowner,XmlElementxmlElement):base(owner)I//TODO:验证xmlElement的结构xml=xmlElement;)我们可以通过“任务列表”窗格(从“视图”菜单中选择“任务列表”项)查看到所有的任务列表清单,如下图所示:TODO;KS QutftfofUf49图2-1:VisualStudio中的“任务列表”窗格[51除了“TOD〇”之外,还可以使用“HACK”和“UNDONE”标记。事实上,我们还可以自己创建这类特殊的注释文本。然而,从清晰的角度来说,使用“TODO”已经足够了。注释的格式C#沿用了与C/C++完全相同的注释语法,“〃”可以注释至行末,“/小••*/”可以注释任意位置的一段文字。虽说这只是简简单单的两个符号,但经过一代又一代程序员的奇思妙想,注释的形式层出不穷,到处可以见到排版精美,镶嵌着漂亮花边的注释,比如:/・ 这是ー个排版精美的注释 ・/这种注释非常常见,它也有许多变种,下面的这ー个来源于MINIX操作系统的源代码倒:
这是上一个注释的变种*/其他各式各样的形式相信你也有所目睹,然而ー个基本的问题随之摆在我们面前:注释需要如此精美的外观吗?别忘了我在上一节里所进行的讨论,注释的重要意义在于使代码的思路更为清晰,而不是给程序添加一些装饰。很多人热忠于这些复杂的注糅格式,也许是为了达到强调的目的,使得某些注释比其他注释更为“醒FI”——这无可非议,上面那个从MINIX操作系统源代码中摘出的注释就是起到这个作用:它在每个函数的开始处使用这种重量级的注释来加强函数的名称,而在其他地方只使用了常规的注释格式。我并不反对使用某种方式来对注释进行强调,但我反对在调整精美的注释格式上花费过多的时间。上面提到的这些注释格式都需要程序员亲手ー个字符一个字符地输入,手动对齐左右的星号,将文字居中,如果注称内容很长的话,工作量更大。假如日后对程序进行了修改,从而需要修改注释的时候——这经常会发生——如果你删去了前面一行中的ー两个字,那么后面每一行都需要调整:要么你选择让前一行缺两个字,给你精美的注释留下一处遗憾;要么你就得花费许多Del键和空格键的操作来重新建立这个漂亮的方框。这个问题发展到极端就会出现一个可怕的结果:为了保持注释的美观,干脆不修改注释。这不是完完全全本末倒置了吗?事实上,由于C#中支持使用XML文档注释来为关键的类型和成员添加说明,普通注释基本仅限于在函数的内部使用。这样ー来,大动干戈去搭建复杂的注释样式就显得没有必要了,常规的不加任何修饰的注释格式完全可以满足我们的需要。如果你仍然觉得不够清晰,可以使用#region来整理,不过此时往往意味着你的函数写得过于复杂了,应该考虑分解为更多的函数。.1单行注释单行注释是最为常用也是最灵活方便的,可以自成一行,也可以置于行末。自成一行是较为普遍的做法,它位于要注释的代码之前,与要注释的代码块在缩进上保持一致,之前最好留有一个空行。例如下面的这一段代码:代码示例2-9:单行注释样例protectedvoidbtnAddNewClick(objectsender,EventArgse)
//定位控件TextBoxtxtChoiceText=tableChoices.FindControl("txtChoiceText")asTextBox;/Z创建新选项数据对象Choiceitemnewitem=newChoiceItem(owner);newitem.Text=txtChoiceText.Text;//添加数据Additem(newitem);/Z显示ShowListltem(newitem);txtChoiceText.Text=}在书写时要注意,“〃”之后与注释文本之间应留有一个空格,避免拥挤。下面的一些格式已经不再适用,应避免使用:// 居中的注释文本以及,//=================带有イヅ号装饰的注释文本==================这种注释形式需要手工进行居中排版,当需要修改时显得很不方便。也不要使用,ソ・…*/”来标记单行注释,这会显得非常古怪且没有必要。对于ー组并列的注释来说,如果屏幕空间允许,那么放置在行末也是不错的选择。下面给出的是使用行末注释的典型情形:代码示例2-10:行末注释样例//包含各状态的缓存图//正常状态privateBitmappaintNormal=null;//正常状态privateBitmappaintHovering=null; /Z悬停状态privateBitmappaintSelected=null; /Z选中状态privateBitmappaintHoveringSelected=null;/Z选中并悬停状态它唯一的缺点是,一旦代码部分被修改,可能会破坏注释部分原有的对齐,产生额外的代码格式化工作量。2.2多行注释多行注释主要
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- GB 40248-2026人员密集场所消防安全管理
- T/SILA 012-2023家居照明光健康规范
- T/SAME 001-2022临床实验室II级生物安全柜管理要求
- T/CES 316-2024光伏电站能量流测试技术规范
- T/CHSA 104-2025咬合板治疗颞下颌关节紊乱病的专家共识
- T/CSTEA 00027-2021大田美人茶
- T/SRMA 40.3-2026基于国际功能、残疾和健康分类的结构化康复病历书写指南 第3部分:帕金森
- 2025~2026学年河北省邢台市任泽区骆庄学区小学联考冀教版三年级上册12月月考数学试卷【附答案】
- 2027届河南省鲁山、舞钢九年级数学第一学期期末学业质量监测模拟试题含解析
- 车间级日常安全管理实务培训
- 2024花粉-食物过敏综合征诊断及管理
- CADinventor-三维建模练习118个
- 中华民族共同体概论课件第五讲大一统与中华民族共同体初步形成(秦汉时期)
- 宠物医院员工手册
- 医联官方平台查报告
- JGT160-2017 混凝土用机械锚栓
- 表B. 0 .12施工进度计划报审表
- 四年级上册《一个豆荚里的五粒豆》课本剧
- 第三章DICTRA动力学计算软件简介课件
- p0020电芯上料料框滚筒线技术协议
- 煤矿用CAD线型文件
评论
0/150
提交评论