当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《最喜欢的(聪明的)防守编程最佳实践》经验,为你挑选了28个好方法。

如果你必须为防御性编码选择你最喜欢的(聪明的)技术,它们会是什么?虽然我目前的语言是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块).

- 谈到一致性:魔鬼在细节中

与您正在使用的其他库一致.例如,在Java中,如果要创建一个提取一系列值的方法,则使下限包含且上限为exclusive.这将使其与String.substring(start, end)以相同方式操作的方法一致.您将在Sun JDK中找到所有这些类型的方法,因为它使各种操作包括元素的迭代与数组一致,其中索引从零(包括)到数组的长度(独占).

那么你最喜欢的防守做法是什么?

更新:如果您还没有,请随意加入.在选择正式答案之前,我有机会获得更多回复.



1> Joe Soul-bri..:

在c ++中,我曾经喜欢重新定义new,以便它提供一些额外的内存来捕获fence-post错误.

目前,我更倾向于避免采用防御性编程来支持测试驱动开发.如果你快速和外部地发现错误,你就不需要用防御性操作来捣乱你的代码,你的代码是干的,你最终会减少你必须防御的错误.

正如WikiKnowledge所写:

避免防御性编程,快速失败.

防御性编程我指的是编写代码的习惯,这些代码试图弥补数据中的某些失败,编写代码假定调用者可能提供的数据不符合调用者和子例程之间的契约,并且子例程必须以某种方式应对用它.


永远不要避免防御性编程.事情不是"补偿"数据中的故障,而是保护自己免受恶意数据的攻击,这些恶意数据旨在使您的代码执行不应该执行的操作.请参阅缓冲区溢出,SQL注入.没有什么比XSS下的网页更快失败,但它并不漂亮
防御性编程试图处理程序其他部分引入的非法条件.处理不正确的用户输入是完全不同的事情.
我认为"快速失败"程序是防御性编程的一种形式.
@ryan是完全正确的,快速失败是一个很好的防守概念.如果你所处的状态是不可能的,不要试图保持跛行,快速失败!如果您是元数据驱动,则非常重要.防御性编程不只是检查你的参数......
错误...请注意,这种防御性编程的定义甚至不接近于问题中隐含使用的定义.
我对"防御性编程"有更广泛的定义(很像:http://en.wikipedia.org/wiki/Defensive_programming).编写"快速失败"函数的重点是*保护*某些对象处于错误状态时依赖于它们的函数.

2> John MacInty..:

SQL

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

select *    
--delete    
From mytable    
Where ...

当我运行它时,我会知道我是否忘记或拙劣的where子句.我有安全感.如果一切正常,我会在' - '评论标记之后突出显示所有内容,然后运行它.

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


开始交易| 回滚交易
我只是将它包装在一个交易中,所以如果我搞砸了就可以回滚......
+1是的,我想这可以用一个事务代替,但我喜欢这样做的简单性.
使用交易更好; 这意味着你可以运行它几十次,并看到*实际效果*,直到你确定你已经正确并且可以提交.

3> LeopardSkinP..:

在应用程序启动时分配合理的内存块 - 我认为Steve McConnell将此称为代码完成中的内存降落伞.

这可以用于严重出错的情况并且您需要终止.

预先分配此内存可为您提供安全网,因为您可以将其释放,然后使用可用内存执行以下操作:

保存所有持久数据

关闭所有相应的文件

将错误消息写入日志文件

向用户呈现有意义的错误



4> Diomidis Spi..:

在没有默认情况的每个switch语句中,我添加了一个使用错误消息中止程序的情况.

#define INVALID_SWITCH_VALUE 0

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


或者用一种现代风格的语言.
Assert的优点是可以在编译时全局禁用其效果.然而,在某些情况下,如果您的语言支持,则抛出可能更合适.
Assert具有另一个优势,使其对生产代码有用:当出现问题时,它会告诉您确切的失败以及错误来自程序的哪一行.像这样的错误报告很棒!
@Diomidis:另一个角度是:Assert具有**缺点**,它的效果可以在编译时全局禁用.

5> Ryan Lundy..:

当你处理枚举的各种状态时(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:,可怕的崩溃停止...直到我添加另一个帐户类型,忘了更新这里的案例.

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


如果您不处理案例块中的某些枚举,大多数编译器都足够聪明,可以发出警告.但是将默认值设置为失败仍然是一种好形式 - 枚举只是一个数字,如果内存损坏,则可能会出现无效值.
Slashene在评论中隐含了这一突破.:P
调试之后应该是生成代码的'throw new NotSupportedException()'.

6> Nik Reiman..:

使用字符串(特别是依赖于用户输入的字符串)打印出错误消息时,我总是使用单引号''.例如:

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

这种缺乏引号%s非常糟糕,因为说文件名是一个空字符串或只是空格或其他东西.打印出来的信息当然是:

ERROR: Could not open file

所以,总是做得更好:

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

然后至少用户看到这个:

ERROR: Could not open file ''

我发现这在最终用户提交的错误报告的质量方面产生了巨大的差异.如果有一个像这样看起来很滑稽的错误消息而不是通用的声音,那么他们更有可能复制/粘贴它而不只是写"它不会打开我的文件".


好的,我也见过这个问题

7> 小智..:

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和运行真实的.


我曾经一直这样做,但它导致数据库集群上的额外开销,我不得不停止.

8> dkretz..:

适用于所有语言:

将变量范围缩小到最低要求.提供的Eschew变量只是为了将它们带入下一个语句.不存在的变量是您不需要理解的变量,您不能对此负责.出于同样的原因,尽可能使用Lambdas.


避开部分究竟是什么意思?我有时会引入只能存活到下一行的变量.它们充当表达式的名称,使代码更具可读性.
是的我也不同意.对于非常复杂的表达式,使用临时变量将它们分解成两个或更短更简单的表达式通常是个好主意.它在维护中不易出错,编译器会优化临时值

9> peSHIr..:

如果有疑问,炸弹申请!

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

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


@MarkJ:你真的不懂,是吗?如果它早期爆炸(=在开发和测试期间),它应该永远不会炸弹生产.所以我真的希望他们*做*这样的程序!
或者使用基于合同的编程,比如Spec#!

10> David Grant..:

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

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

不要让任何事情逃脱你不需要的课程!


FYI,Collections.unmodifiableList返回列表的不可变*视图*,而不是不可变的*copy*.因此,如果原始列表被修改,视图也将被修改!

11> Eric Johnson..:

在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



12> CMS..:

C#:

string myString = null;

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

}

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

}


此示例仅允许您隐藏程序中的潜在危险情况.如果你不希望它为null,你希望它抛出异常,如果你期望它为null,你应该这样处理它.这是一种不好的做法.
在C#中,我总是只使用string.Equals(,).如果它们中的任何一个为空都无关紧要.

13> Binoj Antony..:

在对字符串执行任何操作之前对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)


这不是防御性的,它忽略了这个问题.应该是`if(!string.IsNullOrEmpty(myString))抛出新的ArgumentException("","myString");/*用字符串*/`做一些事情.

14> Eddie..:

在Java和C#中,为每个线程赋予有意义的名称.这包括线程池线程.它使堆栈转储更有意义.甚至为线程池线程提供一个有意义的名称需要花费更多的精力,但如果一个线程池在长时间运行的应用程序中出现问题,我可能会导致堆栈转储(你知道SendSignal.exe,对吗? ),抓住日志,而不必打断正在运行的系统,我可以告诉哪些线程......无论如何.僵局,泄漏,成长,无论问题是什么.



15> ChrisA..:

使用VB.NET,默认情况下为整个Visual Studio启用Option Explicit和Option Strict.



16> Outlaw Progr..:

使用Java,即使您关闭断言运行生产代码,使用assert关键字也很方便:

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

    return blahBlah(param);
}

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



17> Nitin Bhide..:

C++

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

然后用SAFE_DELETE(pPtr)SAFE_DELETE_ARRAY(pPtr)替换所有' delete pPtr '和' delete [] pPtr '调用

现在如果你在删除它后使用指针'pPtr',那么你将会出现'访问冲突'错误.它比随机内存损坏更容易修复.


或者使用智能指针...或者说,尽可能避免新的/删除.

18> JMS..:

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

readonly var prodSVC = new ProductService();



19> Eddie..:

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

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

通过这种方式,我得到一个堆栈跟踪,向我展示我是如何进入意外情况的,说一个永远不会解锁的锁,一个不能为null的null,依此类推.显然,如果抛出一个真正的异常,我不需要这样做.这时我需要查看生产代码中发生的事情而不会实际干扰其他任何事情.我希望抛出一个异常,我没赶上之一.我只是希望使用适当的消息记录堆栈跟踪,以便向我标记正在发生的事情.


你也可以使用新的Exception("message").printStackTrace(); 不需要抛掷或捕获,但您仍然可以在日志中获得良好的堆栈跟踪.显然,这不应该在生产代码中,但它对调试非常有用.

20> Steve Rowe..:

如果您使用的是Visual C++,请在覆盖基类的方法时使用override关键字.这样,如果有人碰巧更改了基类签名,它将抛出编译器错误,而不是静默调用错误的方法.如果它早先存在,这将节省我几次.

例:

class Foo
{
   virtual void DoSomething();
}

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



21> Eddie..:

我已经在Java中学到了几乎永远不会无限期地等待锁定解锁,除非我真的希望它可能需要无限期的长时间.如果实际上,锁定应在几秒钟内解锁,那么我将只等待一段时间.如果锁没有解锁,那么我会抱怨并将堆栈转储到日志中,并且根据系统稳定性的最佳状态,继续执行,就像锁解锁一样,或者继续,就像锁从未解锁一样.

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


在必须具有高正常运行时间的系统中,最好至少获取诊断信息,以便找到问题所在.有时您更喜欢在系统处于已知状态时退出线程或将响应返回给调用者,因此您需要等待信号量.但你不想永远挂起

22> Brian Rasmus..:

C#

在公共方法中验证引用类型参数的非空值.

sealed为类使用了很多,以避免在我不想要的地方引入依赖项.允许继承应该明确而不是偶然.



23> Joe McMahon..:

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

"权限被拒绝"告诉您存在权限问题,但您不知道问题发生的原因或位置."无法写入事务日志/我的/文件:只读文件系统"至少让你知道做出决定的基础,即使它是错的 - 特别是如果它是错的:错误的文件名?打错了?其他意外错误? - 当你遇到问题时,让你知道你在哪里.



24> Matt Briggs..:

在C#中,使用as关键字进行强制转换.

string a = (string)obj

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

string a = obj as string

如果obj不是字符串,则将as留空

您仍然需要考虑null,但这通常更直接,然后寻找强制转换异常.有时您需要"强制转换"类型行为,在这种情况下(string)obj,首选语法.

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


没得到它.对我来说似乎是一个糟糕的决定,更喜欢null.你会在运行时遇到*问题*没有提示原始原因.
这似乎与堡垒中的大型宴会厅窗户一样具有防御性.但这是一个美丽的景色!

25> Eddie..:

准备好任何输入,并获得任何意外的输入,转储到日志.(在合理范围内.如果您正在读取用户的密码,请不要将其转储到日志中!并且不要每秒将数千种这类消息记录到日志中.在记录之前有关内容,可能性和频率的原因.)

我不只是谈论用户输入验证.例如,如果您正在阅读希望包含XML的HTTP请求,请为其他数据格式做好准备.我很惊讶地看到HTML响应,我只期望XML - 直到我看到并看到我的请求是通过透明代理我不知道并且客户声称无知 - 并且代理超时试图完成请求.因此,代理向我的客户端返回了一个HTML错误页面,混淆了只需要XML数据的客户端.

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



26> raupach..:

Java的

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

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

在getter中使用防御复制.除非返回基本类型或不可变对象,否则请确保将对象复制为不违反封装.

永远不要使用克隆,使用复制构造函数.

学习equals和hashCode之间的契约.这经常被违反.问题是它在99%的情况下不会影响您的代码.人们覆盖等于,但不关心hashCode.有些实例会使您的代码破坏或行为异常,例如将可变对象用作映射中的键.



27> Zen..:

我尝试使用契约式设计方法.它可以通过任何语言模拟运行时间.每种语言都支持"断言",但是编写更好的实现可以让您轻松便捷地以更有用的方式管理错误.

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

在方法开头添加前置条件断言是确保参数一致的好方法.在方法结束时,我编写后置条件,检查输出是什么意思.

为了实现不变量,我在任何检查"类一致性"的类中编写一个方法,该类应该由前置条件和后置条件宏自动调用.

我正在评估代码合同库.



28> too much php..:

我忘了用echoPHP 写太多次了:

bar->baz(); ?>

bar->baz(); ?>

我会永远尝试找出原因 - > baz()没有返回任何东西,而实际上我并没有回应它!:-S所以我创建了一个EchoMe类,它可以包含任何应该回显的值:

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现在会引发异常...

推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有