防御编程
‘壹’ 防御式编程的相关实例
1. 在非法输入(Invalid Inputs)中保护你的程序
一个好程序,在非法输入的情况下,要么什么都不输出,要么输出错误信息。有几种方法来防止非法的输入:
(1)检查来自于外部资源(external sources)的所有数据的值,例如来源于网络的数据的值,来源于文件的数据的值。检查的目的是保证数据值在一个允许的范围内。
(2)检查每一个例程(routine)的输入参数值。
一旦非法输入被发现,那么应该根据情况进行处理。防御式编程的最佳的形式是在一开始就不引入错误。
2. 断言(Assertions)
一个断言通常是一个例程(routines)或者一个宏(marcos)。每个断言通常含有两个参数:一个布尔表示式(a boolean expression)和一个消息(a message)。一个布尔表达式的反面表示了一个错误。C 标准库提供了一个 assert 宏,它只带有一个参数,用法如下:
assert(1 == 0); // 注意 boolean expression 不要加引号
使用 assert 宏,需要包含头文件 cassert 或者assert.h,执行上面语句的结果是程序终止运行,输出与下面消息类似的消息:
Assertion failed: 1 == 0, file d:我的文档visual studio projectslearningassertassert.cpp, line 9
通常来说,我们会定义自己的 assert 宏,其目的有两个:
(1)新增参数,例如新增一个消息参数,使得 assert 宏输出更为丰富的信息。
(2)改变 assert 的行为内容。C 标准库中的 assert 宏将中断程序,实际上,我们可以让程序继续运行而不中断或者进入调试状态等,另外还可以控制消息输出的目标,即控制消息是输出到控制台还是文本文件,甚至是通过网络发出。
下面是一个 C++ 实现的断言:
#ifdef _DEBUG
#define Assert(exp, message)
{
if (!(exp))
{
std::cout << Assertion failed: << #exp <<
<< Message: << message <<
<< line: << __LINE__ <<
<< file: << __FILE__ <<
;
exit(EXIT_FAILURE);
}
}
#else
#define Assert(exp, message)
#endif
执行 Assert(1 == 0, Error); 结果为:
Assertion failed: 1 == 0
Message: Error
line: 24
file: d:我的文档visual studio projectslearningassertassert.cpp
使用断言应该注意一下的几个问题:
1)对非预期错误使用断言
断言中的布尔表达式的反面一定要描述一个非预期错误,下面所述的在一定情况下为非预期错误的一些例子:
(1)空指针。
(2)输入或者输出参数的值不在预期范围内。
(3)数组的越界。
非预期错误对应的就是预期错误,我们通常使用错误处理代码来处理预期错误,而使用断言处理非预期错误。在代码执行过程中,有些错误永远不应该发生,这样的错误是非预期错误。断言可以被看成是一种可执行的注释,你不能依赖它来让代码正常工作(《Code Complete 2》)。例如:
int nRes = f(); // nRes 由 f 函数控制, f 函数保证返回值一定在 -100 ~ 100
Assert(-100 <= nRes && nRes <= 100); // 断言,一个可执行的注释
由于 f 函数保证了返回值处于 -100 ~ 100,那么如果出现了 nRes 不在这个范围的值时,就表明一个非预期错误的出现。后面会讲到“隔栏”,那时会对断言有更加深刻的理解。
2)不要把需要执行的代码放入断言中
断言用于软件的开发和维护,而通常不在发行版本中包含断言。
需要执行的代码放入断言中是不正确的,因为在发行版本中,这些代码通常不会被执行,例如:
Assert(f()); // f 函数通常在发行版本中不会被执行
而使用如下方法则比较安全:
res = f();
Assert(res); // 安全
3)对来源于内部系统的可靠的数据使用断言,而不要对外部不可靠的数据使用断言,对于外部不可靠数据,应该使用错误处理代码。再次强调,把断言看成可执行的注释。
前条件(preconditions)和后条件(postconditions)
前条件是调用方代码在调用例程(routines)或者实例化对象之前要确保为真的条件,后条件是例程执行后或者类实例化后应满足的条件。下面是一个例子:
// 前条件,这里 nNum1 和 nNum2 的取值被前面代码所约束并保证取值在 -50 ~ 50
Assert(-50 <= nNum1 && nNum1 <= 50, Add_nNum1);
Assert(-50 <= nNum2 && nNum2 <= 50, Add_nNum2);
int nRes = add(nNum1, nNum2);
// 后条件
Assert(-100 <= nRes && nRes <= 100, Add_nRes);
注意,由于 nNum1 和 nNum2 取值范围已经被约束,因此可以使用断言,但是如果 nNum1 和 nNum2 的值来源于不可靠的外部系统,那么应该使用错误处理代码,而不是使用断言。
3. 错误处理技术
这里主要讲述如何处理预期错误。
(1)终止程序运行
有些错误非常严重,如果出现,那么最好就的做法就是让程序终止并且让用户重启程序。例如,对于显示 X 光片的绘图程序,如果数据出错,那么就关闭程序,这个时候关闭程序要远远好于显示错误的数据。
(2)继续程序运行
有时候,错误出现了,但是没有必要去关闭程序,那么就有两种处理方案:
a. 在例程中处理错误
例如让例程返回一个中立值,这是一种可行的方法,中立值在有些语言里面被描述为“类型的默认值”,例如整型的中立值为 0,指针的中立值为 NULL(或 null 等)
b. 在例程外处理错误
返回一个错误码也是可行的,返回错误码意味着,错误将交由其他程序部分来处理,而不是本例程处理。
对于出现了错误,而没有终止程序的运行,这时候,你可以在日志文件中添加一个警告信息。
抉择:正确性和健壮性
有些程序要求非常高的正确性,而有些程序要求较高的健壮性,通常两者我们只能取其一。
(1)正确性意味着结果永远是正确的,如果出错,宁愿不给出结果也不要给定一个不准确的值。
(2)健壮性意味着通过一些措施,保证软件能够正常运行下去,即使有时候会有一些不准确的值出现。
4. 隔栏(barricades)
隔栏本身就是一组错误处理代码,对于内部类只需要使用断言而无需使用错误处理代码。当断言为假时,表明了问题出在了程序中而不是数据中,需要通过修改代码来消除问题。在此,请读者联系本文开始 --- “在非法输入(Invalid Inputs)中保护你的程序”这一部分进行思考。
‘贰’ 关于单片机编写程序 将A中的二进制数变换成3为BCD码 并将百,十,个位数分别防御内部RAM的50H 51H 52H中
这程序也写的太复杂了!既然51单片机有DIV指令,并且只是把A中的数字转变为BCD,那就用DIV直接运算:
MOV A,#0FDH
MOV B,#100
DIV AB
MOV 50H,A
MOV A,B
MOB B,#10
DIV AB
MOV 51H,A
MOV 52H,B
计算完成,这样50H就是百位数,51H是十位数,52H是个位数
‘叁’ 用java编程。实现两个人对决。有血量有防御。有攻击力
几天前有人提了个类似的问题,我当时写了,但是忘记了没有回复。现在发给你参考参考
<!----攻击力=攻击+随机运气暴击---->
<!-----防御力能抵制等量的伤害----->
<!----运气值决定回血量和回血次数--->
*********lucy和jcak进入了角斗场*********
lucy初始状态:血:1000攻:290防:60运气:8
jcak初始状态:血:1200攻:200防:120运气:10
===========第1回合=============
lucy对jcak造成了362点伤害
运气事件:jcak吃了血瓶增加了30点生命值
jcak对lucy造成了250点伤害
===========第2回合=============
lucy对jcak造成了322点伤害
jcak对lucy造成了260点伤害
运气事件:lucy吃了血瓶增加了35点生命值
===========第3回合=============
lucy对jcak造成了362点伤害
运气事件:jcak吃了血瓶增加了15点生命值
jcak对lucy造成了270点伤害
运气事件:lucy吃了血瓶增加了15点生命值
===========第4回合=============
lucy对jcak造成了338点伤害
jcak对lucy造成了280点伤害
运气事件:lucy吃了血瓶增加了5点生命值
===========第5回合=============
lucy对jcak造成了298点伤害
运气事件:jcak吃了血瓶增加了45点生命值
jcak对lucy造成了260点伤害
===========第6回合=============
lucy对jcak造成了346点伤害
lucy取得了胜利
参考代码。在附件
‘肆’ 小白准备转行学习前端,有大神可以提一些建议吗
学习是以兴趣为前提的,你要对你所要学的内容产生兴趣,这样你才会花心思去学习。这和是不是小白没关系的,对于小白而言,在学习过程中就需要更努力,多花时间和心思没有什么是学不会的。
自学方法:
1、作为一个初学者,你必须明确系统的学习方案,我建议一定有一个指导的人,全靠自己学,放弃的几率非常大,在你对于web前端还没有任何概念的时候,需要一个人领进门,之后就都靠自己钻研,第一步就是确定web前端都需要哪些内容,并且在多少时间内学完,建议时间6个月保底。
2、视频为主,书为辅。很多初学者在学习前端的时候非常喜欢去买书,但是最后的结果是什么?看来看去什么都不会写,所以在这里给大家提醒,书可以看,但是是在建立于你已经对于某个知识点有了具体操作的执行后,在用书去巩固概念,这样更加利于你对于知识的理解。
3、对于学习技术来讲,掌握一个学习方法是非常重要的,其实对于学习web前端来讲,学习方法确实很多都是相通的,一旦学习方法不对,可能就会造成“方法不对,努力白费”。其实关于这方面还是很多的,我就简单说个例子,有的人边听课边跟着敲代码,这样就不对,听课的时候就专心听,做题的时候就专心做题,这都是过来人的经验,一定要听。根据每个人的不同,可能学习方法也会有所出路,找到适合你自己的学习法方法是学习的前提。
4、不建议自己一个人瞎学,在我了解学习编程的这些人来看,从零基础开始学并且最后成功做这份工作的其实并没有几个,我觉得大部分原因就是因为他们都不了解web前端是干什么的,学什么的,就盲目的买书看,到处找视频看,最后看着看着就放弃了,所以我建议初学者在没有具体概念之前,还是找有经验的人请教一下,聊过之后你就会知道web前端具体是干什么的,该怎么学,这是我个人的小建议,可以不采纳。
自学路线:
第1阶段:前端页面重构(4周)
内容包含了:(PC端网站布局项目、HTML5+CSS3基础项目、WebApp页面布局项目)
第2阶段:JavaScript高级程序设计(5周)
内容包含:(原生JavaScript交互功能开发项目、面向对象进阶与ES5/ES6应用项目、JavaScript工具库自主研发项目)
第3阶段:PC端全栈项目开发(3周)
内容包含:(jQuery经典交互特效开发、HTTP协议、Ajax进阶与PHP/JAVA开发项目、前端工程化与模块化应用项目、PC端网站开发项目、PC端管理信息系统前端开发项目)
第4阶段:移动端项目开发(6周)
内容包含:(Touch端项目、微信场景项目、应用Angular+Ionic开发WebApp项目、应用Vue.js开发WebApp项目、应用React.js开发WebApp项目)
第5阶段:混合(Hybrid,ReactNative)开发(1周)
内容包含:(微信小程序开发、ReactNative、各类混合应用开发)
第6阶段:NodeJS全栈开发(1周)
内容包括:(WebApp后端系统开发、一、NodeJS基础与NodeJS核心模块二、Express三、noSQL数据库)
视频教程:
网页链接
网页链接
如果你对于学习前端有任何不懂的可以随时来问我,如果没有比较好的教程,也可以问我要。
‘伍’ pthon 中获取数据库中的值用于断言中时报错,该怎么解决,具体信息如下:
这个问题是如何在一些场景下使用断言表达式,通常会有人误用它,所以我决定写一篇文章来说明何时使用断言,什么时候不用。
为那些还不清楚它的人,Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息。例如:
py> x = 23
py> assert x > 0, "x is not zero or negative"
py> assert x%2 == 0, "x is not an even number"
Traceback (most recent call last):
File "", line 1, in
AssertionError: x is not an even number
很多人用assert作为一个很快和容易的方法来在参数错误的时候抛出异常。但这样做是错的,非常错误,有两个原因。首先AssertError不是在测试参数时应该抛出的错误。你不应该像这样写代码:
if not isinstance(x, int):
raise AssertionError("not an int")
你应该抛出TypeError的错误,assert会抛出错误的异常。
但是,更危险的是,有一个关于assert的困扰:它可以被编译好然后从来不执行,如果你用 –O 或 –oo
选项运行Python,结果不保证assert表达式会运行到。当适当的使用assert时,这是未来,但是当assert不恰当的使用时,它会让代码用
-O执行时出错。
那什么时候应该使用assert?没有特定的规则,断言应该用于:
防御型的编程
运行时检查程序逻辑
检查约定
程序常量
检查文档
(在测试代码的时候使用断言也是可接受的,是一种很方便的单元测试方法,你接受这些测试在用-O标志运行时不会做任何事。我有时在代码里使用
assert False来标记没有写完的代码分支,我希望这些代码运行失败。尽管抛出NotImplementedError可能会更好。)
关于断言的意见有很多,因为它能确保代码的正确性。如果你确定代码是正确的,那么就没有用断言的必要了,因为他们从来不会运行失败,你可以直接移除这些断言。如果你确定检查会失败,那么如果你不用断言,代码就会通过编译并忽略你的检查。
在以上两种情况下会很有意思,当你比较肯定代码但是不是绝对肯定时。可能你会错过一些非常古怪的情况。在这个情况下,额外的运行时检查能帮你确保任何错误都会尽早地被捕捉到。
另一个好的使用断言的方式是检查程序的不变量。一个不变量是一些你需要依赖它为真的情况,除非一个bug导致它为假。如果有bug,最好能够尽早发现,所以我们为它进行一个测试,但是又不想减慢代码运行速度。所以就用断言,因为它能在开发时打开,在产品阶段关闭。
一个非变量的例子可能是,如果你的函数希望在它开始时有数据库的连接,并且承诺在它返回的时候仍然保持连接,这就是函数的不变量:
Python
def some_function(arg):
assert not DB.closed()
... # code goes here
assert not DB.closed()
return result
断言本身就是很好的注释,胜过你直接写注释:
# when we reach here, we know that n > 2
你可以通过添加断言来确保它:
assert n > 2
断言也是一种防御型编程。你不是让你的代码防御现在的错误,而是防止在代码修改后引发的错误。理想情况下,单元测试可以完成这样的工作,可是需要面
对的现实是,它们通常是没有完成的。人们可能在提交代码前会忘了运行测试代码。有一个内部检查是另一个阻挡错误的防线,尤其是那些不明显的错误,却导致了
代码出问题并且返回错误的结果。
加入你有一些if…elif 的语句块,你知道在这之前一些需要有一些值:
# target is expected to be one of x, y, or z, and nothing else.
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
run_z_code()
假设代码现在是完全正确的。但它会一直是正确的吗?依赖的修改,代码的修改。如果依赖修改成 target = w
会发生什么,会关系到run_w_code函数吗?如果我们改变了代码,但没有修改这里的代码,可能会导致错误的调用 run_z_code
函数并引发错误。用防御型的方法来写代码会很好,它能让代码运行正确,或者立马执行错误,即使你在未来对它进行了修改。
在代码开头的注释很好的一步,但是人们经常懒得读或者更新注释。一旦发生这种情况,注释会变得没用。但有了断言,我可以同时对代码块的假设书写文档,并且在它们违反的时候触发一个干净的错误
assert target in (x, y, z)
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
assert target == z
run_z_code()
这样,断言是一种防御型编程,同时也是一种文档。我想到一个更好的方案:
if target == x:
run_x_code()
elif target == y:
run_y_code()
elif target == z:
run_z_code()
else:
# This can never happen. But just in case it does...
raise RuntimeError("an unexpected error occurred")
按约定进行设计是断言的另一个好的用途。我们想象函数与调用者之间有个约定,比如下面的:
“如果你传给我一个非空字符串,我保证传会字符串的第一个字母并将其大写。”
如果约定被函数或调用这破坏,代码就会出问题。我们说函数有一些前置条件和后置条件,所以函数就会这么写:
def first_upper(astring):
assert isinstance(astring, str) and len(astring) > 0
result = astring[0].upper()
assert isinstance(result, str) and len(result) == 1
assert result == result.upper()
return result
按约定设计的目标是为了正确的编程,前置条件和后置条件是需要保持的。这是断言的典型应用场景,因为一旦我们发布了没有问题的代码到产品中,程序会是正确的,并且我们能安全的移除检查。
下面是建议的不要用断言的场景:
不要用它测试用户提供的数据
不要用断言来检查你觉得在你的程序的常规使用时会出错的地方。断言是用来检查非常罕见的问题。你的用户不应该看到任何断言错误,如果他们看到了,这是一个bug,修复它。
有的情况下,不用断言是因为它比精确的检查要短,它不应该是懒码农的偷懒方式。
不要用它来检查对公共库的输入参数,因为它不能控制调用者,所以不能保证调用者会不会打破双方的约定。
不要为你觉得可以恢复的错误用断言。换句话说,不用改在产品代码里捕捉到断言错误。
不要用太多断言以至于让代码很晦涩。
‘陆’ 限流器有什么用限流器的原理是什么
限流器是一种限制某种操作在一定时间内的执行次数(例如每秒钟5次)或者执行量(例如每秒钟1G大小的数据)的机制。
限流器是一种防御性的编程实现方式,在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。
限流器优点
动作速度快,反应时间小于20ms(在一个电力周波内),可在电力系统故障时自动触发;能将短路电流减少一半以上。
故障线路被断路器开断后,能快速自动复位并在几秒之内多次动作,以配合重合闸,正常运行时,功耗应接近于零,最大不能高于输送功率的0.25%,可靠性应高于与其同时运行的断路器。