The latest instance of the course can be found at: O1: 2024
- CS-A1110
- Supplementary Pages
- Using the Debugger
Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.
Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Using the Debugger
About This Page:
Questions Answered: How can I execute my program step by step and examine what it does? What’s a good tool for hunting runtime errors?
Prerequisites: This page assumes prior knowledge of topics from Weeks 1 to 4. You can make the most of this page if you read it sometime after reaching Chapter 4.2.
Points Available: None. This material is optional.
Related Projects: Miscellaneous.
Introduction: Tracing Program Execution
Programmers — and students of programming — commonly need to figure out what an existing program does. For instance, they may need to:
- develop an existing program further;
- write documentation about a program someone else wrote;
- locate errors in a buggy program (self-written or otherwise); or
- study an example program provided by a teacher.
In such circumstances, programmers often mentally trace the program’s behavior step by step. Such tracing is also useful while writing an entirely new program: in order to determine if the code works, the programmer needs to be able to “run it in their mind”
As a quick example, consider the Experience
class from Chapter 3.4:
class Experience(val name: String, val description: String, val price: Double, val rating: Int) {
def valueForMoney = this.rating / this.price
def isBetterThan(another: Experience) = this.rating > another.rating
def chooseBetter(another: Experience) = if (this.isBetterThan(another)) this else another
}
Here’s a little program that calls some of the class’s methods:
object ExperienceTest extends App {
println("Starting the program.")
val wine1 = new Experience("Il Barco 2001", "okay", 6.69, 5)
val wine2 = new Experience("Tollo Rosso", "not great", 6.19, 3)
val better = wine1.chooseBetter(wine2)
var result = better.name
println(result)
result = "Better than wine1? " + better.isBetterThan(wine1)
println(result)
}
Try running ExperienceTest
mentally step by step. If you do it carefully, you’ll notice
that there is a whole bunch of things you need to keep track of at each step:
- where in the program you’re currently at (which line and where on that line?);
- the class and object definitions that form the program (although you can easily refresh your memory from the code);
- the values of each variable at the present time (
var
s demand particular attention); - which data is associated with each object (via its instance variables), as determined by earlier object-instantiating commands and subsequent effects on state (if any);
- while running a method: Which object is the active
this
object for the method call? What are the values of each parameter variable during this method call? - when returning from a method: Where was this particular method call initiated? Where should execution resume after returning?
- the relevant built-in operators, functions, and classes and how they are used; and
- any intermediate results that are formed while evaluating compound expressions.
Not all of those things are directly visible in program code. Since there are a lot of details to remember, it often makes sense to use an auxiliary tool that lightens the programmer’s burden and helps them concentrate on the overall goals and structure of the program, possible locations of bugs, or whatever the task calls for.
A simple piece of paper can be a very useful aid: you can make notes of what happens during the program run and sketch out diagrams of the relevant objects and variables.
Instead of or in addition to pen and paper, it can be helpful to use an auxiliary program that illustrates program execution step by step. In this ebook, you’ve already seen various programs illustrated as animated diagrams. Such animations aren’t always available, though, so what to do?
Debuggers
A debugger is a utility program for examining the execution steps of other programs. You can run your program in a debugger and examine its state as you step through it line by line. Debuggers’ most prominent purpose is to help the programmer locate defects in code; hence the name.
The information that a debugger displays is similar to that shown in this ebook’s animations. However, debuggers generally aren’t quite as graphical and detailed as those animations, since they’re usually designed for experienced professionals rather than learners; dealing with large programs calls for a different design. Nevertheless, beginner programmers too can benefit from using a debugger.
Some debuggers are standalone programs; others are integrated in an IDE. Eclipse’s Scala IDE, for instance, includes a debugger for Scala programs.
The Debugger in Scala IDE
Learning to handle a debugger fluently takes some effort. Here are some of the things you may want to do:
- See the below list of common debugging commands in Scala IDE.
- Do the little practice task further below on this page.
- Experiment on your own programs: write a small program and examine it in the debugger or explore O1’s example programs.
- Find additional material online. For instance, Scala IDE’s web site has a short tutorial video.
- Ask O1’s teaching assistants to help you at the lab sessions.
Selected debugger commands
- Setting a breakpoint
- Before starting a program in the debugger, you’ll usually want to set at least one breakpoint: a location in the program where execution should pause so that you can examine the program in detail. To do this, start by choosing a line in your program where you want the breakpoint. (The choice is up to you, but placing a breakpoint at the first command within your app object is a good idea to try first.) Then select Run ‣ Toggle Breakpoint (or
Ctrl+Shift+B
or double-click the gray bar next to the line number). Doing the same again toggles the breakpoint off.- Launching a program in the debugger
- Select your program’s app object. Then: Run ‣ Debug As ‣ Scala Application (or right-click the file name and choose Debug As ‣ Scala Application or press the bug in the toolbar at the top). This command launches the your program and runs it until the (first) breakpoint. (If you haven’t set any breakpoints, your entire program will run much like usual.)
- Executing one entire line of code
- Select Run ‣ Step Over or press
F6
. The computer will then execute the current line in full without displaying any intermediate steps. Do this repeatedly to execute multiple lines.- Executing (part of) a line in detail
- Select Run ‣ Step Into or press
F5
. This command is similar to Step Over but doesn’t necessarily execute the entire line: it displays pauses at certain important events within the line’s execution. In particular, if the line contains any method calls, Step Into “jumps inside the method” so that you can examine the method’s execution steps in detail. The debugger similarly highlights how variables are accessed for their values. Executing a single line of code can thus be split into several consecutive Step Intos.- Returning from a method to the call site
- Select Run ‣ Step Return or press
F7
. The method you were in runs until it returns and your debugger session resumes where that method was called.- Continuing execution without stepping
- Select Run ‣ Resume or press
F8
. Your program will run until the next breakpoint, or until the end of the entire program run if no more breakpoints are reached.- Stepping backwards
- There’s no command for this, but you can terminate the current debugger run and start over.
- Stopping a debugging session
- Stop the program with Run ‣ Terminate or
Ctrl+F2
or use the other commands listed above until you reach the end of the program and the Debug tab says <terminated>.- Exiting the debugger
- Move back to the Scala perspective by clicking the Scala icon in Eclipse’s top-right corner. Usually, you’ll want to terminate or finish the current program run first so that you won’t burden your computer with incomplete executions suspended in the background.
You’ll find shortcuts to many of the above commands in the toolbar at the top of the debugger view. You can Terminate by pressing , for example.
Student question: Why can’t debuggers go backwards?
There are certain technical challenges to overcome, which is why most support for backwards stepping hasn’t been built into most debuggers. But it’s not impossible and might become more frequently supported in the future.
A Small Practice Task
Fetch the Miscellaneous project. It contains a class named o1.excursion.Excursion
and
an app object named ExcursionTest
.
Start here
First study the Scaladocs for Excursion
and skim its program
code. Then proceed.
Also, if you didn’t import O1’s Eclipse preferences file in Chapter 1.2 (or aren’t sure if you did), do that now. Some of the instructions below don’t apply unless your Eclipse is set to O1’s recommended settings.
Now drill into the given code; see below for guidance. As you do so, it will turn out
that there is a little bug in the Excursion
class.
The instructions below are only guideline; feel free to experiment on your own.
Examining Excursion
in the debugger
Set a breakpoint at the first println
line in ExcursionTest
. Launch the program in
the debugger. (If Eclipse prompts you for a Preferred Launcher, pick Scala
JVM Launcher.)
Execution pauses at the breakpoint. Step through the program at your own pace. Explore:
- First, get to know the Step Over command (
F6
). - Notice how the program’s output appears step by step in the Console.
- Try to understand each execution step as it happens.
- Observe the values in the Variables tab.
- Try the Step Into command (
F5
), too. - Notice the description of the call stack in the Debug tab. Notice how it changes as method calls begin and end. (The top part of the stack’s description is where the interesting stuff happens.)
- Sooner or later, as you work your way through the program with Step Into and Step Over, you should notice something odd:
Did some unfamiliar code show up as ResizableArray.scala
? Don’t worry, that’s to be
expected:
- You’ve ended up reading the code that implements Scala’s
standard library.
ResizableArray
is a library class that has been used for implementing Scala’sBuffer
class. - That code showed up because of an
IndexOutOfBoundsException
error. A look at the call stack should tell you that the error has arisen while executinglastParticipant
in theExcursion
class, which has used a buffer, which has in turn used theResizableArray
class. - The highlighted line of code “throws” (
throw
) an error. This error makes the program crash. The debugger that’s running the program has, however, automatically paused execution just before the imminent crash so that you can examine the program’s state at that time. - Execute the highlighted line with, say, Step Over
(
F6
). A stack trace detailing the runtime error appears in the text console (cf. the example in Chapter 4.2). The program run is over.
You may already have noticed — and you can confirm from the stack trace — that the
error occurred while running the lastParticipant
method call that was initiated on
line 22 of ExcursionTest
. Use the debugger to study this error further. (Even if you
already identified the bug, you can do this to practice.) See below for a suggested workflow.
Exploring the buggy method
- Relaunch the debugger. Execution again pauses at the breakpoint you set earlier.
- Add another breakpoint on line 22.
- Select Resume (
F8
). The program runs until the second breakpoint that you just set. The highlighted line, which produces the program crash, hasn’t been executed yet. - Select Step Into (
F5
) two times. This will merely show you howtestTrip
receives its value. - Select Step Into (
F5
) a third time. This will take you inside thelastParticipant
method. - Go to the Variables tab and check the state of the
Excursion
object. (this
refers to that object while we’re runninglastParticipant
on the object.) Among other things, you should see the buffer thatinterestedStudents
refers to and, within that buffer, the names of four people and the current size of the buffer (4).
Recall: the error we got was IndexOutOfBoundsException
, which means that an index
wasn’t within the appropriate range, given the buffer’s current size. Can you spot
the mistake in lastParticipant
’s code? Study the code and explore further in the
debugger as necessary.
Placing breakpoints
In that example, we placed the breakpoints in the app object. Another alternative would
have been to set a breakpoint directly into the lastParticipant
method. Had we done so,
the debugger would pause the program each time that method is invoked.
Similarly, if your program has a graphical user interface, you can place a breakpoint in one of the GUI’s event-handler methods or one of the model’s methods that is invoked by those event handlers. The debugger will then stop as you use the GUI and cause that method to be reached.
It’s also possible to define a conditional breakpoint that triggers only under specific circumstances: see Breakpoint Properties in Eclipse’s menu.
On Debuggers and Debugging
Most of O1’s programming assignments don’t specifically exhort you to use the debugger. Nevertheless, it’s a good idea to gradually make this tool part of your arsenal. When you run into bugs, remember that the debugger is available to use.
As you’ve already seen, even though the tool is called a “debugger”, it doesn’t magically fix any errors. It doesn’t “even” locate the errors automatically. That still requires work from you the programmer. However, just the fact that the debugger helps you to trace program runs is sometimes a great time-saver.
Speaking of how humans are needed for debugging, here’s a 49-second video of Steve Jobs advertising an IDE feature:
Summary of Key Points
- A programmer often needs to mentally run programs step by step.
- There is software that can help the programmer reason about programs. These tools are especially useful when the program is unfamiliar, buggy, and/or complex.
- A debugger is an auxiliary program that lets the programmer examine the intermediate stages of a program run and the state of the program at those stages.
- Eclipse’s Scala IDE has a debugger built in. You may find it useful in O1 and elsewhere.
- Links to the glossary: debugger, breakpoint.
Feedback
Credits
Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!
Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.
Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.
The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.
The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.
The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.
The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio have done the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.
The other diagrams and interactive presentations in the ebook are by Juha Sorva.
The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.
The pedagogy behind O1Library’s tools for simple graphical programming (such as Pic
)
is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and
Krishnamurthi and Picturing Programs by Stephen Bloch.
The course platform A+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.
For O1’s current teaching staff, please see Chapter 1.1.