7.2.1 精心地构造、划分子模块,并按“接口”部分及“内核”部分合理地组织子模块,以提高“内核”部分的可移植性和可重用性。

说明:对不同产品中的某个功能相同的模块,若能做到其内核部分完全或基本一致,那么无论对产品的测试、维护,还是对以后产品的升级都会有很大帮助。

7.2.2 精心构造算法,并对其性能、效率进行测试。

7.2.3 时刻注意表达式是否会上溢、下溢。

示例:如下程序将造成变量下溢。

unsigned char size ;
while (size-- >= 0) {// 将出现下溢
    ... // program code
}

当size等于0时,再减1不会小于0,而是0xFF,故程序是一个死循环。应如下修改。

char size; // 从unsigned char 改为char
while (size-- >= 0) {    
    ... // program code
}

7.2.4 使用变量时要注意其边界值的情况。

示例:如C语言中字符型变量,有效值范围为-128到127。故以下表达式的计算存在一定风险。

char chr = 127;
int sum = 200;

chr += 1; // 127为chr的边界值,再加1将使chr上溢到-128,而不是128。
sum += chr; // 故sum的结果不是328,而是72。

若chr与sum为同一种类型,或表达式按如下方式书写,可能会好些。

sum = sum + chr + 1;

7.2.5 在运算中不要减小数据的精度。

7.2.6 为用户提供良好的接口界面,使用户能较充分地了解系统内部运行状态及有关系统出错情况。

7.2.7 系统应具有一定的容错能力,对一些错误事件(如用户误操作等)能进行自动补救。

7.2.8 对一些具有危险性的操作代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬件等的安全构成危害,以提高系统的安全性。

7.2.9 使用第三方提供的软件开发工具包或控件时,要注意以下几点:

(1)充分了解应用接口、使用环境及使用时注意事项。
(2)不能过分相信其正确性。
(3)除非必要,不要使用不熟悉的第三方工具包与控件。
说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用之前一定对它有较充分的了解,同时第三方工具包与控件也有可能存在问题。

7.2.10 尽可能的对接口进行 instanceof 运算。

说明:instanceof运算符用于强制类型转换之前检查对象  的真实类型以避免类型转换异常,从而提高代码健壮性。

7.2.11 使用大写'L'表示 long 常量

示例: 如下代码:

long a = 100000L;

如果写成小写l,容易被误认为是数字1

long a = 1000001;

7.2.12 在switch 中每个 case 语句都应该包含 break 或者 return 。

说明:因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释。

7.2.13 使用ObjectStream 的方法后,调用reset() ,释放对象。

说明:ObjectStream中会缓存收发的对象,当再次发送同一个对象时,实际发送的不是对象的内容,而是对象的索引信息。
当持续发送新对象时,ObjectStream收发端的缓存会持续增加,如果不调用reset(), 将会导致内存耗尽。

示例:通过ObjectStream收发如下对象。

public class Person implements java.io.Serializable {
  private final String firstName;
  private final String surname;
  private int age;

  public Person(String firstName, String surname, int age) {
    this.firstName = firstName;
    this.surname = surname;
    this.age = age;
  }

  public String toString() {
    return firstName + " " + surname + ", " + age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

public class Sender {
  public static void main(String[] args) throws IOException {
    Socket s = new Socket("localhost", 7000);
    ObjectOutputStream oos = new ObjectOutputStream(
        s.getOutputStream());
    Person p = new Person("Heinz", "Kabutz", 0);
    for (int age=0; age < 1500 * 1000; age++) {
      p.setAge(age);
      oos.writeObject(p);
    }
  }
}

执行Sender时,接收端收到的数据age一直是0,而不是顺序增加的,因为ObjectOutputStream第一次发送p时,发送的是完整数据,后续只发送了对象索引,接收端将第一次接收到的数据存入缓存,后续读取的都是缓存中的数据。

public class Sender2 {
  public static void main(String[] args) throws IOException {
    Socket s = new Socket("localhost", 7000);
    ObjectOutputStream oos = new ObjectOutputStream(
        s.getOutputStream());
    for (int age=0; age < 1500 * 1000; age++) {
      oos.writeObject(new Person("Heinz", "Kabutz", age));
    }
  }
}

Sender2每次发送的都是新对象,接收端收到的数据age会持续增加,与发送的数据一直,但运行一段时间后将导致内存耗尽。

public class Sender3 {
  public static void main(String[] args) throws IOException {
    Socket s = new Socket("localhost", 7000);
    ObjectOutputStream oos = new ObjectOutputStream(
        s.getOutputStream());
    for (int age=0; age < 1500 * 1000; age++) {
      oos.writeObject(new Person("Heinz", "Kabutz", age));
      oos.reset();
    }
  }
}

Sender3每次调用writeObject后又调用了reset()方法,接收端的数据正确,而且不会导致内存耗尽,但reset()耗时比较多,Sender3效率太低。

public class Sender4 {
  public static void main(String[] args) throws IOException {
    Socket s = new Socket("localhost", 7000);
    ObjectOutputStream oos = new ObjectOutputStream(
        s.getOutputStream());
    for (int age=0; age < 1500 * 1000; age++) {
      oos.writeObject(new Person("Heinz", "Kabutz", age));
      if (age % 1000 == 0) oos.reset();
    }
  }
}

Sender4每发送1000个Person对象reset一次,既保证了接收数据的正确性,又兼顾效率。

最后修改日期: 2022年2月23日