C#探索之路(6):浅学C#契约式编程、防御式编程
C#探索之路6:浅学C#契约式编程、防御式编程
契约式编程、防御式编程
背景:
契约式编程是一门编程方法论,但是相对于C/C++而言,契约式编程通过大量的宏、条件编译,代码上看来就显得不那么简洁了,但契约式编程对于C#而言,.NET 4.0 对宏和条件编译进行抽象封装。封装了相关的类库来是的代码更为清晰、简洁,使开发人员的思维更加清晰。
一、契约式编程
概念:
契约式编程它一般由 Precondition(前置条件), Postcondition(后置条件) 和 Invariant(不变量) 等概念组成。.NET 4.0 除上述概念之外,还增加了 Assert(断言),Assume(假设) 概念。
契约的思想不难理解,它只是一组结果为真的表达式。如若不然,契约就被违反。那按照定义,程序中就存在纰漏。
Ⅰ方式:
1. Assert
Assert(断言)是最基本的契约。.NET 4.0 使用 Contract.Assert() 方法来特指断言。它用来表示程序点必须保持的一个契约。
Contract.Assert(this.privateField > 0); Contract.Assert(this.x == 3, "Why isn’t the value of x 3?");
断言有两个重载方法,首参数都是一个布尔表达式,第二个方法的第二个参数表示违反契约时的异常信息。
当断言运行时失败,.NET CLR 仅仅调用 Debug.Assert 方法。成功时则什么也不做。
2、Assume
.NET 4.0 使用 Contract.Assume() 方法表示 Assume(假设) 契约。
Contract.Assume(this.privateField > 0); Contract.Assume(this.x == 3, "Static checker assumed this");
Assume 契约在运行时检测的行为与 Assert(断言) 契约完全一致。但对于静态验证来说,Assume 契约仅仅验证已添加的事实。由于诸多限制,静态验证并不能保证该契约。或许最好先使用 Assert 契约,然后在验证代码时按需修改。
当 Assume 契约运行时失败时, .NET CLR 会调用 Debug.Assert(false)。同样,成功时什么也不做。
3、Preconditions(前置条件)
Postconditions 契约表示方法终止时的状态。它跟 Preconditions 契约的运行时行为完全一致。但与 Preconditions 契约不同,Postconditions 契约相关的成员有着更少的可见性。
//eg1 Contract.Requires(x != null); //eg2 Contract.RequiresAlways(x != null); //eg3 if (x == null) throw new ArgumentException("The argument can not be null."); Contract.EndContractBlock(); // 前面所有的 if 检测语句皆是 Preconditions 契约
包含诸多关于契约的使用方式和条件,可以参考如下链接:
https://developer.aliyun.com/article/301484
二、防御式编程
概念:
防御式编程的这种思想字如其名,就是通过正确的使用代码来建立一种根据外界情况防御影响到功能代码的这么一种思想方法论;
Ⅰ主要思想和要点:
1、保护程序免遭非法输入数据的破坏
-
概念
核心要义:
子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。
通常有三种方法处理错误数据的情况:
①检查所有来源于外部的数据的值。 ②检查子程序所有输入参数的值 ③决定如何处理错误的输入数据。
-
应用
这点在我们常见的项目开发中就有用到,例如常见的商城系统,聊天系统这类带有输入框的内容,我们购买物品时,我们常见的有inputfield 输入框,而好的编程思维是应该只允许正确的输入结果通过,而非法的输入结果要么就被排除在外,要么就通过代码的例如正则表达式去实现筛选掉非法字符,常见的屏蔽字等等…
2、断言
-
概念:
断言(assertion)是指在开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或宏)。
断言为真,则表明程序运行正常,而断言为假,则意味着它已经在代码中发现了意料之外的错误。则进行错误的处理;
-
应用
对于数据传递,我们可以根据对数据进行断言,是否为空,是否为我们想要的类型等;当且仅当为我们想要的数据类型的时候,我们才进行后面的处理,否则执行其他操作或者报错处理;
3、错误处理技术
-
概念
断言可以用于处理代码中不应发生的错误。那么又该如何处理那些预料中可
能要发生的错误呢?根据所处情形的不同,可以选择不同的方案:①返回中立值(neutral value)
②换用下一个正确数据
③返回与前次相同的值
④换用最接近的有效值
⑤在曰志文件中记录警告信息\\返回一个错误码
⑥调用错误处理子程序或对象
⑦显示出错信息或者关闭程序一或把这些技术结合起来使用
既然有这么多的选择,你就必须注意,应该在整个程序里采用一致的方式处理非法的参数。对错误进行处理的方式会直接关系到软件能否满足在正确性、健壮性和其他非功能性指标方面的要求。确定一种通用的处理错误参数的方法,是架构层次(或称髙层次)的设计决策,需要在那里的某个层次上解决。一旦确定了某种方法,就要确保始终如一地贯彻这一方法。 -
应用
导表工具中运行时,当检测到表格为空时,或者数据出错时,为了方便去表现出其中的问题,我们通常会通过报错暂停程序的方式去做处理,并且抛出缺失的表格或者错误的原因;
协议解析类里面也又涉及到,我们对协议解析的时候,服务端会记录一个错误码,项目中是0、1来区分,给定研发这边就是根据服务端返回的一个协议请求结果的状态来做处理;
4、异常
-
概念
异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。 其他的错误处理机制有 可能会导致错误在不知不觉中向外扩散,而异常则消除了这种可能性。
-
应用
通常我们会通过try、catch来对异常程序片段进行处理,当代码执行遇到错误时,可以明确知道代码抛出异常的位置,更便于研发人员发现问题后对产生问题的地方进行维护;
5、隔离程序,使之包容包容由错误造成的损害
-
概念
以防御式编程为目的而进行隔离的一种方法,是把某些接口选定为“安全”区域的边界。对穿越安全区域边界的数据进行合法性校验,并当数据非法时做出敏锐的反应。也同样可以在类的层次采用这种方法。类的公用方法可以假设数据是不安全的,它们要负责检查数据并进行清理。一旦类的公用方法接受了数据,那么类的私用方法就可以假定数据都是安全的了
6、对防御式编程采取防御的姿态
-
概念
过度的防御式编程也会引起问题。更糟糕的是,防御式编程引入的额外代码增加了软件的复杂度。因此,要考虑好什么地方你需要进行防御,然后因地制宜地调整你进行防御式编程的优先级。
我们应该尽可能考虑可能发生的情况,而不应该把完全不可能的情况也列为防卫的情况之一;