17611538698
webmaster@21cto.com

为什么我不赞成在代码里加注释?

资讯 0 2697 2017-04-10 12:00:49

21CTO社区导读:
  本文来源于Quora,作者有两位,分别是Magnus Falk,有十年的开发和大型分布式系统。以及Matt Godbold20余年编程经验。


  /uploads/fox/10165149_0.jpeg
  关于代码里的注释,我给大家说明的观点有如下。
  概述
  关于注释,首先它是有用的,通常情况下都需要。但是我认为,没有注释会更好。
  下面来向各位解释我的理由:
  我们的目标是编码。如要有人来通读它,需要完全清晰它的作用是什么。包括名称,结构,代码背后的思维等,这些应该最大化的开放、清晰、透明。
  当我自己写注释时,会遵循增加更多详细以及增进的事情。我还期待我未来会再这些代码,不希望出现迷惑。所以我主要编写注释去增强我的未来。我想我此时的代码并不怎么清晰。
  这并不是说注释不好。但是想让代码开发的更清晰,这个注释可以不需要。这是因为一些时候,注释与代码的功能未必匹配。
  以下是代码的几个层次:
  第一层:最差
  复杂。代码不透明,而且什么注释也没有。
  第二层:较好
  复杂,不透明的代码与注释。现在大部分现实情况的程序员都是这样。
  /uploads/fox/10165149_1.jpeg
  还有的程序员这样,实在是不该。
/uploads/fox/10165149_2.jpeg
  第三层:更好
  简单,透明的代码与注释。
  /uploads/fox/10165149_3.jpeg
  第四层:最好
  代码写得如此营养丰富,注释根本不需要。
  关于编码
  我们在编码时,一定要干净,清晰,绝大多数还是需要用注释。因为大多数的程序还处于前三层,第四层是我们要达到的目标。
  那么,什么时候需要注释?
  1. 当代码写的功能较复杂,或者看起来不易读,请使用注释;
  2. 如果执行结果不明显时,请使用注释;
  3. 当调用的方法上下文不明显时,请使用注释;
  4. 注释也不能只写晦涩的一行,为了写明白,有时候你要像写散文一样清晰优雅,无论是中文还是英语。

  代码整洁之实践
  以下是代码整洁的一些具体实践,和语言无关,供大家参考。
  1.如何拒绝注释,用代码来阐述注释
  反例如下:
  /// <summary>/// !@#$%^&^&((!@#$%^&^&((!@#$%^&^&((/// </summary>/// <returns></returns>publicdecimal GetCash(]i{//!@#$%^&^&((var a=newList<int>(]i{2,3,10};var b =2m;var c =0m;//!@#$%^&^&((!@#$%^&^&((foreach (var p in a]i{c +=pb;}returnc;}
  重构后的代码如下:
  publicdecimal CalculateTotalCash(){var itemCounts=newList<int>(){2,3,10};var price=2m;returnitemCounts.Sum(p =>p*price );}
  良好的代码命名完全可以替代注释的作用,如果你正在试图写一段注释,从某种角度来看,你正在试图写一段别人无法理解的代码。
  当你无法为你的方法起一个准确的名称时,很可能你的方法不止做了一件事,违反了(Do one thing)。特别是你想在方法名中加入:And,Or,If等词时。
  2. 为布尔变量赋值
  反例:
  publicbool IsAdult(intage){bool isAdult;if(age >18){isAdult =true;}else{isAdult=false;}returnisAdult;}
  重构后:
  publicbool IsAdult(intage){var isAdult =age >18;returnisAdult;}
  3.双重否定的条件判断
  反例:
  if(!isNotRemeberMe){}
  重构后:
  if(isRemeberMe){}
  不管你有没有见过这样的条件,反正我见过。见到这样的条件判断,我顿时就晕了。
  4.拒绝HardCode,拒绝挖坑
  反例:
  if(carName =="Nissan"){}
  重构后:
  if(car ==Car.Nissan){}
  既然咱们玩的是强类型语言,咱就用上编译器的功能,让错误发生在编译阶段
  5.拒绝魔数,拒绝挖坑
  反例:
  if(age >18){}
  重构后:
  constintadultAge =18;if(age >adultAge){}
  所谓魔数(Magic number)就是一个魔法数字,读者完全弄不明白你这个数字是什么,这样的代码平时见的多了
  6.复杂的条件判断
  反例:
  if(job.JobState ==JobState.New
  ||job.JobState ==JobState.Submitted
  ||job.JobState ==JobState.Expired
  ||job.JobTitle.IsNullOrWhiteSpace()){//....}
  重构后:
  if(CanBeDeleted(job)){//}privatebool CanBeDeleted(Job job){var invalidJobState=job.JobState ==JobState.New ||job.JobState ==JobState.Submitted ||job.JobState==JobState.Expired;var invalidJob =string.IsNullOrEmpty(job.JobTitle);returninvalidJobState||invalidJob;}
  有没有豁然开朗的赶脚?
  7.嵌套判断
  反例:
  var isValid =false;if(!string.IsNullOrEmpty(user.UserName)){if(!string.IsNullOrEmpty(user.Password)){if(!string.IsNullOrEmpty(user.Email)){isValid=true;}}}returnisValid;
  重构后:
  if(string.IsNullOrEmpty(user.UserName))returnfalse;
  if(string.IsNullOrEmpty(user.Password))returnfalse;
  if(string.IsNullOrEmpty(user.Email))returnfalse;returntrue;
  第一种代码是受到早期的某些思想:使用一个变量来存储返回结果。事实证明,你一旦知道了结果就应该尽早返回。
  8.使用前置条件
  反例:
  if(!string.IsNullOrEmpty(userName)){if(!string.IsNullOrEmpty(password)){//register}else{thrownewArgumentException("user password can not be empty");} }else{thrownewArgumentException("user name can not be empty");}
  重构后:
  if(string.IsNullOrEmpty(userName))
  thrownewArgumentException("user name can not be empty");if(string.IsNullOrEmpty(password))
  thrownewArgumentException("user password can not be empty");//register
  重构后的风格更接近契约编程,首先要满足前置条件,否则免谈。
  9.参数过多,超过3个
  反例:
  publicvoidRegisterUser(
  string userName,string password,string email,string phone){}
  重构后:
  publicvoidRegisterUser(User user){}
  过多的参数让读者难以抓住代码的意图,同时过多的参数将会影响方法的稳定性。另外也预示着参数应该聚合为一个Model
  10.方法签名中含有布尔参数
  反例:
  publicvoidRegisterUser(User user,bool sendEmail){}
  重构后:
  publicvoidRegisterUser(User user){}publicvoidSendEmail(User user){}
  布尔参数在告诉方法不止做一件事,违反了Do one thing
  10.写具有表达力的代码
  反例:
  privatestring CombineTechnicalBookNameOfAuthor(List<Book>books,string author){var filterBooks =newList<Book>();foreach (var book in books){if(book.Category==BookCategory.Technical &&
  book.Author ==author){filterBooks.Addbook]size=14;}}var name ="";foreach var book in filterBooks]size=14{name +=book.Name +"|";}returnname;}
  重构后:
  privatestring CombineTechnicalBookNameOfAuthor(List<Book>
  books,string author){var combinedName =books.Where=>b.Category ==BookCategory.Technical]size=14.Where=>b.Author ==author]size=14.Select=>b.Name]size=14.Aggregatea,b]size=14=>+"|"+b);returncombinedName;}
  相对于命令式代码,声明性代码更加具有表达力,也更简洁。这也是函数式编程为什么越来越火的原因之一。
  四.关于DRY
  平时大家重构代码,一个重要的思想就是DRY。我要分享一个DRY的反例:
  项目在架构过程中会有各种各样的MODEL层,例如:DomainModel,ViewModel,DTO。很多时候这几个Model里的字段大部分是相同的,于是有人就会想到DRY原则,干脆直接用一种类型,省得粘贴复制,来回转换。
  这个反例失败的根本原因在于:这几种Model职责各不相同,虽然大部分情况下内容会有重复,但是他们担当着各种不同的角色。
  五.利用先进的生产工具
  以Visual Studio插件中的Reshaper为例,本文列举的大部分反例,Reshaprer均能给予不同程度的提示。经过一段时间的练习,当Reshaper对你的代码给予不了任何提示的时候,你的代码会有一个明显的提高。
  截图说明Reshaper的提示功能:
  /uploads/fox/10165149_4.png
  /uploads/fox/10165149_5.png
  /uploads/fox/10165149_6.png
  光标移动在波浪线处,然后Alt+Enter,Resharper 编辑器会自动对代码进行优化。
  [size=16]小结[/size]
  我们希望的目标是改变自己的编码风格,通过自我完善,还有类似上面的代码最佳实践,以减少使用注释。
  代码中要尽可能清晰,能很清楚的表明意图——即代码透明。想需要提高代码的可读性,且不借助注释,必须改变代码的编写的方式。
  也欢迎大家留言评论。

  编译:21CTO社区
  作者:Magnus Falk
  来源:https://www.quora.com/Are-comm ... thing


评论