你如何使错误的代码看起来不对? 你使用什么模式来避免语义错误?

自从我第一次犯了错误, if我总是写这样的ifs:

 if (CONST == variable) { 

避免常见(至less对我来说)这样做的错误:

 if (variable = CONST) { //WRONG, assigning 0 to variable 

而且自从我读了乔尔·斯波尔斯基(Joel Spolsky)的文章“ 使错误的代码看起来错了”以来,我一直在试图将他的build议付诸实践。

那么,如果你犯了一个语义错误,你还会使用其他什么模式来使错误的代码看起来错误,或者强制语法错误?

我发现编译器错误的代码看起来很重要。 在实践中(并且只有在使用强types语言时),这意味着省略任何types的variables前缀(甚至是应用程序匈牙利语)而偏向于不同的types。 要使用Joel的示例,如果有两种不同的types来表示原始string和已消毒的string,并且两者之间没有隐式转换,则应用程序匈牙利地址甚至不能发生问题。

Word文档坐标也是一样的。 从某种意义上说,Apps Hungarian只是对没有足够严格的types检查的编译器/语言的解决方法。

我使用的一种做法(不是每个人都会同意的做法) 总是围绕着用{和}在C ++中的代码块。 所以,而不是这个:

 if( true ) DoSomething(); else DoSomethingElse(); 

我会写这个:

 if( true ) { DoSomething(); } else { DoSomethingElse(); } 

这样,如果我(或其他人)稍后再回到此代码以将其他代码添加到其中一个分支,则不必担心忘记将代码括在花括号中。 我们的眼睛会直观地看到缩进是我们要做的事情的线索,但大多数语言不会。

有一件事我尽量使用,如果可能的话。 是为了避免使用布尔types作为参数的function,特别是如果有多个参数。

哪个更可读…

 compare("Some text", "Some other text", true); 

…要么…

 compare("Some text", "Some other text", Compare.CASE_INSENSITIVE); 

无可否认,这可能会有点矫枉过正,但是设置起来不难,提高了可读性,减less了作者错误地记住“真”意味着“是的,做大小写比较”还是“是的,以不区分大小写的方式进行比较“。

当然,例如…

 setCaseInsenstive(true); 

…简单而明显,可以独处。

如果没有理由在初始化后改变这个值,那么总是声明variables是“const”(或者在你的编程语言中是等价的)。

如果你习惯这样做,那么只要你看到一个非constvariables,你最终就会开始质疑。

@ Matt Dillard :

防御性编程的另一个措施是在每个开关代码块中总是使用一个break语句(即不要让case语句“通过”到下一个语句)。 唯一的例外是如果多个case语句被同样地处理。

有时处理案例X和案例Y意味着做几乎相同的事情,在这种情况下,下一个案例使代码更容易阅读。 尽pipe具体指出,但有一个评论:

 switch( type ) { case TypeA: do some stuff specific to type A // FALL THROUGH case TypeB: do some stuff that applies to both A and B break case TypeC: ... } 

如果你使用这个约定,那么所有的case语句都应该有一个break,一个return,一个continue或者一个表示它正在经历的注释。 但是,没有注释的空白案例可以:

 case TypeA: case TypeB: // code for both types 

与原始示例相同的是这个string字面比较技巧。 如果将string引用variables与string字面值进行比较,则可能会引发NullPointerException

 if(variable.equals("literal")) { // NullPointerExceptionpossible ... } 

你可以避免这种可能性,如果你翻转的东西,并把字面先。

 if("literal".equals(variable)) { // avoids NullPointerException ... } 

@Zack:

所以,你是说,而不是使用前缀命名约定来创build和总是使用两个新的类:SafeString和UnsafeString?

听起来对我来说是一个更好的select。 编译错误比运行时错误要好得多。

究竟。 布鲁斯·埃克尔(Bruce Eckel)写了一篇论文,认为静态打字是多余的,因为嘿,你正在编写testing用例,对不对? 错误。 当然,我正在编写testing用例,但编写好的testing用例很困难,而且还有很多工作要做。 尽可能多的获得帮助,编译时types检查几乎是最好的帮助。 此外,使用testing时,即使在使用自动化testing签入过程时,失败的testing也会比编译时错误晚得多,从而导致错误纠正延迟。 编译器可以给出更直接的反馈。

这并不是说我没有看到解释型语言的优点,但是dynamicinput可能是一个巨大的缺点。 我实际上对没有静态types的现代解释语言感到失望,因为正如Joel所表明的那样,这使得编写正确的代码变得更加困难,迫使我们采用像匈牙利语应用程序这样的二stream黑客。

嘿,我打了一半,想知道这是否真的有用,但是因为马特和格雷姆都已经发布了关于这个问题的答案,我会继续。

前几天,在给交换机增加一个新的情况下,我忘了结束这个情况。 一旦我发现错误,我改变了我的switch语句的缩进:

 switch(var) { case CONST1: statement; statement; statement; break; case CONST2: statement; statement; statement; case CONST3: statement; statement; break; default: statement; } 

(这是如何猜测大多数人通常会缩进):

 switch(var) { case CONST1: statement; statement; statement; break; case CONST2: statement; statement; statement; case CONST3: statement; statement; break; default: statement; } 

为了让缺失的突破脱颖而出,并让我更可能不会忘记添加一个,当我添加一个新的案例。 (当然,如果你有条件地在不止一个地方有条件的话,你不能这样做)

如果我只是做一些小事,比如设置一个variables或从case语句中调用函数,那么我经常会像这样构造它们:

 switch(var) { case CONST1: func1(); break; case CONST2: func2(); break; case CONST3: func3(); break; default: statement; } 

如果你想rest一下,那就太好了。 如果你的陈述不是相同的长度,加上空格直到中断alignment,以及其他任何有意义的东西:

 switch(var) { case CONST1: func1("Wibble", 2); break; case CONST2: longnamedfunc2("foo" , 3); break; case CONST3: variable = 2; break; default: statement; } 

虽然如果我将相同的parameter passing给每个函数,我会使用一个函数指针(下面是来自工作项目的实际代码):

 short (*fnExec) ( long nCmdId , long * pnEnt , short vmhDigitise , short vmhToolpath , int *pcLines , char ***prgszNCCode , map<string, double> *pmpstrd ) = NULL; switch(nNoun) { case NOUN_PROBE_FEED: fnExec = &ExecProbeFeed; break; case NOUN_PROBE_ARC: fnExec = &ExecProbeArc; break; case NOUN_PROBE_SURFACE: fnExec = &ExecProbeSurface; break; case NOUN_PROBE_WEB_POCKET: fnExec = &ExecProbeWebPocket; break; default: ASSERT(FALSE); } nRet = (*fnExec)(nCmdId, &nEnt, vmhDigitise, vmhToolpath, &cLines, &rgszNCCode, &mpstrd); 

在我的工程应用中,我将测量单位和参考框架作为variables名称的一部分。 这样我可以很容易地发现矛盾。 例子:

 r1_m = r2_ft; //wrong, units are inconsistent (meters vs feet) V1_Fc = V2_Fn; //wrong, reference frames are inconsistent //(Fc=Local Cartesian frame, Fn=North East Down frame) 

Konrad Rudolph写道:

应用程序匈牙利语只是对没有严格的types检查的编译器/语言的解决方法。

所以,你是说,而不是使用前缀命名约定来创build和总是使用两个新的类:SafeString和UnsafeString?

听起来对我来说是一个更好的select。 编译错误比运行时错误要好得多。

我总是在我的代码中使用大括号。

尽pipe可以写下:

 while(true) print("I'm in a loop") 

用圆括号来阅读是比较容易的。

 while(true){ print("Obviously I'm in a loop") } 

我认为这主要来自Java作为我的第一语言。

击败了一拳

防御性编程的另一个措施是在每个switch代码块中总是使用一个break语句(即不要让case语句“通过”到下一个语句)。 唯一的例外是如果多个case语句被同样地处理。

 switch( myValue ) { case 0: // good... break; case 1: case 2: // good... break; case 3: // oops, no break... SomeCode(); case 4: MoreCode(); // WARNING! This is executed if myValue == 3 break; } 

有时候这可能是期望的行为,但是从代码可读性的angular度来看,我认为重构“共享”代码以防止这种模糊性会更好。

如果编程语言允许,只在初始化它们的地方声明variables。 (换句话说,不要在每个函数的顶部声明它们)。如果你在同一个地方一致地声明和初始化,那么你有更less的机会有未初始化的variables。

避免嵌套循环,避免缩进代码超过几个级别。

通常可以通过将嵌套代码提取到函数/方法来重构嵌套循环或深度嵌套代码。 这通常会使代码更容易推理。

我书中最好的东西就是减less噪音。 这就是为什么我喜欢把exception分开的原因,这是一个很好的例子(确保处理错误的情况不是内联的)。

如果噪声降低了,那么您所查看的代码只是执行方法/函数名称所表示的特定目的,这使得识别错误代码变得更容易。

我玩过(0 ==variables)技巧,但是可读性有所损失 – 你必须在脑海中将事物转换为“如果variables等于零”。

我第二次马特·迪拉德(Matt Dillard)推荐围绕单线条件加上括号。 (如果可以的话,我会把它投票!)

当性能不重要时我使用另一个技巧:我将定义

 void MyClass::DoNothing() { } 

并用它来代替空的语句。 一个简单的分号很容易丢失。 可以像这样添加数字1到10(并将其存储在总和中):

 for (i = 1; i <= 10; sum += i++) ; //empty loop body 

但这是更可读和自我loggingIMO:

 for (i = 1; i <= 10; sum += i++) { DoNothing(); } 

不要使用只有1或2个字符的variables名称。 不要使用非常短的variables名称。 (除了循环。)

尽可能less使用全局variables。 当我把“int i”当成全局的时候真的是一个大问题(是的,我在一个真实的项目中看到过类似的东西:))

总是使用括号。

以下是来自Juval Lowy关于如何设置代码样式(C#)的非常好的阅读。 你可以在这里find它: http : //www.idesign.net/在右侧的“资源”

或者这里是一个直接的链接(这是一个压缩的PDF): http : //www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip

IDesign C#编码标准,用于Juval Lowy的开发指南和最佳实践

目录:

前言
1.命名约定和风格
2.编码实践
3.项目设置和项目结构
4.框架具体指导原则
– 4.1数据访问
4.2 ASP.NET和Web服务
– 4.3multithreading
– 4.4序列化
– 4.5远程
– 4.6安全
4.7 System.Transactions
– 4.8企业服务
5.资源

使用一点匈牙利语

标记variables可能会有所帮助。 例如,如果您正在清理用户input,您可能会这样做:

 $username = santize($rawusername); 

这样,如果你要说echo $rawusername ,它会看起来不对,因为它是。

当在JS中定义数组或对象(和其他语言中的模拟)时,我把值放在值的前面。 这是来自多次修改数组/对象,并由于缺less语法错误, – 如果行很长很容易忘记。

 oPerson = { nAge: 18 ,sFirstName: "Daniel" ,sAddress: "..." ,... } 

类似于长线的串接,我把+放在前面而不是末端“

 sLongString = "..........................................................." + ".........................................................." + ".............." + ""; 

如果你正在玩HTML的土地,尝试获得validation的代码 – 有几次来自HTMLvalidation器插件的红色小x给了我一个方便的捷径,以解决我还没有注意到的问题。

获得有效的HTML可能并不总是可能的,但通常值得一试。