我的例子比较简单,可视化程序运行过程的工具在不同编程语言都有所探索或已有产品。
网页和介绍都在这里: http://findtheflow.io/ 。我不是很善于推介产品,只是记录使用过程供大家参考。
这周数据结构学到第6章Ordered Linked List,章末程序是编制一个输入并输出学生信息(名字,学号,专业),学生信息以StudentComparable implements Comparable<StudentComparable>的形式体现 ,而学生信息的集合通过public class LinkedListGenericInnerNode<T extends Comparable<T>> 形式储存起来。作为有序链表,排序的标准是学生ID的大小,这个是后来debug的线索。
有序链表有两种实现方式,一种是使用外部的NodeGeneric类,一种是将其作为内部类。考虑的NodeGeneric本身不需要在主程序中调用,为增强代码功能单元内部的紧密结合我选择内部类的方式。
主程序非常简单:
public static void main(String[] args) {
LinkedListGenericInnerNode<StudentComparable> list= new LinkedListGenericInnerNode<StudentComparable >();
LinkedListGenericInnerNode<StudentComparable> ptr;
StudentComparable student1 = new StudentComparable("George","MIS",1234);
StudentComparable student2 = new StudentComparable("Maya","CS",1212);
StudentComparable student3 = new StudentComparable("Joffrey","CS",3456);
list.insert(student1);
list.insert(student2);
list.insert(student3);
list.printRecursive();
}
结果运行时发现了一个非常奇怪的现象:只有student1和student3的信息被输出。debug开始咯!~
一开始我采用控制变量法在3个object中保留1-2个,发现都正常运行,而3个在一起时中间那个就无法输出(还不知道是否被输入到list里),这是我使用Flow去记录并展示程序运行过程,总体结果如下:
可以看到insert方法被调用了三次,说明3个学生信息都试图加入到list中。
再看方法的返回值(鼠标放在显示方法的方框上即可),第1、3次都是true,
第二次华丽地显示false:
继续查看insert方法内调用子方法的返回值,发现正常(1代表head<头链环>的数值大于被插入的)
到这里可以看出,insert过程正常执行,但结果是false。在insert方法内,只有一个地方能(在初始赋值为false后)改变boolean inserted的值。查找后发现是该if()内的参数出错,先发全部(正确)代码:
public boolean insert(T item){
boolean inserted=false;
NodeGeneric<T> ptr, prev;
ptr=head;
prev=null;
while (ptr!=null && ptr.data.compareTo(item)<0){
prev=ptr;
ptr=ptr.next;
}
if (ptr==null||!(ptr.data.equals(item))){
inserted=true;
NodeGeneric<T> newp=new NodeGeneric();
newp.data=item;
newp.next=ptr;
if (prev==null)
head=newp;//inserting in an empty list
else
prev.next=newp;
}
return inserted;
}
while先控制ptr(“指针”)进行搜索,在ptr指向null(list结尾)或ptr所获取的data等于或大于预备插入的item情况下停止搜索。然后由if内的指令进行插入。
检查发现if条件的第二部分!(ptr.data.equals(item) 少了一个!,boolean值相反,这导致在item在list中不存在的情况下返回false直接让if终止,所以student2没有插入成功。
那为什么student1和3插入成功了呢?因为student1插入时list是空的,ptr是null,if结构发生short-circuit(短路);而student3的id大于student1,在之前while中ptr移动到list最后也是ptr,if再次短路。
阴阳差错,错误代码部分成功运行。
多谢Flow帮我比较容易地找出问题,要不然对于近百行的class,一行行一句句看如同重写代码,非常不经济也不符合程序开发的原则。
此外,本次bug充分暴露了条件判断中short-circuit的问题。
补充:
1 除了student2的insert()返回false,从第3次insert()只调用了一个compareTo和printRecursive下printR迭代3次而不是4次也可能判断出student2插入为false。
2 幸好问题只是if参数,更糟糕的情况是insert()返回true然而实际上list中没有,甚至导致其他node出了问题。当然但愿这里复杂情况在能力和知识进一步增长的情况下早日出现,早暴露早预防。
3 Flow还有可以通过package、class和method三级探索程序运行时不同层级的互动关系,本文案例非常简单,问题是单单通过运行单线过程分析得到解决的,在更复杂的情况中,不同package和class的互动关系要作为debug的线索。
4 附正确运行图