知识库 知识库
首页
  • Hyperskill - Java

    • Java basic
    • Java OOP
    • 应知
    • 扩展
    • IO & Stream
    • Error & Exception
    • Algorithm & Data structure
    • Design pattern
    • Web
    • Spring boot
  • 练习题

    • 选择题 & 填空题
    • 代码题
  • Frank - Java与生活 (OOP)

    • 参考资料
    • Java基础
    • OOP上半部分
    • OOP下半部分
  • Frank - Java API进阶

    • Base API
    • Unit Test and main function
  • 学习笔记
  • 学习笔记

    • 数据库
  • Frank - MySQL删库跑路

    • 安装、连接、配置
    • 基本操作——数据库
    • 基本操作——表
    • 基本操作——数据
    • 数据类型
    • 列属性完整性
    • 数据库设计思维
    • 单表查询
    • 多表查询
  • 学习笔记

    • 其它
  • Frank - Linux现代方法

    • 必知
    • 命令
    • 技巧
  • 技术文档
  • Git
  • GitHub技巧
  • 前端
  • Khan Academy - 语法
  • Monthly
  • 阅读
  • Others
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 标签
  • 归档
GitHub (opens new window)

Jim FuckPPT

Java小学生
首页
  • Hyperskill - Java

    • Java basic
    • Java OOP
    • 应知
    • 扩展
    • IO & Stream
    • Error & Exception
    • Algorithm & Data structure
    • Design pattern
    • Web
    • Spring boot
  • 练习题

    • 选择题 & 填空题
    • 代码题
  • Frank - Java与生活 (OOP)

    • 参考资料
    • Java基础
    • OOP上半部分
    • OOP下半部分
  • Frank - Java API进阶

    • Base API
    • Unit Test and main function
  • 学习笔记
  • 学习笔记

    • 数据库
  • Frank - MySQL删库跑路

    • 安装、连接、配置
    • 基本操作——数据库
    • 基本操作——表
    • 基本操作——数据
    • 数据类型
    • 列属性完整性
    • 数据库设计思维
    • 单表查询
    • 多表查询
  • 学习笔记

    • 其它
  • Frank - Linux现代方法

    • 必知
    • 命令
    • 技巧
  • 技术文档
  • Git
  • GitHub技巧
  • 前端
  • Khan Academy - 语法
  • Monthly
  • 阅读
  • Others
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 标签
  • 归档
GitHub (opens new window)
  • Hyperskill - Java

    • Java basic

    • Java OOP

    • 应知

    • 扩展

    • IO & Stream

    • Error & Exception

      • Theory:Errors in programs
      • Theory:What is an exception
      • Theory:NPE
      • Theory:Array exceptions
      • Theory:Hierarchy of exceptions
      • Theory:Exception Handling
      • Theory:Throwing exceptions
      • Theory:Cutom exceptions
      • Theory:What is a bug
      • Theory:Introduction to logging
      • Theory:Debugging techniques
        • Logging/'printf' debugging
        • Assertions
        • Attaching a debugger
        • Conclusion
    • Algorithm & Data structure

    • Design pattern

    • Web

    • Spring boot

  • 练习题

  • Frank - Java与生活

  • Frank - Java API进阶

  • 学习笔记

  • Java
  • Hyperskill - Java
  • Error & Exception
Jim
2022-12-30
目录

Theory:Debugging techniques

Debugging is the process of finding and fixing bugs in a program. Some bugs, like those that prevent the program from compiling, can be fixed easily since the compiler or an IDE can tell you what's wrong. Other bugs are trickier and may require you to put a lot of effort into detecting them.

In this topic, we will consider the most popular ways that programmers use to debug a program:

  • Logging
  • Assertions
  • Attaching a debugger

# Logging/'printf' debugging

One way to track the changes in the program state is to insert additional print statements in the code. When executed, they will inform you about what's happening under the hood at runtime.

For example, you can insert a line just before a method returns that will print the return value to the console. This way, in addition to seeing the final result, you will also be able to understand what happens at a certain stage of data processing.

Let's look at the following code snippet, which hangs indefinitely when run:

class UnexpectedResults {
    public static void main(String[] args) {
        count(1, 10);
    }

    public static void count(int start, int to) {
        while (start < to); {
            System.out.println(start);
            start++;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

While the error is not hard to catch, we can still add some print statements that would clearly indicate where the hanging happens:

class UnexpectedResults {
    public static void main(String[] args) {
        System.out.println("main() started");
        count(1, 10);
        System.out.println("main() complete");
    }

    public static void count(int start, int to) {
        System.out.println("count() started");
        while (start < to); {
            System.out.println(start);
            start++;
        }
        System.out.println("count() complete");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Now, instead of just hanging, the program will output:

main() started
count() started
1
2

This output shows us that the program reaches the start of the count() method, but never reaches its end, which means that the problem is in the while loop. After we take a closer look at the construct, we see that there is an extra semicolon.

Inserting print statements is the most basic way to debug your code, however, we provide it just so you know this technique. You should not use this method in real projects because modern debuggers can do the same in a much more convenient way and because you would not be able to do that everywhere. For example, if you want to get information from some library code, this would be a problem because you cannot modify compiled code.

Be patient, we will cover the nice way shortly.

# Assertions

In order to detect bugs in the program at earlier development stages, you can use assertions. The assertion is a mechanism that monitors the program state, but unlike additional print statements, it terminates the program in a fail-fast manner when things go wrong.

Fail-fast is an approach when errors that could otherwise be non-fatal are forced to cause an immediate failure, thus making them visible.

You may wonder why one would want to crash the production code, and the answer is: one wouldn't. Assertions are meant for testing/debugging and should never be used in production code.

Let’s take a look at the following program:

class BrokenInvariants {
    public static void main(String[] args) {
        Cat casper = new Cat("Casper", -1);
    }
}

class Cat {
    String name;
    int age;
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

This code creates a Cat object. This would be fine if it wasn't for the negative age value that makes no sense. Naturally, in a more complex program, this may lead to various bugs. Such an object may be passed around for a long time before we see a problem, and when a problem arises, it is not always obvious what was the cause.

To prevent that from happening, we can use an assertion right in the Cat constructor:

public Cat(String name, int age) {
    assert (age >= 0) : "Invalid age";
    this.name = name;
    this.age = age;
}
1
2
3
4
5

The part before the colon specifies the boolean expression that should be checked, and when it evaluates to false, an error is thrown. The part after it specifies the message that describes the error.

Now, if we run the code with the -ea flag (java -ea BrokenInvariants), the program will throw an error and terminate right in the Cat constructor:

Exception in thread "main" java.lang.AssertionError: Invalid age
at Cat.<init>(scratch_1.java:11)
at BrokenInvariants.main(scratch_1.java:3)
1
2
3

You may have noticed that we used the word invariants and are curious what it means. Invariants are constraints that must be met for a program to function properly. In the code above, positive age is an example of an invariant. Using a negative age is asking for a problem. Enforcing invariants is exactly why we need assertions.

We can also use assertions to check method preconditions and postconditions, that is conditions that must be met before or after a method is invoked.

The only and very important limitation that should be observed when using assertions is that they should never produce side effects and change the way a program operates. In other words, the assertion should not affect a program in any way other than throwing an error.

Below is an example of an assertion that violates this rule:

assert (age++ >= 0) : "Invalid age";
this.name = name;
this.age = age;
1
2
3

Besides checking the condition, the assertion also increments the age value, which means that when assertions are disabled, the program will operate differently, invalidating the results of testing and probably introducing new bugs. Clearly, you want to avoid such situations in production.

# Attaching a debugger

A debugger is a tool that interferes with the normal program execution allowing you to get runtime information and test different scenarios to diagnose bugs. This is the most popular use of debuggers. However, when you grow more experienced with them, you'll see that they can be helpful in various situations, not necessarily related to bugs.

Modern debuggers provide a vast variety of tools that can be used to diagnose the most intricate failure conditions, so they definitely warrant a section of their own. In the next topics, we will get started with IntelliJ IDEA debugger and learn how to debug simple code.

# Conclusion

In this topic, you have learned about different debugging techniques you can use to ensure that your code is error-free. The most basic method is inserting print statements to keep track of the values and the execution order of your program. Also, you may add assertions using the assert keyword to make potential hidden errors visible at an early stage of development. Such assertions may be enabled and disabled by adding or removing the -ea JVM flag. Finally, you may attach a debugger to the program to examine its internal state at runtime. These techniques will help you to detect and fix bugs in your code efficiently.

编辑 (opens new window)
#Debugging
上次更新: 2022/12/30, 13:28:45
Theory:Introduction to logging
Theory:Computer algorithms

← Theory:Introduction to logging Theory:Computer algorithms→

最近更新
01
《挪威的森林》
04-14
02
青钢影
04-14
03
Processing strings
02-18
更多文章>
Theme by Vdoing | Copyright © 2022-2023 Jim Frank | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式