• 1. 代码审查技术
  • 2. 学习目标掌握代码分析和评审技术 掌握自动化代码分析工具的使用
  • 3. 建筑工程师的第一堂课-关注细节 关注并弄清楚桥梁修建细节,否则你建起来的桥就有可能坍塌。 -《安全编程 – 代码静态分析》1940年Tacoma Narrows大桥600英尺的一段坍塌落入普吉特湾。
  • 4. 代码审查对成本的节省
  • 5. 黑盒测试的缺陷黑盒测试既不充分,而且效率也低。 在系统完成之前,测试就无法开始,测试人员只有软件版本发布时才能拿到版本进行测试。
  • 6. Static white-box testingFind bugs early Find bugs that would be difficult to uncover or isolate with dynamic black-box testing Cost effective Side benefit:give black-box tester ideas for test cases to apply when they receive the software for testing
  • 7. 不懂开发怎么做代码审查? 霍元甲因为他本人身体虚弱,所以父亲从小不让练武功,而生长在那样的环境中,他天天可以看到兄弟们在练功,招式已经记忆在心里,但是苦在没有练功的机会,他利用体力劳动的过程中,改变劳动方式,趁机练功,后来发展到独创“迷踪拳”。
  • 8. 代码静态分析静态分析是指在不执行的情况下对代码进行评估的过程。包括: - 类型检查 - 风格检查 - 程序理解 - BUG查找 - 安全审查
  • 9. 静态分析 - 类型检查
  • 10. 在Java中,下面的语句虽然符合类型检查规则,但是会在运行时失败,抛出一个ArrayStoreException异常: Object[] objs = new String[1]; objs[0] = new Object();
  • 11. //lint -w2 //lint +e734 #include "stdafx.h" int main() { char ch = 0; int n = 0; //... ch = n; return 0; }VC6编译通过,但是PCLint可以通过静态代码检查找出类型转换造成的精度丢失问题
  • 12. 静态分析 - 风格检查常见工具 C/C++:PC-Lint JAVA:PMD .NET:StyleCop风格检查更加挑剔,也更加注重空格、缩进、命名、注释、程序结构这些表面的东西。风格检查程序所展示的错误往往都是影响代码的可读性和可维护性的问题。
  • 13. typedef enum { red, green, blue } Color; char *getColorString( Color c) { char *ret = NULL; switch( c ) { case red: printf( "red" ); } return ret; } gcc的“-Wall”选项将检查出其中的问题。
  • 14. (本页无文本内容)
  • 15. typedef const char *CSTRING; CSTRING revere( int lights ) { CSTRING manner = "by land"; if( lights > 0 ) if( lights == 2 ) manner = "by sea"; else manner = ""; return manner; } int main() { printf( "The British are coming %s\n", revere( 1 ) ); return 0; }
  • 16. 静态分析 - 程序理解程序理解工具能帮助我们搞懂代码库中的大量代码,洞察程序运转之道。 集成开发环境(IDE)一般至少都包含某些程序理解功能,例如:“查找本方法的所有应用”。 常用工具: - 代码流程图:Code Visual to Flowchart - UML与源代码双向工程,例如Fujaba
  • 17. (本页无文本内容)
  • 18. Fujiaba 能在UML视图和源代码之间来回转换
  • 19. 静态分析 - Bug查找BUG查找的目的不像风格检查那样抱怨格式方面的问题,而是根据“BUG惯用法”(规则)来描述代码中潜在的缺陷。 常用工具: PMD、FindBugs、Coverity、Klocwork
  • 20. EmptyCatchBlock: Empty Catch Block finds instances where an exception is caught, but nothing is done. In most circumstances, this swallows an exception which should either be acted on or reported. Example: public void doSomething() { try { FileInputStream fis = new FileInputStream("/tmp/bugger"); } catch (IOException ioe) { } }
  • 21. (本页无文本内容)
  • 22. string str = ""; for (int i = 0; i < 100; i++) { str += i.ToString(); }
  • 23. 静态分析 - 安全审查最早的安全工具ITS4、RATS、Flawfinder比素有美名的grep多不了多少东西;绝大多数情况下,它们将对代码进行扫描,寻找对strcpy()这样的函数所进行的调用。
  • 24. int main(int argc , char * argv[] ) { char buf1[1024]; char buf2[1024]; char * shortstring = “a short string”; strcpy( buf1 , shortstring ); // safe strcpy( buf2 ,argv[0] ); // dangerous } 好的安全分析工具将会区分出第一个strcpy调用是安全的(尽管可能不需要),而第2个调用则是危险的。
  • 25. 静态安全分析工具测试基准SecuriBench是一个基于Web的开源Java程序集合,包含一些已知的安全缺陷: http://suif.stanford.edu/~livshits/securibench SecuriBench Micro是SecuriBench的缩减版: http://suif.stanford.edu/~livshits/work/securibench-micro ABM(Analyzer Benchmark)测试基准由一些小程序和大型的真实程序构成: http://samate.nist.gov 比较静态安全分析工具的最佳方法是使用这些工具分析同一代码并比较其结果。
  • 26. 把工具引入到代码审查流程中
  • 27. (本页无文本内容)
  • 28. 练习:读代码、找错误int main(int argc, char* argv[]) { char heightString[12]; char weightString[12]; int height , weight; float bmi; printf( "Enter your height in inches:" ); gets( heightString ); printf( "Enter your weight in pounds:" ); gets( weightString ); height = atoi( heightString ); weight = atoi( weightString ); bmi = ((float)weight/((float)height*height)) * 703.0; printf("\nBody mass index is %2.2f\n\n",bmi); return 0; }
  • 29. 通过代码审查找出常见安全问题不恰当地处理输入 缓冲区溢出 错误和异常处理
  • 30. 审查输入处理错误怀疑、审慎是安全之本。 -本杰明.富兰克林 “绝对不要相信输入!” 程序员不得不接收用户输入,但是又不能够相信输入-那该怎么做呢?明智的方法是充分检查输入,并且验证输入的正确性。
  • 31. 输入包括: 命令行参数 配置文件 从数据库中检索出来的数据 环境变量 网络服务 注册表值 临时文件 …
  • 32. 不要盲目依赖数据库的数据来保证应用程序的正确运行。 这段代码没有对查询所返回的记录行数进行检验,只是简单地调用返回的第一行记录: ResultSet rs = stmt.executeQuery(); rs.next(); int balance = rs.getInt(1);
  • 33. 对来自数据库的数据加以验证,确保数据表中唯一值所对应的数据行的唯一性。如果存在2个数据行,则表明可能有攻击者试图插入伪造的数据行。 修正后的代码检验了返回的数据行,以保证只返回一行数据: ResultSet rs = stmt.executeQuery(); if( !rs.next() ) { throw new LookupException( “没有余额数据行!” ); } if ( !rs.isLast() ) { throw new LookupException( “余额数据行多余一行!” ); } int balance = rs.getInt(1);
  • 34. 千万不要依赖DNS域名或者IP地址来进行认证。因为如果攻击者获取了DNS更新权限(DNS缓存中毒),则可能将数据包路由到自己的机器上,或者伪装IP地址,使得系统误认为是同一个域中的机器。
  • 35. struct hostent *hp; struct in_addr myaddr; char * tHost = "trustme.com"; myaddr.s_addr = inet_addr( ip_addr_string ); hp = gethostbyaddr( (char *) &myaddr , sizeof( struct in_addr ) , AF_INF ); if( hp && !strncmp( hp->h_name , tHost , sizeof(tHost ) ) ) { trusted = true; } else { trusted = false; }
  • 36. 黑名单 vs. 白名单黑名单法(black listing):尝试枚举所有不能接受的输入值。 白名单法(white listing):通过已知正确值清单来进行检查。
  • 37. 下面是摘自Tomcat5-1.31版本中的一段程序,采用了一个黑名单,假定在Web页面中只有4个特殊的字符输入才会导致程序的安全问题: for( int i=0; i < content.length; i++ ) { switch ( content[i] ) { case '<': result.append("<"); break; case '>': resule.append(">"); break; case '&': resule.append("&"); break; case '"': resule.append("""); break; default: result.append(content[i]); } }
  • 38. 黑名单法只拒绝已知恶意的数据,而在给定环境中,恶意值的集合通常是难于枚举的(或者可能是无限的),所以黑名单法一般是不完善的。 即使可能列出一个危险输入值的完整清单,也很可能随着时间的变迁而过时。黑名单法是不利于应用程序安全的。
  • 39. (本页无文本内容)
  • 40. 审查缓冲区溢出错误缓冲区溢出(buffer overflow):将大量数据塞入一个较小的缓冲区中所导致的程序漏洞 缓冲区溢出通常被攻击者用于重写内存中的数据。 近10年来,缓冲区溢出漏洞的数量没有明显下降的趋势。
  • 41. (本页无文本内容)
  • 42. void trouble(){ int a =32; char line[128]; gets(line); } 缓冲区溢出就是将长度超过缓冲区大小的数据写入程序的缓冲区,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其他指令。 当一个超长的数据进入到缓冲区时,超出部分就会被写入其他缓冲区,其他缓冲区存放的可能是数据、下一条指令的指针,或者是其他程序的输出内容,这些内容都被覆盖或者破坏掉。可见一小部分数据或者一套指令的溢出就可能导致一个程序或者操作系统崩溃。
  • 43. 天生危险的函数gets、cin、scanf、 strcpy、wcscpy、lstrcpy sprintf、fprintf、swprintf
  • 44. 禁用函数列表
  • 45. Linux和Windows平台上替换C类库的字符串解决方案GNU/Linux Bstrlib: http://bstring.sourceforge.net FireString: http://firestuff.org/wordpress/projects/firestring GLib: http://developer.gnome.org/doc/API/2.0/glib/index.html Libmib: http://www.mibsoftware.com/libmib/astring Windows Vstr: http://www.and.org/vstr Safe CRT: http://msdn.microsoft.com/msdnmag/issues/05/05/safecandc/default.aspx SafeStr: http://www.zork.org/safestr
  • 46. 审查整数溢出错误当一个整数值大于或者小于其范围时,就会产生整数溢出错误(integer overflow)。 “回绕”
  • 47. unsigned long readamt; readamt = getstringsize(); if( readamt > 1024 ) return -1; readamt--; buf = malloc( readamt );如果getstringsize返回0,则readamt-1将等于4294967295(无符号32位整数的最大值),这个操作可能会因为内存不足而失败。
  • 48. 检测整数溢出
  • 49. IntSafe数学函数对于C语言,微软开发了IntSafe类库,它可以帮助检测和防止整数操作中的漏洞 IntSafe函数库包含200多个转换函数
  • 50. 审查异常处理错误char buf[10],cp_buf[10]; fgets(buf,10,stdin); strcpy(cp_buf,buf); 如果发生I/O错误,fgets将不给buf添加NULL终止符。在buf中缺少NULL终止符将可能会导致在后续的strcpy调用时发生缓冲区溢出。
  • 51. Try catch finallyC++和Java都支持try/finally的语法。不论异常是否抛出,finally模块总是在try模块后执行。 但是,如果finally模块包含一个return语句,将抑制异常抛出。
  • 52. 审查资源泄漏问题未能及时释放资源(包括数据库对象、文件句柄和套接字)可能会导致严重的性能问题。 在C程序中,在单个函数体中查找多个return语句,通常是分布式错误处理代码的标志,也是资源泄漏的滋生地。
  • 53. 函数有多个出口时,没有在每个出口处对动态申请的内存进行释放。一般在异常处理时容易出现这种错误。下面的代码段就是这样的例子: ..... pRecord = new char[pTable->GetRecordLength()]; assert(pRecord != NULL); if (pTable->GoTop(FALSE) != DBIERR_NONE) return; // 如果从这里返回,pRecord将得不到释放 ..... pTable->Close(); delete[] pRecord; }
  • 54. char * getBlock( int fd ) { char * buf = (char *)malloc( BLOCK_SIZE ); if ( !buf ) { return NULL; } if ( read (fd,buf ,BLOCK_SIZE ) != BLOCK_SIZE ) { return NULL; } return buf; } 如果read调用失败,将导致已经分配的buf内存泄漏!
  • 55. try { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(CXN_SQL); harvestResult( rs ); stmt.close(); } catch( SQLException e) { logger.log( Level.ERROR ,” error executing sql query” , e ); } 如果在执行SQL或处理结果时出现了异常,那么stmt对象将不能被关闭。假如这种情况经常发生,数据库将耗尽可用的指针,不能执行任何其他的查询。
  • 56. C/C++代码审查C/C++语言编码规范 C/C++常见代码问题检测
  • 57. 循环语句的效率for (row=0; row<100; row++) { for ( col=0; col<5; col++ ) { sum = sum + a[row][col]; } }
  • 58. 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU 跨切循环层的次数: for (col=0; col<5; col++ ) { for (row=0; row<100; row++) { sum = sum + a[row][col]; } }
  • 59. 指针的使用 _UC *puc_card_config_tab; ... ... Get_Config_Table( AMP_CPM_CARD_CONFIG_TABLE, &ul_card_config_num, &puc_card_config_tab, use_which_data_area ); ... ... b_middle_data_ok = generate_trans_middle_data_from_original_data( puc_card_config_tab, Ul_card_config_num) .... ...
  • 60. 分配资源是否正确释放void WarnSvr::SaveWarnData() { ...... for( int m = 0;m < RecordsInBuffer[EVENT_ALARM]; m++ ) { HISTORY_FILTER_INDEX * item = new HISTORY_FILTER_INDEX; item->Csn = Buffer[EVENT_ALARM][m].Csn; item->Position = m + ( RecordsInHistoryFile - RecordsInBuffer[EVENT_ALARM] ); //If a warn with a certain Csn is not in EventFilterIndex,it is not necessary to be added to HistoryFilterIndex int item_total = EventFilterIndex.GetItemsInContainer(); BOOL find_flag = false; for (int k = 0; k < item_total; k++ ) { if( EventFilterIndex[k]->Csn == item->Csn ) { find_flag = true; break; } } if( find_flag ) { HistoryFilterIndex.Add( item ); if( HistoryFilterIndex.IsFull() ) ClearIndexEntry(); } }
  • 61. PC-LintLINT工具是一种软件质量保证工具,许多国外的大型专业软件公司,如微软公司,都把它作为程序检查工具,在程序合入正试版本或交付测试之前一定要保证通过了LINT检查,他们要求软件工程师在使用LINT时要打开所有的编译开关,如果一定要关闭某些开关,那么要给出关闭这些开关的正当理由。 PC-LINT是GIMPEL SOFTWARE公司的产品,其中的内容是非常广泛的,光是选项就有30 0多个,涉及到程序编译及语法使用中的方方面面。
  • 62. 练习:PC-Lint的安装和使用1: 2: char *report( short m, short n, char *p ) 3: { 4: int result; 5: char *temp; 6: long nm; 7: int i, k, kk; 8: char name[11] = "Joe Jakeson"; 9: 10: nm = n * m; 11: temp = p == "" ? "null" : p; 12: for( i = 0; i < m; i++ ) 13: { k++; kk = i; } 14: if( k == 1 ) result = nm; 15: else if( kk > 0 ) result = 1; 16: else if( kk < 0 ) result = -1; 17: if( m == result ) return temp; 18: else return name; 19: }
  • 63. /* Off-By-One Example */ #include int main() { int i; int a[] = {1,2,3}; int n = sizeof(a)/sizeof(int); for(i=0;i<=n;i++) printf("a[%d]=%d\n",i,a[i]); return 0; }
  • 64. #define NStu 3000 #define NCrs 400 extern int const StuCourse[NStu][NCrs]; extern double const CostBook[NCrs]; double total_cost() { int i, j; double cost = 0; for( i = 0; i < NStu; i++ ) for( j = 0; j < NCrs; i++ ) cost += StuCourse[i][j] * CostBook[j]; return cost; }
  • 65. JAVA代码审查代码审查和语法错误检查是两个不同层次的概念。语法错误是低层次、强制性的检查,任何违反语法的程序都是无法通过编译的,也就是说可运行的程序必须是语法正确的。而代码审查是高级别,非强制性的检查,它对语法正确的程序施加了更高更严格的要求,从而提升程序的可读性、降低因变量命名、方法定义、程序逻辑的不完整性等问题而导致程序的潜在出错机率,增加程序的可维护性和健壮性。
  • 66. 'switch' Statement Should Include a Default Case 1. switch (formatType) 2. { 3.  case 1: 4.   formatStr = "yyyyMMddHHmmss"; 5.   break; 6.  case 2: 7.   formatStr = "yyyy'-'MM'-'dd HH:mm:ss"; 8.   break; 9.  case 3: 10.  formatStr = "yyyy.MM.dd HH:mm:ss"; 11.  break; 12. case 4: 13.  formatStr = "yyyy'年'MM'月'dd HH:mm:ss"; 14.  break; 15. }
  • 67. 根据Sun的编码规范,每个switch流程控制语句都必须带一个default分支,以保证逻辑分支的完整性。 如果没有第15~16行的default代码,代码审查将给出警告。 1. switch (formatType) 2. { 3.  case 1: 4.   formatStr = "yyyyMMddHHmmss"; 5.   break; 6.  case 2: 7.   formatStr = "yyyy'-'MM'-'dd HH:mm:ss"; 8.   break; 9.  case 3: 10.  formatStr = "yyyy.MM.dd HH:mm:ss"; 11.  break; 12. case 4: 13.  formatStr = "yyyy'年'MM'月'dd HH:mm:ss"; 14.  break; 15. default: 16.  formatStr = "yyyy'-'MM'-'dd HH:mm:ss"; 17. }
  • 68. Accessing Static Members by the Descendant Class Name 1. public class ASMO1 2. { 3.  void func() 4.  { 5.   ASMO1 obj1 = new ASMO1(); 6.   ASMO2 obj2 = new ASMO2(); 7.   obj1.attr = 10; 8.   obj2.attr = 20; 9.   obj1.oper(); 10.   obj2.oper(); 11.  this.attr++; 12.  this.oper(); 13. } 14. 15. static int attr; 16. static void oper() 17. {} 18. } 19. 20. class ASMO2 21. { 22.  static int attr; 23.  static void oper() 24.  {} 25. }
  • 69. 类中所有的静态方法或变量都应该通过类名来引用,如果通过类的实例来引用这些静态的成员将影响到程序的可读性。如果通过类名来引用静态变量,将容易分辨出这些成员的静态属性。 因为类静态成员变量在JVM中仅存在一份,而非每个对象实例各自一份,因此静态成员变量可以看成类的成员。
  • 70. Complex Assignment 1. int i = 0; 2. int j = 0; 3. int k = 0; 4. int l = 0; 5. i *= ++j; 6. 7. k = j = 10; 8. 9. l = j += 15; 10. 11. i = j++ +20; 12. 13. i = (j = 25) + 30; 14. 15. i = j++ + 20; 16. 17. i = (j = 25) + 30;
  • 71. 往往有些程序员热衷于将Java的语法发挥到极致,以资其对Java语法精通的凭据。如果是为了练习语法、理解语法,无可厚非。但如果在需要充分协作沟通的软件项目中,简洁明了,清晰易懂将会受到推崇,晦涩难懂的语句将会受到奚落。 故此,大部分的软件公司的规范都对语句的精简明了提出了要求。
  • 72. Complex Initialization or Update Clauses in 'for' Loops1.for (i = 0, j = 0, k = 10, l = -1; i < 10; i++, j++, k--, l += 2) 2. { 3. //do something 4. }
  • 73. 由于for循环控制语句的高度灵活性,所以for()中的代码往往是复杂晦涩代码的乐园,在for语句中以逗号分隔的赋值语句最多不应超过3个: 1. for (i = 0, j = 0, k = 10, l = -1; i < 10; i++, j++, k--, l += 2) 2. { 3.  //do something 4. } 5. //应改写成下面的样式 6. //l = -1; 7. //for (i = 0, j = 0, k = 10; i < cnt; i++, j++, k--) 8. //{ 9. // //do something 10. // l += 2; 11. //}
  • 74. Use Abbreviated Assignment Operator 1. void oper () { 2. int i = 0; 3. i = i + 20; 4. i = 30 * i; 5. }
  • 75. 在可能的情况下使用缩减赋值运行符(*=, /=, %=, +=, -=, <<=, >>=, &=, ^=, 和|=),因为这些语句能够提高编码录入的速度,增强代码的简洁性,同时缩减赋值运行符使某些编辑器运行得更快。 1. void oper () { 2. int i = 0; 3. i = i + 20; 4. i = 30 * i; 5. } 6. //应改写成 7. //void oper () 8. //{ 9. // int i = 0; 10. // i += 20; 11. // i *= 30; 12. //}
  • 76. Coding Style: - Multiple Statements on One Line - Place Statement in Block - Use 'L' instead of 'l' at the End of Integer Constants 1. i++; j++; 2 3. if (val < 0) 4.  return; 5. while (val >= 10) 6. val /= 10; 7. 8. void func () { 9.  long var = 0x0001111l; 10. }
  • 77. 不应将多行语句写在同一行代码中。 代码块应以“{}”框起来,虽然增长了代码,但代码结构性更强。 声明长整型使用大写的“L”类型指定符,而非小写的“l”,因为后者和数字1相似。
  • 78. Hiding Names 1. public class HideName 2. { 3.  int index; 4.  void func() 5.  { 6.   int index; 7.   // do something 8.  } 9.  void setIndex(int index) 10.  { 11.   this.index = index; 12.   index++; 13.  } 14. }
  • 79. 类成员变量被局部变量隐藏:因类方法体中的局部变量和类成员变量具有相同的名字,而使成员变量被屏蔽 1. public class HideName 2. { 3.  int index; 4.  void func() 5.  { 6.   int index;//隐藏了成员变量index,应改成另一个名字,如int newIndex; 7.   // do something 8.  } 9.  void setIndex(int index) 10.  { 11.   this.index = index; //该语句行中带this显式引用成员变量进行赋值,审查规则将不报警 12.   index++;//该语句行没有this显式引用,审查规则将报警 13.  } 14. }
  • 80. Hiding Inherited Field 1. class Window 2. { 3.  protected int style; 4. } 5. 6. class Button extends Window 7. { 8.  protected int style; 9. }
  • 81. 子类成员变量隐藏父类成员变量:子类成员变量和可继承的父类成员变量名字相同。 1. class Window 2. { 3.  protected int style; 4. } 5. 6. class Button extends Window 7. { 8.  protected int style;//具有和父类相同的成员变量,应改为另一个名字,如anStyle 9. }
  • 82. Hiding Inherited Static Methods 1. class Animal 2. { 3.  static void oper1(){} 4.  static void oper2(){} 5. } 6. 7. class Elephant extends Animal 8. { 9.   static void oper1() 10.  static void oper2() 11. }
  • 83. 子类覆盖父类静态方法:和非静态的方法覆盖不一样,静态的父类方法不应被子类覆盖。 成员变量和局部变量的隐藏,常常会使开发人员张冠李戴,犯一些不经意的错误,而子类隐藏父类的成员和静态变量常常是由于没有注意到父类中已经具有相同的名字而引起的,由此而生产的程序Bug由于其隐身性强,是很难被发现。 1. class Animal 2. { 3.  static void oper1(){} 4.  static void oper2(){} 5. } 6. 7. class Elephant extends Animal 8. { 9.   static void oper1() {}//隐藏了父类中的静态方法,应取另一个名字,如anOper1() 10.  static void oper2() {}//隐藏了父类中的静态方法,应取另一个名字,如anOper2() 11. }
  • 84. Use Conventional Variable Names 1. void method(double d) 2. { 3.  int i; 4.  Exception e; 5.  char s; 6.  Object f; 7.  String k; 8.  Object UK; 9.  Object COM; 10. }
  • 85. 避免用过于简单的变量名 除了循环体中的临时变量,及一些没有特殊意义的常见数据类型,应该尽量避免使用一个字符作为变量。那些无特殊意义且常见的数据类型,所选取的单字符变量名必须按表1进行命名。 为了减少潜在的冲突,避免不必要的混淆,不允许以大写域名或国家代码作变量名。 1. void method(double d) 2. { 3.  int i; 4.  Exception e; 5.  char s;//应该改为c 6.  Object f;//应该改为o 7.  String k;//应该改为s 8.  Object UK;//和英国国家代码相同,应改为其他的名字,如ukObj 9.  Object COM;//和域名相同,应改为其他的名字,如obj_1 10. }
  • 86. Break Statement is Missing before Case clause 1. switch (c) { 2.  case '+': 3.   ... 4.   break; 5.  case '-': 6.   ... 7.  case 'n': 8.   ... 9.  case ' ': case '\t': 10.  ... 11.  break; 12. }
  • 87. 根据Sun编码惯例,程序入口点从一个case进入,直接到达下一个case代码段。 前一个case没有对应的break语句时,在跨过的地方必须给出一个显示的注释,表示是特定流程控制的要求,而非无意遗漏。
  • 88. Non-Case Label in Switch statement 1. public class CaseLabel 2. { 3.  /**点*/ 4.  public static final int POINT = 1; 5.  /**线*/ 6.  public static final int LINE = 2; 7.  /**多边形*/ 8.  public static final int POLYGON = 3; 9. 10. public String getFigureType (int kind) 11.  { 12.   String tempName = null; 13.   switch (kind) 14.   { 15.    case POINT: 16.     LINE: 17.     tempName = "POINT and LINE"; 18.     break; 19.    case POLYGON: 20.     tempName = "POLYGON"; 21.     break; 22.    default: 23.     tempName = "UNDEFINE"; 24.   } 25.   return tempName; 26.  } 27. }
  • 89. 在switch中出现非case的标签: 在Java语句中有两个标签,即case分支标签,另一个则是语句标签,如果case分支标签语句误删或遗漏了case关键字,则case分支标签将变成语句标签,而编译器无法识别这个错误。
  • 90. Suspicious Break/Continue 1. void scan(char[] arr) 2. { 3.  loop: 4.  for (int i = 0; i < arr.length; i++) 5.  { 6.   switch (arr[i]) 7.   { 8.    case '0':case '1':case '2':case '3':case '4': 9.    //5~6的数字 10.   case '5':case '6':case '7':case '8':case '9': 11.   { 12.    if (processDigit(arr[i])) 13.    { 14.     continue loop; 15.    } 16.    else 17.    { 18.     break; 19.    } 20.   } 21.   case ' ':case '\t': 22.   { 23.    processWhitespace(arr[i]); 24.    continue; 25.   } 26.   default: 27.    processLetter(arr[i]); 28.    break; 29.  } 30. }
  • 91. 有错误嫌疑的break和continue: break和continue用于switch和循环中的跳转控制,break用于提前结束循环以及从switch中退出,break的这种“多态性”使得在循环体中内嵌switch语句时,常会带来一些隐患。即开发者本希望退出外层循环,结果却只退出内层的switch语句而已。
  • 92. Comparing Floating-Point Values 1. void calc(double limit) 2. { 3.  if (limit == 0.0) 4.  { 5.   System.out.println(" the float-point number is exactly 0"); 6.  } 7. }
  • 93. 避免对浮点值进行等值逻辑判断 浮点数都是一定精度的数据,由于内部表示的误差,往往字面上相同的两个浮点数,其内部表示也不完全相同。故此应避免对浮点值数进行等值逻辑判断,而应采用逻辑比较判断。 1. void calc(double limit) 2. { 3.  if (limit == 0.0)//应改为通过和较小值比较来判断,如if(Math.abs(limit) < 0.0000001) 4.  { 5.   System.out.println(" the float-point number is exactly 0"); 6.  } 7. }
  • 94. Mixing Logical Operators Without Parentheses 1. boolean a, b, c; 2. ... 3. if (a || b && c) 4. { 5. ... 6. }
  • 95. 添加()清晰化复杂的表达式 写复杂的表达式时不应过度依赖运算操作符的计算优先顺序,而应养成使用“()”的好习惯,当一个逻辑表达式由多个逻辑运算组成时,应该用“()”划分不同的部分。 1. boolean a, b, c; 2. ... 3. if (a || b && c) //应该替换成if ((a || b) && c) 4. { 5. ... 6. }
  • 96. Member is Not Used 1. public class Unuse 2. { 3.  private String name; 4.  public Object value; 5. 6.  private Object getValue() 7.  { 8.   return value; 9.  } 10. 11. private void print() 12.  { 13.   System.out.println(getValue() + " = " + value); 14.  } 15. }
  • 97. 类中private的成员方法和成员变量不可能在外部类中调用,如果发现private的成员变量或方法并没有在内部的protect或public方法中使用,即这个成员永远不会在运行期得到引用,而成为一个无用的成员变量和方法。 代码清单中name变量,getValue()及print()方法都是无用的方法,因为不可能通过外面的类访问到这些成员,Unuse也没有提供调用这些成员的接口,所以这些成员都可以从类中清除。
  • 98. Comparison always produces the Same Result 1. void handleEvent(Event e){ 2.  if (e != null) { 3.   ... 4.   if (e = = null) { 5.    ... 6.   } 7.  } 8. } 9. 10. void putChar(char c, boolean isLetter, boolean isDigit) { 11.  if (isDigit) { 12.   boolean isLetterOrDigit = isLetter || isDigit; 13.   ... 14.  } 15. }
  • 99. 无作为的表达式 比较表达式总是返回相同的值。 1. void handleEvent(Event e){ 2.  if (e != null) { 3.   ... 4.   if (e = = null) { // 该表达式的值永远都是false,因为进入这个代码段的e恒为非空 5.    ... 6.   } 7.  } 8. } 9. 10. void putChar(char c, boolean isLetter, boolean isDigit) { 11.  if (isDigit) { 12.   boolean isLetterOrDigit = isLetter || isDigit;//该表达式的值永远都是true 13.   ... 14.  } 15. }
  • 100. Operation has No Effect 1. public class NoEffect 2. { 3.  private String name; 4.  private int index; 5. 6.  NoEffect(String n, int index) 7.  { 8.   this.name = name; 9.   this.index = index; 10.  } 11. 12. int getPosition() 13.  { 14.   int base = 0; 15.   return index + base; 16.  } 17. 18.  int getModule() 19.  { 20.   int x = 1, y = 2; 21.   return x % y; 22.  } 23. }
  • 101. 无效的算术运算,例如:    当进行加法和减法运算时,有一个操作数是0。 当进行乘法运算时,乘法或被被乘数为1。 当进行除法运算时,除数为1。 当进行取模运算时,左边的操作数的绝对值比右边操作数的绝对值小,此时x%y=x。 属性赋值时,将本身的值赋给自己。
  • 102. Statement is Unreachable 1. int[] arr = new int[size]; 2. if (arr == null) 3. { 4.  return null; 5. }
  • 103. 流程控制中存在不可到达的语句 有些流程控制由于测试条件恒为false,则流程中的程序无法到达。 1. int[] arr = new int[size]; 2. if (arr == null) //由于arr不为空,则该测试逻辑不可能通过,程序无法进入该程序块中 3. { 4.  return null; 5. }
  • 104. JBuilder的Code Audits功能的使用 JBuilder根据Sun的编码规范及软件开发界总结出的一套行之有效的编码习惯,对Java开发中的编码风格、声明风格、Javadoc文档注释、EJB规范、命名风格、潜在错误、编码中的画蛇添足等诸多方面进行代码审查并给出警示,以便开发人员发现这些不足和隐患予以及时更正。
  • 105. C#代码审查C#编码规范 C#常见代码问题检测
  • 106. 避免不必要的强制转换 public static void UnderPerforming(ArrayList list) { foreach(object obj in list) { if(obj is Control) { Control aControl = obj as Control; // Use aControl. } }
  • 107. 使用字符串长度属性测试是否为空字符串string s1 = "test"; public void EqualsTest() { if (s1 == "") { Console.WriteLine("s1 equals empty string."); } }
  • 108. 优先使用交错数组而非多维数组 int[,] multiDimArray = {{1,2,3,4}, {5,6,7,0}, {8,0,0,0}, {9,0,0,0} };
  • 109. 属性不应返回数组 public class Test { string [] nameValues; public Test() { nameValues = new string[100]; for (int i = 0; i< 100; i++) { nameValues[i] = "Sample"; } } public string [] Names { get { return (string[]) nameValues.Clone(); } } public static void Main() { Test t = new Test(); for (int i = 0; i < t.Names.Length ; i++) { if (t.Names[i] == ("SomeName")) { // Perform some operation. } } } }
  • 110. public class PhotoLibrary { private Photo[] photos = null; public Photo[] Photos { get { Photo[] copyOfPhotos = new Photo[photos.Length]; for (int i = 0;i < photos.Length; i++) { copyOfPhotos[i] = photos[i].Clone(); } return copyOfPhotos; } } } // 使用Photos属性: for (int i=0; i < library.Photos.Length; i++) { if (library.Photos[i].Width > library.Photos[i].Height) { Console.WriteLine("Landscape"); } else if (library.Photos[i].Width < library.Photos[i].Height) { Console.WriteLine("Portrait"); } else { Console.WriteLine("Square"); } }
  • 111. 避免过多的继承 class BaseClass { } class FirstDerivedClass : BaseClass { } class SecondDerivedClass : FirstDerivedClass { } class ThirdDerivedClass : SecondDerivedClass { } class FourthDerivedClass : ThirdDerivedClass { } class FifthDerivedClass : FourthDerivedClass { }
  • 112. 避免异常的发生private void DrawBox(Rectangle rect , Brush brush) { Graphics g = this.CreateGraphics(); try { g.FillRectangle(brush , rect); } catch (ArgumentNullException) { g.FillRectangle(SystemBrushes.Desktop, rect); } finally { g.Dispose(); } }
  • 113. 控制异常foreach (string exp in expressions) { try { int num = int.Parse(exp); s += num; } catch (FormatException) { Console.WriteLine(exp + " 包含无效的Int32数值。"); } catch (OverflowException) { Console.WriteLine(exp + " 溢出。"); } }
  • 114. 注意多个catch子句的排列顺序try { if (path != null) { FileStream fstream = File.Open(path , FileMode.Open); } // ... } catch (ArgumentException) { MessageBox.Show("文件名格式不正确或包含非法字符"); } catch (ArgumentOutOfRangeException) { MessageBox.Show("无效的打开文件模式"); } // ...
  • 115. 异常信息的隐藏public AccountInfo SignIn(string userName , byte[] key) { byte[] savedKey = GetSavedKeyByUserName(userName); try { for (int i = 0; i < savedKey.Length; i++) { if (savedKey[i] != key[i]) { throw new InvalidPasswordException("密码无效。"); } } return GetAccountInfo(userName); } catch (IndexOutOfRangeException ex) { throw new InvalidPasswordException("密码无效。", ex); } }
  • 116. 异常提示信息 throw new ArgumentOutOfRangeException( "age", "年龄不能为负数!!!!!!!!!");
  • 117. 访问控制public struct Person { public int Age; } public class Program { public static void main() { Person person; person.Age = -3; } }字段其实就是结构或类中的普通变量成员。这将意味着,一旦字段被公开,对其的访问便处于完全失控的状态。
  • 118. FxCopFxCop是一款专门用于分析.NET托管代码程序集的工具。它能检查.NET代码是否满足Microsoft.NET Framework 设计规范。它使用反射、MSIL解析等技术来分析程序集,可以检查超过200个缺陷,包括库设计、本地化、命名规范、性能、安全等方面的内容。
  • 119. FxCop的使用
  • 120. FxCop命令行运行方式FxCopCmd /f:” F:\CNJ\C6\CodeAnalysisTest1\CodeAnalysisTest1\bin\Debug\ CodeAnalysisTest1.exe” /out:”C:\1.txt” /s /rule:+”E:\Program Files\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\Rules\DesignRules.dll” /ruleid:-“Microsoft.Design#CA2210”
  • 121. 练习:利用FxCop检查C#代码问题private void button1_Click(object sender, EventArgs e) { string str =""; for (int i = 0; i < 100; i++) { str += i.ToString(); } }
  • 122. FxCop的规则定义方法 FxCop支持用户自定义检查规则,通过使用FxCop的SDK,继承基类BaseIntrospectionRule,并编写自定义的检查方法,可针对项目的代码添加更多的检查规则。
  • 123. FxCop的代码分析原理 大多数代码分析工具扫描的是源代码,但是FxCop则直接对编译好的代码进行扫描。.NET的每个程序集都有metadata(可称为元数据,metadata是关于一个程序集中各元素的类型信息库,它本身也存放在这个程序集中),metadata对程序集以及程序集内用到的所有类型进行描述。FxCop会利用metadata的信息来获得代码内部的运行情况。 另外,FxCop也对代码编译时生成的中间语言(MSIL)进行检查。通过对metadata和MSIL检查的结合,FxCop可以得出大量信息,以此获得对代码执行时的行为的相关信息。它把代码和各条规则逐一比较检查,当找到不符合规则的代码时就会生成一条消息。
  • 124. 练习:定义FxCop代码检查规则
  • 125. DevPartner DevPartner Studio 8.0 Professional Edition是一款与Visual Studio紧密结合的测试工具。它能帮助检测和诊断各种.NET代码问题,包括代码评审(Code Review)、错误检测、性能分析、覆盖率分析、内存分析等功能。
  • 126. DevPartner的Code Review功能
  • 127. 在Rule Manager中编辑代码规则 DevPartner通过扫描和匹配代码中符合正则表达式的代码来判断代码是否违反了规则,因此可以说正则表达式是制定规则、识别代码缺陷的关键。
  • 128. 练习:设计规则自动检查代码错误// 在使用完SqlDataReader后应该调用其Close方法。 例如下面是一个错误的例子: public void myMethod2() { // SQL查询语句 string mySelectQuery = "SELECT OrderID, CustomerID FROM Orders"; // 新建连接 SqlConnection myConnection = new SqlConnection("myConnString"); // 新建SQL命令 SqlCommand myCommand = new SqlCommand(mySelectQuery, myConnection); // 打开连接 myConnection.Open(); // 新建数据读取器 SqlDataReader myReader; myReader = myCommand.ExecuteReader(); // 执行读操作 while (myReader.Read()) { Console.WriteLine(myReader.GetInt32(0) + ", " + myReader.GetString(1)); } }
  • 129. 代码风格检查好的代码编写风格能让代码变得“赏心悦目”,增强代码的可读性和可维护性,并且能促进项目组基于代码的沟通。 开发人员在软件开发过程中,如果编写的代码可读性差、代码风格不一致,或者缺乏注释,则会直接导致在人员更替、开发工作的交接时,需要花费更多的时间和精力,甚至导致项目延期、失败,并且给后期维护工作也带来了很多麻烦。
  • 130. 命名规范骆驼(Camel) 帕斯卡(Pascal) 匈牙利
  • 131. “匈牙利”法该命名规则的主要思想是“在变量和函数名中加入前缀以增进人们对程序的理解”。 例如所有的字符变量均以ch为前缀,若是指针变量则追加前缀p。如果一个变量由ppch 开头,则表明它是指向字符指针的指针。
  • 132. “匈牙利”法的缺点“匈牙利”法最大的缺点是烦琐,例如 int i, j, k; float x, y, z; 倘若采用“匈牙利”命名规则,则应当写成 int iI, iJ, ik; // 前缀 i 表示int 类型 float fX, fY, fZ; // 前缀 f 表示float 类型 如此烦琐的程序会让绝大多数程序员无法忍受。
  • 133. 骆驼式命名法正如它的名称所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。例如,下面是分别用骆驼式命名法和下划线法命名的同一个函数:    printEmployeePaychecks();     print_employee_paychecks();      第一个函数名使用了骆驼式命名法 -- 函数名中的每一个逻辑断点都有一个大写字母来标记;第二个函数名使用了下划线法----函数名中的每一个逻辑断点都有一个下划线来标记。     骆驼式命名法近年来越来越流行了,在许多新的函数库和Microsoft Windows这样的环境中,它使用得当相多。另一方面,下划线法是c出现后开始流行起来的,在许多旧的程序和UNIX这样的环境中,它的使用非常普遍。
  • 134. 帕斯卡(Pascal)与骆驼命名法类似。只不过骆驼命名法是首字母小写,而帕斯卡命名法是首字母大写,如:public void DisplayInfo(); string UserName; 二者都是采用了帕斯卡命名法 在C#中,以帕斯卡命名法和骆驼命名法居多。
  • 135. StyleCop简介 StyleCop是微软内部使用的一个代码风格检查工具。提供了简单和有效的方式来对项目的代码编写风格进行检查。StyleCop可以多种方式运行,可以插件的方式在Visual Studio的IDE中运行;也可以MSBuild任务的方式运行,可整合到程序构建流程中;或者以命令行的方式运行,可针对一个或多个代码文件进行检查。
  • 136. StyleCop的设置
  • 137. 练习:使用StyleCop检查代码风格using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; /* */ namespace StyleCopTest1 { public partial class Form1 : Form { public Form1() { // InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { // MessageBox.Show("OK!"); } } }
  • 138. JavaScript代码检查工具-JSLint
  • 139. HTML ValidatorHTML Validator:基于Tidy和OpenSP,HTML Validator对你访问的页面进行验证并给出简单的图标提示。工具可以给你提供更多信息,当你查看页面的源代码时,验证错误将被高亮显示。
  • 140. (本页无文本内容)
  • 141. (本页无文本内容)
  • 142. Why have code conventions?80% of the lifetime cost of a piece of software goes to maintenance. Hardly any software is maintained for its whole life by the original author. Code conventions improve the readability of the software, allowing engineers to understand new code more quickly and thoroughly.
  • 143. 代码评审的目的(1)-保证代码质量
  • 144. 代码评审的目的(2)-学习和交流
  • 145. 有效无痛苦的代码评审 -询问胜于陈述
  • 146. 有效无痛苦的代码评审 -避免“为什么”的问题
  • 147. 有效无痛苦的代码评审 -记住要赞扬他人
  • 148. 有效无痛苦的代码评审 - 确保参照良好的编码标准
  • 149. 有效无痛苦的代码评审 -确保讨论集中在代码上而不是编码人员上
  • 150. 有效无痛苦的代码评审 -记住通常有多于一种的解决方法