软件构造 6-4 Debugging

6.4 代码调试

一. BugDebug

  DeBug 的方法(优先选上面一种方法):
在这里插入图片描述
  Debug保证程序质量(之前是防御式编程、测试,并且其与前两者均不是最影响程序质量核心手段)或修复程序错误最后一个手段。
  Debug 的目的是寻求错误的根源并消除它。但 debug 会占用大量的时间。


  Debug 是测试的后续步骤:测试发现问题, debug 消除问题。
  Debug困难的:

  • “症状”和“原因”可能相隔很远,高耦合导致的结果
  • 当其他 bug 被修复后,该 bug 消失了
  • 例如四舍五入造成的不准确
  • 因为人工错误导致 bug 的症状难以有效追踪
  • 有些症状是由计时 定时等时间原因导致的
  • 难以重现出错时的输入数据
  • 由于外部软硬件环境变化,导致间歇性的症状
  • 分布式导致的问题

二. Debug 的过程

  Debug 的过程:

  • 重现 bug
  • 诊断 bug
  • 修复 bug
  • 反思
    在这里插入图片描述

  错误定位占据了绝大部分的调试时间。
  针对这种问题,研究者研究问题:自动化错误定位。但普通人常用的方法为:假设-检验(即通常的暴力搜索)。

1. 复现 bug

  从最小测试用例集开始复现错误。

  • 确定有哪些因素跟 bug 相关,将这些因素找出来并变化它们的值。
  • 确保你的 bug 复现环境软件版本软件的运行环境输入数据)跟用户发现 bug 的环境尽可能保持一致。

2. 诊断 bug

  定位 bug 的手段有:

  • 测量,如 logging
  • 分治,或称 Wolf fence algorithm(防狼围栏算法)

在这里插入图片描述

  • 切片,特征值 x 出错了,找出程序中有助于计算特定值 x 的部分。如:
    在这里插入图片描述
    或如下程序中 tax 出错了,查看标红片段:
    在这里插入图片描述
  • 寻找差异
    (1). Leveraging VCS 充分利用版本控制系统,找出在哪个 commit 之后出现了 bug 症状。例如版本控制工具 git 使用类似分治方法探索不同的 commit ,但指定具体的一部分 commit 出错需要用户来指定。
    (2). 基于差异的调试:两个测试用例,分别通过/未通过。通过查找二者所覆盖的代码之间的差异,快速定位出可能造成 bug 的代码行。如下图,通常执行最少的语句(6、7行)可能出现错误。
    在这里插入图片描述
    (3). 查找其他方面的差异:软硬件环境、 JVM 参数配置、输入文件、……
  • 符号化 debug:符号化执行,即不需输入特定的值,使用“符号值”(而实际值”)作为输入解释器模拟程序执行,获得每个变量的“符号化表达式”,从而可判断是否执行正确。
    符号化执行程序的三个状态
    各变量的符号化取值
    路径条件 PC
    计数器
    符号化执行树在这里插入图片描述
  • 调试器
  • 其他方法

三. Debugging 工具

0. 暴力方式

  假设-验证:假设原因,验证原因。可以通过技巧:

  • 看内存导出文件
  • 到处 println()
  • 自动化调试工具
1.1 Memory dump

  不同版本 Java 内存管理方式可能不同,如 Java 8Java 10 的内存管理方式不同;不同语言的内存管理方式可能不同。

1.2 Stack trace

  根据信息知道

  • 实际抛出异常的位置。(信息第一行)
  • 在您自己的代码中执行的最后一行。(信息中出现自己程序的第一行)
  • 你的代码的入口点,也就是你的代码的方法在堆栈跟踪中首先被调用?(信息中心出现自己程序的最后一行)
  • 此执行的入口,Junit测试。(信息最后一行)
    在这里插入图片描述
      具体获取栈内容(7.2节):
Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String description = out.toString();
//or
public class WhoCalled {
	static void f() {
		// Generate an exception to fill in the stack trace
		try {
			throw new Exception();
		} catch (Exception e) {
			for(StackTraceElement ste : e.getStackTrace())
				System.out.println(ste.getMethodName());
		}
	}
	static void g() { f(); }
	static void h() { g();}
	public static void main(String[] args) {
		f();
		System.out.println("--------------------------");
		g();
		System.out.println("--------------------------");
		h();
	}
}
//or
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for(Thread t : map.keySet()){
	StackTraceElement[] frames = map.get(t);
	//... analyze frames
}

  上面的第二种方法输出

f
main
--------------------------
f
g
main
--------------------------
f
g
h
main
1.3 Printf debugging

  在程序内部各部分展示程序执行时的动态信息,比使用静态dump 分析更有效。
  来源于 C 语言。一旦软件对外发布****,所有用于 debugprint 语句都要去除或禁用

  • 可以选择注释,但速度慢效率低
  • 委托
//Coding & debugging
public static void MyPrint(String in) {
	System.out.println(in);
}
MyPrint("Entering callMethod ");
result = callMethod();
MyPrint("The result is " + result);
//Released
public static void MyPrint(String in) {
	//System.out.println(in);
}
MyPrint("Entering callMethod ");
result = callMethod();
MyPrint("The result is " + result);
1.4 Logging

  通过设定日志级别来确定要 log 哪些信息;log 结果可被多种渠道加以处理,可通过设定条件进行过滤,并输出为多种格式;可使用层次化的多个日志记录器。结构如下:
在这里插入图片描述
  针对一个应用,为其设定全局的 logger:

import java.util.logging.*;
Logger.getGlobal().info("File -->Open menu item selected");
/*
Results:
May 10, 2018 10:12:15 PM LoggingImageViewer main
INFO: File -->Open menu item selected
*/

  关闭日志命令:

Logger.getGlobal().setLevel(Level.OFF);

  使用全局 logger 导致信息混乱,可以定义自己的 logger

import java.util.logging .*;
private static final Logger myLogger = 
				Logger.getLogger("com.mycompany.myapp");
//often using class name as logger name

//Or

public class LogTest {
	static String strClassName = LogTest.class.getName(); //get class name
	static Logger myLogger = Logger.getLogger(strClassName);
	// using class name as logger name
	……
	myLogger.info("XXXX");
}

  对于日志的输出,可以输出到控制台(缺省)。对于日志的处理,日志处理器也需要设定日志级别。

public class LevelTest {
	private static String name = test.class.getName();
	private static Logger log = Logger.getLogger(name)
	public void sub() {
		log.setLevel(Level.FINEST);
		log.setUseParentHandlers(false);
		Handler handler = new ConsoleHandler();
		handler.setLevel(Level.FINEST);
		log.addHandler(handler); //将 logger 和相应的 handler 捆绑在一起, delegation
		
		log.severe("severe level");
		log.warning("warning level");
		log.info("info level");
		log.config("config level");
		log.fine("fine level");
		log.finer("finer level");
		log.finest("finest level");
	}
}

  除了控制台处理器 ConsoleHandler ,还可以设定其他的 handlers

  • SocketHandler 网络
  • FileHandler 文件
  • StreamHandler
  • ConsoleHandler
  • MemoryHandler

  可以选用不同的格式:

  • SimpleFormatter 人类可读格式
  • XMLFormatter
1.5 编译器警告信息

  为了正确性,我们可以把编译器的 warning level 调到最高级别,消除所有 warning;精益求精,把 warningerror 看待;学着把编译器当作自己的老师,搞清楚每一个 warning ,并在后续代码中避免。

1.6 Debugger
  • Breakpoints 设置断点
    执行方式有
    Step Over
    Step Into
    Step Return
  • Single-stepping 单步执行
  • Resume operation 恢复运行
  • Temporary breakpoints 临时断点
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值