最喜欢的(聪明的)防御性编程最佳实践


148

如果您必须选择最喜欢的(聪明的)技术进行防御性编码,它们将是什么?尽管我当前使用的语言是Java和Objective-C(具有C ++背景),但是可以使用任何语言进行回答。除了我们这里70%以上的人已经知道的防御技术外,这里还将重点介绍巧妙的防御技术。因此,现在该深入了解您的绝招了。

换句话说,除了这个无趣的示例,请尝试思考其他问题:

  • if(5 == x) 代替 if(x == 5):避免意外分配

以下是一些有趣的最佳防御性编程实践的示例(特定于语言的示例在Java中):

-锁定变量,直到您知道需要更改它们为止

也就是说,您可以声明所有变量,final直到知道需要进行更改为止,此时可以删除final。一个普遍未知的事实是,这对于方法参数也有效:

public void foo(final int arg) { /* Stuff Here */ }

-发生不良情况时,请留下大量证据

发生异常时,您可以执行许多操作:显然,将其记录下来并执行一些清理工作只是其中的一部分。但是您也可以留下一些证据(例如,在调试器中将变量设置为“ UNABLE TO LOAD FILE”或99999等哨兵值将很有用,以防您碰巧遇到异常catch-block)。

-关于一致性:细节决定成败

与您正在使用的其他库保持一致。例如,在Java中,如果要创建一种提取一系列值的方法,则将下限包括在内,将上限值排除在外。这将使其与String.substring(start, end)以相同方式运行的方法保持一致。在Sun JDK中,您会发现所有这些类型的方法都具有这种行为,因为它会进行各种操作,包括与数组一致的元素迭代,其中索引的范围是从零(包括)到数组的长度(不包括)。

那么,您最喜欢的防御方法是什么?

更新:如果您还没有的话,请随时鸣叫。在选择正式答案之前,我有机会获得更多回应。


我想在这里代表30%的愚昧人,他们可能不了解简单的技术。有人与每个人都应该知道的“显而易见”技术有联系吗?
elliot42


您为什么选择官方答案?这听起来完全不明智。
bzlm

好吧,在编程方面,即使是聪明,也应该以“最少惊讶的规则”为荣。对于我来说,打破标记最佳答案的堆栈溢出精神将违反堆栈溢出协议(因此违反了上述规则)。除此之外:我喜欢关闭:-)
Ryan Delucchi 09年

Answers:


103

在c ++中,我曾经喜欢重新定义new,因此它提供了一些额外的内存来捕获篱笆张贴错误。

目前,我更喜欢避免防御性编程,而倾向于测试驱动开发。如果您在外部快速地捕获错误,则无需通过防御性操作使代码混乱,您的代码会变干,并且最终可以减少需要防御的错误。

正如WikiKnowledge所写

避免防御性编程,而要快速失败。

通过防御性编程,我的意思是习惯于编写尝试补偿数据中某些故障的代码,编写假定调用者可能提供的数据不符合调用者与子例程之间的约定并且子例程必须以某种方式应对的代码用它。


8
防御性编程正试图处理程序其他部分引入的非法条件。处理不正确的用户输入是完全不同的事情。
2009年

5
Errr ...请注意,防御性编程的定义甚至与问题中隐式使用的定义并不接近。
溶胶

15
切勿避免防御性编程。事情不是要“补偿”数据中的故障,而是要保护自己免受旨在使您的代码执行不应做的事情的恶意数据的侵害。请参见缓冲区溢出,SQL注入。没有什么比在XSS下失败更快的了,但是还不是很漂亮
JorgeCórdoba2009年

6
我认为“快速失败”过程是防御性编程的一种形式。
Ryan Delucchi 09年

6
@ryan完全正确,快速失败是一个很好的防御概念。如果您无法进入自己的状态,请不要试图保持步,失败并大声疾呼!如果您是元数据驱动的,则特别重要。防御性编程不只是检查您的参数...
比尔K 2009年

75

的SQL

当我必须删除数据时,我写

select *    
--delete    
From mytable    
Where ...

当我运行它时,我会知道我是否忘记或破坏了where子句。我很安全。如果一切正常,我将突出显示“-”注释标记后的所有内容并运行它。

编辑:如果我要删除大量数据,我将使用count(*)而不是*


我在无法选择文本的iPhone上编写了此代码。如果有人可以将我的代码标记为代码,将不胜感激。谢谢。
约翰·麦金太尔

6
我只是将其包装在交易中,这样如果我搞砸了就可以回滚...
rmeador

36
开始交易| 回滚交易
Dalin Seivewright 09年

3
+1是,我想可以用事务代替它,但是我喜欢这样做的简单性。
Ryan Delucchi 09年

3
使用事务更好。这意味着您可以将其运行数十次,并查看实际效果,直到您确定正确并可以提交为止。
瑞安·伦迪

48

在应用程序启动时分配合理的内存块-我认为Steve McConnell 在Code Complete 中将其称为内存降落伞

如果发生严重问题并且需要您终止,则可以使用此方法。

预先分配此内存为您提供了一个安全网,因为您可以释放它,然后使用可用的内存来执行以下操作:

  • 保存所有持久数据
  • 关闭所有适当的文件
  • 将错误消息写入日志文件
  • 向用户提出有意义的错误

我看过这叫做雨天基金。

42

在每个没有默认大小写的switch语句中,我添加了一个大小写,该错误使程序中止并显示一条错误消息。

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

2
或用现代怪异的语言抛出。
汤姆·哈特芬

2
声明的优点是可以在编译时全局禁用其效果。但是,在某些情况下,如果您的语言支持,则抛出可能更合适。
Diomidis Spinellis 2009年

2
声明有另一个优点,使它对于生产代码很有用:当出现问题时,它可以准确告诉您失败的原因以及错误的源代码。这样的错误报告真是太好了!
梅森惠勒

2
@Diomidis:另一个角度是:Assert的缺点是可以在编译时全局禁用其效果。
幻灭

1
投掷确实更好。然后确保您的测试覆盖面足够。
多米尼克·克罗宁

41

处理枚举(C#)的各种状态时:

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

然后,在一些常规程序中...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

在某个时候,我将向该枚举添加另一个帐户类型。当我这样做时,我会忘记修复此switch语句。因此,Debug.Fail崩溃令人震惊(在“调试”模式下),以引起我对这一事实的注意。当我添加时case AccountType.MyNewAccountType:,可怕的崩溃停止了……直到我添加另一种帐户类型,然后忘记在此处更新案例。

(是的,这里的多态性可能更好,但这只是我脑海中的一个例子。)


4
如果您不处理case块中的某些枚举,则大多数编译器足够聪明,可以发出警告。但是将默认值设置为fail仍然是一种很好的形式-枚举只是一个数字,如果您遇到内存损坏,则可能会出现一个无效值。
亚当·霍斯

在c中,我习惯在末尾添加一个invalidAccountType。有时这很有用。
2009年

1
@Adam-如果重新编译所有内容,则编译器将发出警告。如果添加新类并且仅进行部分重新编译,则可能不会注意到上述内容,并且默认情况下会节省您的时间。它不会只是默默地失败。
Eddie

4
注释Slashene暗示了这一突破。:P
Ryan Lundy

2
调试之后,应为生产代码添加一个“引发新的NotSupportedException()”。
user7116

35

用字符串输出错误消息时(尤其是取决于用户输入的错误消息),我总是使用单引号''。例如:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

缺少引号%s真的很不好,因为说filename是一个空字符串或只是空格或其他东西。打印出来的消息当然是:

ERROR: Could not open file

因此,总是最好这样做:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

然后,至少用户会看到以下内容:

ERROR: Could not open file ''

我发现这在最终用户提交的错误报告的质量方面有很大的不同。如果有这样一个有趣的错误消息而不是通用的发音,则他们更有可能复制/粘贴该消息,而不仅仅是写“它不会打开我的文件”。


4
好人,我也看到了这个问题
Alex Baranosky,2009年

28

SQL安全

在编写将修改数据的任何SQL之前,我将整个内容包装在回滚的事务中:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

这样可以防止您永久执行错误的删除/更新。而且,您可以执行整个操作并验证合理的记录计数,或者SELECT在SQL和之间添加语句ROLLBACK TRANSACTION以确保一切正常。

当您完全确定它可以达到预期的效果时,请更改ROLLBACKCOMMIT并真正运行。


你把我打败了!:-)
霍华德·平斯利2009年

2
我过去一直都这样做,但是这会导致数据库集群上的大量额外开销,不得不停止。
Zan Lynx

25

对于所有语言:

缩小变量范围到最小可能的要求。只是提供了将它们带入下一个语句的Eschew变量。不存在的变量是您不需要了解的变量,因此您不承担任何责任。出于相同的原因,请尽可能使用Lambda。


5
避开部分到底是什么意思?有时我会引入仅在下一行之前有效的变量。它们用作表达式的名称,这使代码更具可读性。
zoul

4
是的,我也不同意。对于非常复杂的表达式,通常最好使用临时变量将它们分解为两个或更短或更简单的表达式。它在维护中易于出错,编译器将优化临时文件
Cruachan

1
我同意,在某些特殊情况下,为了清楚起见我会声明变量(有时会创建调试目标)。我的经验是,一般做法是朝相反的方向犯错。
dkretz

我同意这两种观点。尽管当我将表达式分解为临时变量时,我还是尝试在单独的范围内这样做。Lambda和辅助方法都很好。
Erik Forbes,2009年

19

如有疑问,请轰炸该应用程序!

每种方法的开头检查每个参数(无论是自己明确编码还是在这里使用基于合同的编程都无关紧要),并在有任何先决条件的情况下使用正确的异常和/或有意义的错误消息进行轰炸没见过。

我们在编写代码时都知道这些隐式先决条件,但是,如果未明确检查它们,则当以后出现问题时,我们将为自己创建迷宫,并且数十个方法调用堆栈将症状的发生和实际位置分开不满足前提条件的地方(=问题/错误实际上在哪里)。


当然,您可以:使用通用代码(小型库,C#扩展方法,无论如何)来简化此过程。所以您可以写点glike param.NotNull("param")代替if ( param == null ) throw new ArgumentNullException("param");
peSHIr

2
或使用基于合同的编程,例如Spec#!
bzlm

取决于它是什么应用程序。我希望编写电传飞机和起搏器代码的人们不要像peSHIr那样认为。
MarkJ

6
@MarkJ:你真的不明白吗?如果炸弹过早(在开发和测试过程中),则在生产时切勿炸弹。所以我真的希望他们这样编程!
peSHIr

我必须承认我不太喜欢,特别是对于私有方法和受保护的方法。原因:a)您使代码杂乱无章,完全不符合业务需求b)这些检查很难测试非公共方法c)在许多情况下无用,例如,因为null值将导致方法失败无论如何,两行之后
Erich Kitzmueller

18

在Java中(尤其是对于集合),请使用API​​,因此,如果您的方法返回List类型(例如),请尝试以下操作:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

不允许任何不必要的事情逃避您的课堂!


+1在C#中有为此的只读集合。
tobsen

1
Java +1。我一直在用它
Fortyrunner

+1 ...我经常这样做(尽管我希望更多的人记得这样做)。
Ryan Delucchi 09年

只要确保没有其他东西有基础列表变量的实例,否则这对您没有多大的帮助...
GreenieMeanie

5
仅供参考,Collections.unmodifiableList返回列表的不可变视图,而不是不可变副本。因此,如果原始列表被修改,视图也将被修改!
Zarkonnen's

17

在Perl中,每个人都

use warnings;

我喜欢

use warnings FATAL => 'all';

这将导致代码因任何编译器/运行时警告而死亡。这在捕获未初始化的字符串时最有用。

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

希望这是更多的广告...
DJG

16

C#:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

在Java中可能也是大多数OO语言相同。
MiniQuark

这在Objective-C中很好,可以将消息发送到nil(具有特定许可证的“空对象的调用方法”)。调用[nil isEqualToString:@“ Moo”]返回false。
zoul

我不同意C#示例。更好的解决方案是使用“ if(myString ==“ someValue”)”。也没有null引用异常,并且肯定更具可读性。
Dan C.

13
该示例仅允许您在程序中隐藏潜在的危险情况。如果您不希望它为null,则希望它引发异常,并且如果您希望它为null,则应照此处理。这是一个坏习惯。
rmeador

2
在C#中,我总是只使用string.Equals(<string1>,<string2>)。它们中的任何一个都不为空都没有关系。
达西·卡塞尔曼

15

在对string.IsNullOrEmpty进行c#检查之前,对字符串进行任何操作,例如length,indexOf,mid等

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[编辑]
现在,我也可以在.NET 4.0中执行以下操作,该操作还检查该值是否只是空白

string.IsNullOrWhiteSpace(myString)

7
那不是防御性的,它是在忽略问题。应该是if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
enashnash 2012年

14

在Java和C#中,为每个线程取一个有意义的名称。这包括线程池线程。它使堆栈转储更加有意义。甚至给线程池线程起一个有意义的名字要花费更多的精力,但是如果一个线程池在长时间运行的应用程序中有问题,我会导致发生堆栈转储(您确实了解SendSignal.exe,对吗? ),获取日志,而不必中断正在运行的系统,我可以知道哪些线程是...什么。不管发生什么问题,僵局,泄漏,增长。


而且-在Windows上-也适用于C ++!(启用了特殊的SEH异常抛出和后续捕获)。
Brian Haak 2013年

12

使用VB.NET,默认情况下,整个Visual Studio的Option Explicit和Option Strict均处于打开状态。


尽管我使用的是较旧的代码,所以我没有严格打开选项(导致太多的编译器错误无法修复),同时打开这两个选项可以节省很多精力(就我而言)疼痛。
Kibbee

1
顺便说一下,对于任何转换不使用Option Strict的旧代码的人,请注意,对于自动转换为String的项目,它们不使用ToString();他们正在使用强制转换字符串。在我早期的.NET时代,我会将其更改为使用ToString(),这会破坏工作,尤其是对于枚举。
Ryan Lundy

10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

然后将所有“ delete pPtr ”和“ delete [] pPtr ”调用替换为SAFE_DELETE(pPtr) SAFE_DELETE_ARRAY(pPtr)

现在,如果错误地删除了指针“ pPtr”,则会出现“访问冲突”错误。它比随机存储器损坏要容易得多。


1
最好使用模板。它的作用域和可重载。

我只是想说。使用模板而不是宏。这样,如果您需要单步执行代码,则可以。
史蒂夫·罗

我了解到哪种方法更容易调试,以重返校园。从那以后我再也没有犯过这个错误。:)
Greg D

3
或使用智能指针...或,尽可能避免新/删除。
Arafangion

1
@Arafangion:避免delete。使用new是好的,只要新的对象被通过智能指针拥有。
Alexandre C.

10

使用Java,即使在断言关闭的情况下运行生产代码,使用assert关键字也很方便:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

即使没有断言,至少代码也记录了预期将参数设置在某个地方的事实。请注意,这是一个私有帮助器函数,而不是公共API的成员。该方法只能由您调用,因此可以确定如何使用它。对于公共方法,最好为无效输入抛出一个真正的异常。


在.NET中,Debug.Assert执行相同的操作。每当您认为“该引用在这里不能为空,对吗?”的地方时,都可以在其中放置一个Debug.Assert,这样,如果它可以为空,则可以修复错误或更改假设。
Ryan Lundy

1
+1,公共方法应引发合同失败,私有方法应断言
user7116

9

readonly找到ReSharper之前,我没有找到关键字,但是现在我本能地使用它,尤其是对于服务类。

readonly var prodSVC = new ProductService();

我可以在所有字段上使用Java的等效“ final”关键字。它确实的确可以使您避免做出笨拙的举动,例如无需设置字段/编写可多次设置字段的混乱开关。我不太可能标记局部变量/参数,但我想这不会有什么坏处。
Outlaw程序员,

C#不允许您将局部变量标记为只读,因此我们什至没有选择...
Jason Punyon

我不确定这里的readonly意味着什么,但是Java的final对我来说还不够。它仅表示指向对象的指针不变,但是无法防止对象本身发生更改。
Slartibartfast

在C#中,只读结构将阻止对其进行更改。
塞缪尔

9

在Java中,当某些事情发生并且我不知道为什么时,有时会像这样使用Log4J:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

这样,我得到了一个堆栈跟踪,向我显示了我是如何陷入意外情况的,比如说一个从未解锁的锁,一个不能为null的null等等。显然,如果抛出了真正的异常,则不需要这样做。在这种情况下,我需要查看生产代码中发生的事情,而又不会真正干扰其他任何事情。我希望抛出一个异常,我没赶上之一。我只想记录一条堆栈跟踪,并记录一条适当的消息以将我标记为正在发生的事情。


嗯,这有点整洁,但我担心会引起功能代码和错误处理代码的混淆。虽然try-catch机制可能很笨拙,但它强制一个将异常重新执行从一个块(try)转移到另一个块(catch),这是所有错误处理的地方
Ryan Delucchi 09年

1
这不用于错误处理,而仅用于诊断。这使您可以查看代码进入意外情况的路径,而不会中断代码流。当方法本身可以处理意外情况时,我会使用此方法,但仍然不应发生这种情况。
Eddie

2
您还可以使用new Exception(“ message”)。printStackTrace(); 不需要抛出或捕获,但是您仍然可以在日志中获得良好的堆栈跟踪。显然,这不应该包含在生产代码中,但是对于调试非常有用。
乔恩

9

如果使用的是Visual C ++,则每当重写基类的方法时,都应使用override关键字。这样,如果有人碰巧更改了基类签名,它将抛出编译器错误,而不是错误地调用方法。如果早已存在,这将为我节省几次。

例:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

对于Java,这是类Foo {void doSomething(){}} class Bar {@Override void doSomething(){}}如果缺少注释,则会发出警告(因此,您不能以静默方式覆盖未使用的方法意思是),并且当注释存在但没有覆盖任何内容时。
乔恩

很好...我对此一无所知...太糟糕了,似乎是特定于Microsoft的...但是一些不错的#define可以帮助使其变得可移植
e.tadeu 2010年

8

我从Java中学到几乎没有无限期地等待锁解锁,除非我真正期望它可能需要无限长的时间。如果实际上,锁应该在几秒钟内解锁,那么我将只等待一定时间。如果锁没有解锁,那么我会抱怨并将堆栈转储到日志中,并根据对系统稳定性最有利的方式,要么继续将其解锁,要么就好像从未解锁一样继续。

在我开始这样做之前,这有助于隔离一些神秘的竞态条件和伪死锁条件。


1
像这样使用超时等待是其他更严重问题的征兆。
dicroce

2
在必须具有高正常运行时间的系统中,最好至少获取诊断信息,以便您能够发现问题。有时,您希望在系统处于已知状态时退出线程或将响应返回给调用方,因此您需要等待信号量。但您不想永远挂死
埃迪(Eddie)2009年

8

C#

  • 验证公共方法中引用类型参数的非空值。
  • sealed在类上花了很多时间,以避免在不需要的地方引入依赖。允许继承应该显式完成,而不是偶然。

8

发出错误消息时,至少尝试提供程序在决定引发错误时所具有的相同信息。

“拒绝权限”告诉您存在权限问题,但您不知道问题发生的原因或原因。“不能写事务日志/ my / file:只读文件系统”至少会让您知道做出决定的依据,即使该决定是错误的-尤其是如果它是错误的:文件名错误?开错了吗?其他意外错误?-并让您知道遇到问题时所在的位置。


1
在为错误消息编写代码时,请阅读该消息并询问您接下来想知道什么,然后添加它。重复直到不合理。例如:“超出范围。” 什么超出范围?“ Foo计数超出范围。” 有什么价值?“ Foo计数(42)超出范围。” 范围是多少?“ Foo计数(42)超出范围(549至666)。”
HABO

7

在C#中,使用as关键字进行转换。

string a = (string)obj

如果obj不是字符串,将抛出异常

string a = obj as string

如果obj不是字符串,将保留为null

您仍然需要考虑null,但这通常比查找强制转换异常更直接。有时,您需要“强制或爆炸”类型的行为,在这种情况下(string)obj,首选语法。

在我自己的代码中,我发现我使用as语法的时间约为75%,而使用(cast)语法的时间约为25%。


正确,但是现在您必须在使用引用之前检查null。
布赖恩·拉斯穆森

7
没明白。对我来说似乎是一个错误的决定,而是选择null。您将在运行时的某处遇到问题,而没有任何暗示其原始原因的提示。
Kai Huppmann

是。仅当您确实想在非正确类型的null时才有用。在某些情况下很有用:在Silverlight中,控制逻辑和设计通常是分开的,该逻辑只想在按钮上使用控件“ Up”。如果不存在,就好像它不存在(= null)。
桑德

2
这似乎与堡垒中的大宴会厅窗户一样具有防御性。但这是一个美景!
Pontus Gagge,2009年

1
这让我想起了一个没有使用异常的人,因为“他们破坏了程序”。如果您希望某个对象不是您期望的类时的某些行为,我想我会用另一种方式编写代码。is/instanceof浮现在脑海。
Kirschstein

6

任何事情做好准备输入如果遇到任何意外输入,请转储到日志中。(有原因。如果您正在从用户那里读取密码,请不要将其转储到日志中!也不要每秒将数千种此类消息记录到日志中。在记录日志之前,请先确定其内容,可能性和频率)

我不仅在谈论用户输入验证。例如,如果您正在读取希望包含XML的HTTP请求,请为其他数据格式做好准备。我很惊讶地看到HTML响应只希望XML,直到我看到并看到我的请求正在通过一个我不知道的透明代理并且客户声称对它不了解为止,并且该代理在尝试完成该请求时超时了请求。因此,代理向我的客户端返回了一个HTML错误页面,使客户端只希望XML数据的混乱变得混乱。

因此,即使您认为自己控制了导线的两端,也可以得到意想不到的数据格式,而不会涉及任何恶意。做好准备,进行防御性编码,并在输入意外的情况下提供诊断输出。


6

我尝试使用按合同设计方法。可以用任何语言模拟运行时。每种语言都支持“断言”,但是编写更好的实现可以使您以更有用的方式管理错误,这既容易又方便。

排名前25位最危险的编程错误的“不正确的输入验证”是在一节“组件间不安全互动”最危险的错误。

在方法开始时添加前提条件断言是确保参数一致的好方法。在方法结束时,我写了后置条件,以检查输出是否符合预期。

为了实现不变式,我在任何检查“类一致性”的类中都编写了一个方法,该方法应通过前置条件和后置条件宏进行自定义调用。

我正在评估代码合同库


6

爪哇

Java api没有不可变对象的概念,这很糟糕!在这种情况下,Final可以为您提供帮助。标签每类是不可变的与最终并准备类相应

有时在局部变量上使用final很有用,以确保它们永远不会改变其值。我发现这在丑陋但必要的循环构造中很有用。它只是为了容易意外地重用变量,即使它被要求是一个常量也是如此。

在您的吸气剂中使用防御复制。除非返回原始类型或不可变的对象,否则请确保复制该对象以免违反封装。

切勿使用克隆,而使用副本构造函数

了解equals和hashCode之间的约定。这经常被违反。问题是它在99%的情况下都不会影响您的代码。人们会覆盖等于,但并不在乎hashCode。在某些实例中,您的代码可能会中断或表现异常,例如,将可变对象用作映射中的键。


5

我忘记echo用PHP 编写太多次了:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

我将永远需要尝试找出为什么-> baz()没有返回任何内容,而实际上我只是没有回声它!:-S因此,我做了一个EchoMe可以包装在任何应回显的值周围的类:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

然后在开发环境中,我使用了EchoMe包装应该打印的东西:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

使用该技术,第一个缺少的示例echo现在将引发异常...


析构函数不应该检查$ this-> printed!== true吗?
卡斯滕2009年

您应该考虑使用模板系统,在几乎所有系统上将php嵌入html都不是最佳选择。
Cruachan

也许我缺少什么?但是在我看来,这似乎是在尝试补偿编码:AnObject.ToString而不是Writeln(AnObject.ToString)
破灭

是的,但是错误很容易在PHP中产生
太多的PHP

4

C ++

键入new时,必须立即键入delete。特别是对于数组。

C#

在访问属性之前,请检查是否为null,尤其是在使用Mediator模式时。对象被传递(如前所述,然后应使用as强制转换),然后检查是否为null。即使您认为它不会为null,也请进行检查。我很惊讶


我喜欢你的第一点。例如,当编写返回某物集合的方法时,我会做类似的事情。我在第一行创建集合,然后立即编写return语句。剩下的就是填充集合的填充方式。
Outlaw程序员,2009年

5
在C ++中,键入new时,应立即将该指针分配给AutoPtr或引用计数的容器。C ++具有析构函数和模板;明智地使用它们可以自动处理删除。
2009年

4

使用允许动态,运行时日志级别调整的日志系统。通常,如果您必须停止某个程序以启用日志记录,那么您将丢失该错误发生的任何罕见状态。您需要能够在不停止该进程的情况下打开更多日志记录信息。

另外,Linux上的'strace -p [pid]'将显示您希望系统调用某个进程(或linux线程)。乍一看可能很奇怪,但是一旦您习惯了通常由libc调用进行的系统调用,您就会发现这对于现场诊断非常有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.