防御式编程

Published on:

防御式编程应该要检查数据的正确性,以及在出现错误数据时应该如何处理(避免程序崩溃,提示用户,让程序正确的崩溃,处理异常等)。 其核心思想是:子程序不应该因传入错误的数据而被破坏,要承认程序都会有问题,都需要被修改。

保护程序免遭非法输入数据的破坏

  • 检查所有来源于外部的数据

    当从文件,用户,网络或其它外部中获取数据时,要检查所有的数据值,以确保值在合法的范围内。

  • 检查输入的参数

  • 决定如何处理错误的数据

断言

断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。通过使用断言,程序员能更快排查出因修改代码或者别的原因而导致的错误。

  • 输入和输出参数处于合理的范围
  • 子程序开始(结束)执行时,文件处于打开(关闭)状态
  • 文件或流已用只读‘只写或可读写的方式打开
  • 仅用于输入的变量没有被子程序所修改
  • 指针非空
  • 传入子程序的数组或其它窗口至少能容纳X个数据元素
  • 表已初始化,存储着真实的数值

断言只是用于开发和维护阶段。

  • 用错误处理代码来处理预期会发生的情况,断言来处理绝不应该发生的情况。
  • 避免把要执行的代码放到断言中
  • 用断言来注解前条件和后条件
  • 对于高健壮性的代码,应该先使用断言再处理错误

错误处理技术

当程序中出现错误数据时,可选的处理方式有如下几种

  • 返回中立值
  • 换用下一个正确的数据
  • 返回与前次相同的数据
  • 换用最近的合法值
  • 把警告信息记录到日志文件中
  • 返回一个错误码
  • 调用错误处理子程序
  • 当错误发生时显示错误信息
  • 用最妥当的方式在局部处理错误
  • 关闭程序

异常

异常是把代码的错误和异常事件传递给调用方的一种特殊手段。 异常和继承一样要审慎明智的使用。

  • 用异常通知程序的其余部分,发生了不可忽略的错误
  • 不能用异常来推卸责任
  • 避免在构造和析构函数中抛出异常,除非你在同一个地方捕获它们
  • 在恰当的抽象层次抛出异常
1
2
3
4
5
6
7
class Employee
{
  public TaxID GetTaxID() throw EOFException()
  {
      ...
  }
}

这个EOFException就和GetTexID的抽象层次不一致,暴露了内部是用读取文件的方式实现的。然而在这个抽象层次,调用方是不关心也不依赖于你是用什么方式实现的。所以正确的做法是再抽象一个Exception。

1
2
3
4
5
6
7
class Employee
{
  public TaxID GetTaxID() throw EmployeeDataNotAvailable()
  {
      ...
  }
}
  • 在异常消息中加入导致异常的全部消息
  • 避免使用空的catch语句
  • 了解所用函数库可能抛出的异常
  • 考虑创建一个集中的异常报告机制。 可以自己定制一个异常的机类,里面实现了如何记录异常的方法
  • 把项目中对异常的使用标准化
  • 考虑异常的替换方案
  • 使用防火墙的隔离技术,在某一层次上统一做数据的检查(数据验证层),下面的层次就可以不用做检查了。

    例如在类的public接口统一对传入的参数进行检测,private就不做这些检测了

  • 尽早引入辅助调试的代码和工具

  • 计划移除辅助调试代码
    • 使用类似ant和make这样的工具
    • 使用内置的预处理器 如DEBUG等
    • 使用自己编写的预处理器
    • 使用调试存根

采用进攻式编程

异常情况应该尽早的在开发阶段让它显现出来。

  • 确保断言语句使程序终止运行
  • 完全填充分配到的所有内存
  • 完全填充已分配到的文件或流
  • 确保每一个case语句中的default分支或else分支都能产生严重的错误。

高质量的子程序

Published on:

创建子程序不仅仅是为了重用和更容易调试。下面给出更多合理的理由。

创建子程序的正当理由

  • 降低复杂度,隐藏细节。
  • 引入中间,易懂的抽象。 把一段代码放进一个命名恰当的子程序中,这是对代码的最好说明,而不是用注释。
  • 避免重复代码
  • 支持子类化,如果是类的方法,那么可以在必要时可以用派生类重写这个方法
  • 隐藏顺序 把事件处理的顺序隐藏起来
  • 提高可移植性, 把一切非标准的API和操作系统等底层相关的API用一个子程序封装起来,可以提高可移植性。
  • 简化复杂的布尔判断 把布尔判断放进子程序中
  • 改善性能 通过使用子程序,你可以只在一个地方优化代码。
  • 隔离复杂度
  • 限制变化所带来的影响
  • 形成中央控制点

在子程序上的设计

功能上的的内聚性,这个是最好的内聚性,这意味着这个子程序只完成一项功能。如sin, CloseFile,GetCustomerName

不理想的内聚性

  • 顺序上的内聚性 指在子程序内包含有需要按特定顺序执行的操作,这些步骤需要共享数据,而且只在全部执行完成后才完成一项特定的功能
  • 通信上的内聚性 指子程序只是共享了数据,代码之间不存在任何联系
  • 临时的内聚性 指一些需要同时执行才放到一起操作的子程序。 如 StartUp。 解决这个问题是让这个子程序的代码分成多个子程序,去调用其它的多个子程序

不可取的内聚性

  • 过程上的内聚性 指一个子程序按特定的顺序执行。 解决方式:可以把不同的操作放进各自的子程序中
  • 逻辑上的内聚性 指若干个操作放入同一个子程序,通过传入一个控制标志来执行不同的操作。 如InputAll 根据控制标志来判断输入的是姓名,不是用户ID。如果这些子程序共享数据可以考虑包裹到类中
  • 巧合的内聚性
阅读全文 →

要抓住事物的本质才能应变

Published on:
Tag: Thinking

在许多事物中都存在着共性,如果没有掌握这种共性。每次来一样新的事物都要从头学起很浪费时间。这种不求甚解,靠死记的方式,在短时间内可以立即看到效果,但长时间来看是效率低下的。我们应该学会深入挖掘事物的本质,抽象出它们的共性,学会这样的方法。这样新的事物或技术出现,我们就立马可以与旧的事物联系起来,而且一旦建立起联系之后我们对新事物的记忆和理解也会更加深刻。所以那些一带而过的学习方式,这种看起来是一下子扩宽了知识面,其实是非常肤浅的,而且没有效率的。

  • 实验证明钻研式的学习更有效率,虽然难度大些,但这是非常值得。只有这样才能掌握以不变应万变的能力。
  • 要用平常心去面对困难的事物,不要浮躁,不要轻易放弃。
  • T型人才应该从那一竖开始。

JNI本地多线程FindClass错误

Published on:
Tag: JNI

今天在用JNI开发Android程序时,遇到一个奇怪的情况。我在本地C++层中用pthread_create创建一个线程,在回调函数中要使用Java层的类,使用FindClass来获取Java的类,但是返回的却是NULL。但在主线程中却是可以用的。后来在Google的Android Developer的JNI Tips文章中找到了相关信息。

If the class name looks right, you could be running into a class loader issue. FindClass wants to start the class search in the class loader associated with your code. It examines the call stack, which will look something like:

Foo.myfunc(Native Method)
Foo.main(Foo.java:10)
dalvik.system.NativeStart.main(Native Method)

The topmost method is Foo.myfunc. FindClass finds the ClassLoader object associated with the Foo class and uses that.

This usually does what you want. You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now the stack trace looks like this:

dalvik.system.NativeStart.run(Native Method)

The topmost method is NativeStart.run, which isn’t part of your application. If you call FindClass from this thread, the JavaVM will start in the “system” class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.

There are a few ways to work around this:

  • Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.
  • Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.
  • Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

通过Java层调用JNI的接口,JNI接口参数都会包含一个class的信息,从这个class中可以获取等到相应的ClassLoader。但是如果我自己创建了一个本地的线程来调用了FindClass,却找不到相应的ClassLoader,而使用了“系统”的ClassLoader,而不是你应用程序的ClassLoader,会找不到和这个app相关的类。 解决的方式有如下三种方式:

  • JNI_OnLoad方法中就去调用FindClass找到相应的jclass并存起来。
1
2
static jclass yourClass;
jclass yourClass = env->FindClass("yourClassName");
  • 在你的本地方法中加一个类的参数,调用这个方法时传一个类的实例进去。比如Foo.class作为参数。
  • 在某个地方缓存ClassLoader对象,然后自己通过这个对象调用loadClass方法。 这三个方法中,我觉得第三个方法对我目前的项目最适用吧,只须要把env->FindClass替换成一个方法,方法里面会先调用env->FindClass,如果返回为NULL,我再调用ClassLoader的loadClass方法去加载类。 我在StackOverFlow找到了相应的实现
阅读全文 →

类的设计

Published on:

抽象数据类型

抽象数据类型可以让你像在现实世界中一样操作实体,而不用关心其底层实现。 ADT的益处

  • 隐藏实现细节
  • 限制改动程序影响的范围
  • 让接口提供更多的信息
  • 让程序的正确性更显而易见,易于检查错误
  • 程序更具自我说明性
  • 无须在程序内部到处传递数据
  • 像现实世界中那样操作实体,不用在底层操作它

注意事项 不要让ADT依赖于存储介质。在命名上须要注意。如操作税率表的RateFile类不如RateTable类的命名方式好,虽然目前是通过文件的方式实现的。

类优秀的设计

  1. 类的接口应该展现一致的抽象层次
  2. 一定要理解类所实现的抽象是什么
  3. 提供成对的服务 (如打开,关闭)
  4. 把不相关的信息移到其它类中
  5. 尽可能让接口可编程,而不是表达语义
  6. 谨防在修改时破坏类的抽象
  7. 不要添加与接口抽象不一致的公用成员
  8. 同时考虑类的抽象性和内聚性
阅读全文 →