1. ********断言********
_ASSERT主要用在同一个开发小组内部编程中,它用来限定开发者在调用别人的
函数时,要遵守被调用函数的前条件、后条件、不变式。可见,断言的使用是为了
缩小调用范围,给同一个小组的开发人员以一个友好的提示。
2. ********错误的分类与处理方法********
(1)操作员与人机交互错误(不满足规则的操作,是可恢复的)
程序检验、提示规则
(2)运行时错误(网络、文件系统、数据库、其它业务引用系统)
记录并抛出异常,或返回错误码
(3)程序员错误与客户模块交互
使用断言
3. ********异常的处理方式********
(1)同步调用
1)对对有能力处理的异常,捕获并处理之
2)对原始信息过于技术化的异常,捕获并包装之,重新抛出
3)在调用的中间层,对未知异常保持沉默
4)在调用的最高层,必须捕获所有异常,避免本身进程或宿主进程崩溃
(2)异步调用
1)异步调用一般不应有任何返回值
2)服务方最好以同样的方式返回正常信息和错误信息,即正常信息是通过通知的
方式返回的话,错误信息也应该通过通知的方式返回;正常信息是通过主动查
询得到的话,错误信息也应该通过主动查询得到。
4. ********异常处理规范********
*异常应该是分层的*
(1)异常定义
1)每个模块应该有自己的应用程序异常类型层次,从本模块主动抛出的应用程序异常
都应该属于该异常类型层次,客户代码可以只捕捉该层次的基类(?)
2)应用程序的所有自定义异常都应该从开发平台提供的“应用程序异常基类”派生
3)中间件等平台程序的运行时异常都应该从开发平台提供的“运行时异常基类”派生(?)
4)中间件等平台程序的运行时错误都应该从开发平台提供的“错误基类”派生(?)
(2)异常捕获
1)对有能力处理的异常,捕获并处理之
2)在调用的中间层,对未知异常保持沉默
3)在调用的最高层,必须捕获所有异常,避免本身进程或宿主进程崩溃
4)记录捕获到的每个原始异常的信息
(3)异常抛出
1)每个模块应使用本模块所能得知的最精确的错误原因报告异常信息
2)如果有原始异常,在重新抛出的自定义异常中附加原始异常的信息
5. ********几点说明********
(1)错误处理与日志系统
1)错误处理不等同于日志系统,日志系统只是错误信息的一种记录手段
2)错误信息的输出应全部调用日志系统来完成
(2)程序员错误与运行时错误
1)接口函数的前置条件,应该是一种规范,是客户程序员必须遵守的约定;客户程序员违反了
约定,程序将产生异常或不可预知的错误;对于这类约定,不应该需要有运行时的代码检查;
比如如果你的接口函数的一个参数不能为null,而你在函数开始部分程序里写:
if(xxx == null)
throw new someException();
你的代码实际上允许该参数为null,因为你对null的情况进行了运行时处理;尽管你在文档里
声明该参数不能为null,但如果客户程序员遵守了约定,那么你这段检查代码就是冗余的。
2)当你对参数没有任何检查就进行了使用,而非法的参数值导致了错误,此时有两种情况:
1...如果你在接口函数说明里列出了参数不允许的非法取值,那么客户程序员应该修改程序避免传入
非法值,该类错误称为程序员错误;
2...如果你没有说明,那么你应该修改函数,处理非法参数。
3)不是所有的限制条件在文档里说明一下,就可以把责任扔给客户程序员了,有一些错误必须处理:
特别是客户程序员不需要import你的package就可以和你的接口交互的情况,如socket服务器 ,
你必须在socket服务程序内部检查所有接收到的数据,拒绝错误的请求,否则极易遭到攻击 。
(3)错误代码与异常
1)不应该使用bool值来返回成功与否,bool返回值只应用在真正查询bool状态的操作中,
如 bool IsDirty(),bool hasNext()等。
2)整形或bool型的错误代码返回值强迫客户程序员检查每一次调用,应用异常取代之 。
6. ********一个函数调用多个子函数时,发生错误的理解********
(1)当后续的子函数发生错误时,前面执行的函数又改变了类的状态,在返回错误码之前,需要回滚状态吗?
答案:不需要回滚。只需要考虑、确定“如果主函数失败,总体上应该怎样处理?”,譬如,这个错误是可以
恢复的,或者不可恢复直接delete这个对象,那么只需要在“后续的某个步骤上处理掉回滚”即可。
(2)当涉及分配资源时,如果失败怎么办?
(3)返回值需要封装吗?
答案:根据功能分解的要求,每个小函数都是一个功能单元。当一个大函数调用这些小函数时,可能发生多个
分配资源失败,如new, createobject等,这些失败的返回值可以统一成“分配失败错误”;对多个不了
解细节的错误,返回值可统一成“ERROR_UNKNOWN”;对于具体技术细节的错误,可以把返回值“原样
返回”;
当这个大函数被调用时,就可以根据“这些经过转换、包装、统一后的返回值”,进行不同方式的处理,如:
继续往上层返,走同一层次的其它路径,发异常,弹对话框等。
****************************************************************************************************************
* 对此问题产生迷惑的真正原因是:没有真正理解“Decompose”,须知“功能分解”就是功能的封装,*
* 对于“封装”而言,它本身就是一个“实体”,它会接受什么“输入”?返回几种“输出”?在封装 *
* 之前,就应该是确定下来的了!所以要充分理解这个“功能实体”的含义! *
****************************************************************************************************************
(4)函数调用分层时,各层都要打log吗
答案:太小的函数或类(功能单元)不需要打log,大一点的要打log,log可以层层往上打。同样返回值也可以
层层往上返,但要注意适当的“转换、包装、统一”。 总之,要充分理解“功能分解”。
7. ********返回值封装与层层打log的再沉思心得********
(1)每一个类都可以安排一个enum集,表示该类所有需要返回的状态码。这样,如果用多个这样的“小类”组合
成一个“大类”时,“小类”的成员函数会被调用,需要判断它返回的“状态码”。这时,根据“大类”
功能分解,就会需要对“小类的返回状态码”进行语义上的再次封装。如果想把“小类的返回状态码”完整
的体现出来,则需要一个“小类的返回状态码”和“大类的返回状态码”的一一对应“映射表”。如果这样
做,对“大类”而言,就是相当于“简单的组合”了“所有小类的返回状态码”。很徒劳!问题是,“大类”
需要用到“小类”所有的返回码吗?
这要视“大类”的“功能设计”而定。如果“大类”一一判断“小类”的返回值,并作出可以弥补的修改,
则返回值的“一一映射”就不必要了,需要更高层次的语义转换。
(2)对开发者而言,所有自己编写模块的返回值,可以共用一个enum集。这样,即使函数调用涉及返回值,也只
需要直接返回即可。既然大小模块都是同一个人(team)开发,而且“大模块”又包含了“小模块”,那么
就是说,可以把“大模块”看作一个“整体”,共用一个enum集当然理所当然!!其实哪里有所谓的“小模
块”呢?它仅仅就是“大模块”的一部分嘛!!!
(3)层层打log的问题。
1、一般地,非外部接口函数,没必要打;
2、不出现错误、状态转换、大的外部数据交换,没必要打;
3、关于同一个问题(错误、状态变换)的log,在这个模块内,没必要打多次,只打一次即可;
4、对于外部调用打出的log,如果和模块内的log重复,则没办法、也没必要干涉。