防御式编程
保护程序免遭非法输入数据的破坏
垃圾进,什么都不出: 三种方法处理进来的垃圾情况
- 检查所有来源于外部的数据的值,防止能攻击你系统的数据(sql注入命令,html注入,整数溢出,传给系统调用的数据)
- 检查子程序所有输入参数的值
- 决定如何处理错误的输入数据
断言
断言在程序运行错误时强制退出程序,有人认为开发阶段为了检测到所有bug,就应该将打上断言, 越能让你犯错,程序上线后的bug越少
错误处理技术
- 返回中立值:初始化一个值,然后程序运行失败时也能有返回的值
- 换用下一个正确的数据: 在处理数据流的时候,有时只需返回下一个正确的数据即可,为了处理完所有的数据
- 返回与前次相同的数据
- 把警告信息记录到日志文件中
- 返回一个错误码
- 调用错误处理子程序或对象,把错误处理集中在一个全局的错误处理的子程序或对象中
- 当错误发生时,显示出错消息,把错误处理的开销减到最少
- 用最妥当的方式在局部处理错误
- 关闭程序:一检测错误就关闭程序
- 健壮性与正确性:
- 人身安全有关的软件,显示正确性而非健壮性,不返回结果,停止程序 胜于 返回结果
- 消费类的软件 注重健壮性,返回一些结果比软件停止有用
- 高层次设计多错误处理方式的影响
异常
- 提供一种无法被忽略的错误通知机制,其他错误处理机制有可能会导致错误在不知不觉中向外扩散,而异常则消除了这种可能性
- 只在真正例外的情况下抛出异常,异常应用来处理 那些不仅仅罕见,而且永远不该发生的情况
- 不要用异常来推卸责任: 如果某种错误的情况可以在局部处理,就应该在局部处理
- 避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获 :
- 在恰当的抽象层次抛出异常: 否则会暴露内部的实现细节
- 在异常消息中加入关于导入异常发生的全部信息:如果异常是因为一个数组下标错误而抛出的,就应该在异常消息中包含数组的上界,下界以及非法的下标值等信息.
- 避免使用空的catch 语句: 在catch 里没有任何的处理,这就意味着,要么try的代码不对,无故抛出一个异常,要么是catch 里的代码不对,不能有效处理一个异常
- 了解你的所用的函数库可能会抛出的异常
- 考虑创建一个集中的异常报告机制
- 包项目的异常的使用 标准化:为了保持对异常处理尽可能的便于管理
- 考虑是否真的需要处理异常
隔离程序:使之包容由错误造成的损害
划分层次
UI和 外部文件 <==> 数据 <==> 内部类
在 不同层之间 可以设置一个交换层,用来处理脏数据的保护膜
辅助调试的代码
- 不要自动的把产品版的限制 强加的开发版之上, 就是意味着 ,可以在开发版加一些可能会轻微损耗资源的操作,来方便调试
- 尽早引入辅助调试的代码
- 采用进攻式编程 : 在开发阶段,显现出来所有 可能会出现,实际中不能出现的控制语句:例如,case if 中加入 默认的 语句 else default
- 确保断言语句使程序终止运行,让问题引起的麻烦越大越好,这样修复的可能性大
- 完全填充分配到所哟的内存,这样可以检测出内存分配的错误
- 完全填充已分配到所有的文件或流,这样排查文件格式错误
- 保证case 语句中有default ,if 后面有 else
开发时惨痛的失败,会让你在发布产品时不会败得太惨
- 计划移除调试辅助的代码: 编写自己的预处理器, 来处理(移除) //# BEGIN DEBUG 和 //# END DEBUG 之间的代码*
确定在产品代码中该保留多少的防御式代码
- 保留那些检查重要错误的代码
- 去除检查细微错误的代码
- 去掉可以导致程序硬性崩溃的代码,比如断言
- 保留让程序稳妥崩溃的代码
- 为你的技术支持人员记录错误信息
- 确认留在代码中错误消息是友好的
对防御式编程采取防御姿态
- 过度的防御性会导致程序臃肿, 运行缓慢
- 考虑什么地方需要进行防御,然后调整防御式编程的优先级别
对数据处理
浮点数
避免数量级相差巨大的数之间的加减运算
避免等量判断: 应该用相减,得到的数字和允许出现溢出的范围内的小数进行比较.
处理舍入误差的问题
检查语言和函数库对特定数据类型的支持枚举类型
枚举类型提高可靠性
可将枚举类型作为布尔值的替换方案
检查非法数值: if 语句 或者 case 语句中 使用枚举作为判断条件的值数组:
确认所有数组小标没有超出数组边界
考虑容器来取代数组,或者将数组作为顺序化接口来处理:当你习惯性的选用数组之前,考虑能否用其他可以顺序访问数据的容器类作为代替方案-例如:集合,栈,队列等
检查数组的边界点
如果数组是多维的,确认下标是否要使用更有意义的名字:使用 i 和 j 这样不清晰的下标,可能会带来误导
地方下标串话
异常
- 【强制】不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,
- 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低
- 【强制】对大段代码进行 try-catch,这是不负责任的表现。 catch 时请分清稳定代码和非稳
定代码,稳定代码指的是无论如何不会出错的代码。 - 捕获所有的异常 :
- 【强制】有 try 块放到了事务代码中, catch 异常后,如果需要回滚事务,一定要注意手动回
滚事务 - jdk 1.7 之前,finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- jdk 1.7 开始 ,使用 try-with-resources 方式。
- 考虑到会出现 NPE(not point exception) ,所以使用集合时,如果是null 的, 请返回空的集合
防止 NPE 出现的情况:
- ”string”.equals(null)
- 返回int 值时可能会有空的情况 public int f(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE。
- 数据库的查询结果可能为 null。
- 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null
- 远程调用返回对象,一律要求进行 NPE 判断。
- 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
- 级联调用 obj.getA().getB().getC(); 一连串调用,易产生 NPE。
错误码的设计:公司外的 http/api 开放接口必须使用“错误码”;
- 用异常代替错误码,这条和上一条相反,这是为了方便测试
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛
- 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
其他
final 可提高程序响应效率,声明成 final 的情况:
- 1) 不需要重新赋值的变量,包括类属性、局部变量。
- 2) 对象参数前加 final,表示不允许修改引用的指向。
- 3) 类方法确定不允许被重写。
Math.random()
这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常) ,如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。
获取当前毫秒数 System.currentTimeMillis();
任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
- 对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。