我们如何使错误的代码看起来不正确?我们使用什么模式来避免语义错误?
自从我第一次犯了在if
中进行赋值的错误以来,我就总是这样写ifs:
if (CONST == variable) {
避免这样做的常见错误(至少对我而言):
if (variable = CONST) { //WRONG, assigning 0 to variable
自从我读了Joel Spolsky的论文《使错误的代码看起来错误》以来,我一直在尝试将他的建议付诸实践。
那么,我们还使用什么其他模式来使错误的代码看起来错误,或者如果我们犯了语义错误,则可以强制语法错误?
解决方案
回答
不要使用仅相差1或者2个字符的变量名。不要使用非常短的变量名。 (除了循环。)
尽可能少使用全局变量。当我们将" int i"作为一个全局对象时,这确实是一个大问题(是的,我在现实生活项目中已经看到过类似的内容:))
始终使用括号。
回答
我使用的一种做法(并不是所有人都不会同意的一种做法)总是在代码块(在C ++中)中使用{和}。所以代替这个:
if( true ) DoSomething(); else DoSomethingElse();
我会这样写:
if( true ) { DoSomething(); } else { DoSomethingElse(); }
这样,如果我(或者其他人)稍后返回此代码以将更多代码添加到分支之一,则不必担心忘记将代码括在花括号中。我们的眼睛会在视觉上看到缩进作为我们正在尝试做的线索,但大多数语言不会。
回答
我总是在代码中使用花括号。
虽然可以这样写:
while(true) print("I'm in a loop")
括号清晰易读。
while(true){ print("Obviously I'm in a loop") }
我认为这主要是因为Java是我的第一语言。
打败
回答
与原始示例相同的地方是此字符串文字比较技巧。如果将字符串引用变量与字符串文字进行比较,则有可能抛出NullPointerException
。
if(variable.equals("literal")) { // NullPointerExceptionpossible ... }
如果我们将内容翻转过来并放在原先的位置,则可以避免这种可能性。
if("literal".equals(variable)) { // avoids NullPointerException ... }
回答
我发现使错误的代码在编译器中看起来很错误很重要。在实践中(仅在使用强类型语言时),这意味着将省略任何类型的变量前缀(甚至是Apps Hungarian),而应使用不同的类型。以Joel的示例为例,如果有两种不同的类型来表示原始字符串和经过净化的字符串,并且两者之间没有隐式转换,那么Apps Hungarian地址甚至不会发生。
Word文档坐标也是如此。在某种程度上,Apps Hungarian只是对没有足够严格的类型检查的编译器/语言的一种解决方法。
回答
以下是Juval Lowy撰写的有关如何设置代码样式(C#)的很好的读物。我们可以在这里找到它:http://www.idesign.net/在"资源"下的右侧
或者直接链接(压缩的PDF):http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip
The IDesign C# Coding Standard, for development guidelines and best practices by Juval Lowy Table of Contents: Preface 1. Naming Conventions and Style 2. Coding Practices 3. Project Settings and Project Structure 4. Framework Specific Guidelines - 4.1 Data Access - 4.2 ASP.NET and Web Services - 4.3 Multithreading - 4.4 Serialization - 4.5 Remoting - 4.6 Security - 4.7 System.Transactions - 4.8 Enterprise Services 5. Resources
回答
Konrad Rudolph wrote: Apps Hungarian is only a workaround for compilers/languages that have no strict enough type checking.
因此,我们是在说而不是使用前缀命名约定来创建并始终使用两个新类:SafeString和UnsafeString?
听起来对我来说是一个更好的选择。编译错误比运行时错误要好得多。
回答
如果我们在HTML领域玩游戏,请尝试获取验证代码,HTML验证程序插件中的小红色x几次给了我一个方便的快捷方式来解决我什至没有注意到的问题。
不一定总能获得有效的HTML,但通常值得一试。
回答
防御性编程的另一种措施是始终在每个" 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; }
有时候这可能是理想的行为,但是从代码可读性的角度来看,我认为最好重构"共享"代码以防止这种歧义。
回答
@Zack:
So, you're saying that rather than using a prefix naming convention to instead create and always use two new classes: SafeString and UnsafeString? Sounds like a much better choice to me. Compile errors are much better than run-time errors.
确切地。布鲁斯·埃克尔(Bruce Eckel)撰写了一篇文章,指出静态类型是多余的,因为,嘿,我们还是在编写测试用例,对吗?错误的。当然,我在写测试用例,但是编写好的测试用例很辛苦并且需要很多工作。最好获得所有帮助,并且编译时类型检查几乎是我们可以获得的最佳帮助。此外,在使用测试时,即使使用自动签入测试过程,也要比编译时错误晚得多地通知失败的测试,从而导致错误纠正延迟。编译器可以给出更直接的反馈。
这并不是说我看不到解释语言的优点吗?我知道,但是动态类型可能是一个巨大的缺点。我实际上很失望,因为没有静态类型的现代解释语言,因为正如Joel所展示的那样,这使得编写正确的代码变得更加困难,并迫使我们诉诸于诸如Apps Hungarian之类的二等黑客。
回答
@马特·迪拉德(Matt Dillard):
One other measure of defensive programming is always to use a break statement in each switch code block (i.e. do not let a case statement "fall through" to the next one). The only exception is if multiple case statements are to be handled identically.
有时处理案例X和案例Y意味着做几乎相同的事情,在这种情况下,进入下一个案例会使代码更易于阅读。不过,最好在注释中特别指出:
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
回答
如果编程语言允许,请仅在初始化变量时声明变量。 (换句话说,不要在每个函数的顶部都声明它们。)如果在同一位置一致地声明和初始化,则拥有未初始化变量的机会要少得多。
回答
如果初始化后没有理由更改变量,则始终将变量声明为" const"(或者编程语言中的等效变量)。
如果我们养成这种习惯,那么每当看到非常量变量时,我们最终都会开始质疑。
回答
避免嵌套循环,并避免缩进代码超过两个层次。
通常可以通过将嵌套代码提取到函数/方法中来重构嵌套循环或者深度嵌套的代码。这通常使代码更易于推理。
回答
嘿,我已经输入了一半,想知道它是否真的有用,但是由于Matt和Graeme都已经发布了有关此问题的答案,因此我将继续。
几天前,在向交换机添加新案例时,我忘了以休息结束案例。找到错误后,我将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; }
为了使丢失的中断更加突出,并使我更有可能在添加新案件时不要忘记添加一个。 (当然,如果我们有条件地在一个以上的位置中断,我们将无法执行此操作,而我有时会这样做)
如果我只是做一些琐碎的事情,例如设置变量或者从case语句中调用函数,那么我通常会像这样构造它们:
switch(var) { case CONST1: func1(); break; case CONST2: func2(); break; case CONST3: func3(); break; default: statement; }
如果我们错过休息,那将变得非常明显。如果语句长度不一样,请添加空格,直到断行对齐为止,并添加其他有意义的内容:
switch(var) { case CONST1: func1("Wibble", 2); break; case CONST2: longnamedfunc2("foo" , 3); break; case CONST3: variable = 2; break; default: statement; }
尽管如果我将相同的参数传递给每个函数,我将使用函数指针(以下是工作项目中的实际代码):
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);
回答
我书中最好的事情就是减少噪音。这就是为什么我喜欢将关注点分离,例外是一个很好的例子(确保错误情况的处理不是内联的)。
如果降低了噪音,则我们正在查看的代码仅在此处执行方法/函数名称所建议的特定目的,这使发现不良代码变得更加容易。
回答
我玩过(0 ==变量)技巧,但是可读性有所下降-我们必须在思维上进行切换,才能将其读取为"如果变量等于零"。
我赞同马特·迪拉德(Matt Dillard)关于在单行条件句中加括号的建议。 (如果可以的话,我会投票赞成!)
当性能不是很关键时,我将使用另一种技巧:我将定义
void MyClass::DoNothing() { }
并使用它代替null语句。裸分号很容易丢失。可以像这样将数字1加到10(并将其总和存储):
for (i = 1; i <= 10; sum += i++) ; //empty loop body
但这是更具可读性和自我记录的IMO:
for (i = 1; i <= 10; sum += i++) { DoNothing(); }
回答
如果可能,我尝试使用一件事。是避免使用布尔类型作为函数的参数,尤其是在有多个参数的情况下。
哪个更易读...
compare("Some text", "Some other text", true);
...或者...
compare("Some text", "Some other text", Compare.CASE_INSENSITIVE);
诚然,有时候这可能有点过大,但设置起来并不难,提高了可读性,并减少了作者错误地记住" true"是"是的,以区分大小写的方式进行比较"还是"是的,以不区分大小写的方式进行比较'。
当然,例如...
setCaseInsenstive(true);
...简单明了,足以让他一个人呆着。
回答
在我的工程应用程序中,我使测量单位和参考框架成为变量名称的一部分。这样,我可以轻松发现不一致之处。例子:
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)
回答
用一点匈牙利语
标记变量可能会有所帮助。例如,如果要清理用户输入,则可以执行以下操作:
$username = santize($rawusername);
这样,如果我们要说echo $ rawusername
,它将看起来是错误的,因为是这样。