Debugging Your Programs
In the professional world of software, you’ll actually spend more time modifying existing programs than you ever will creating new ones. When writing new programs or editing existing ones, it doesn’t matter how much experience or education you might have because even the best programmers can make mistakes. In fact, you can expect that you will make mistakes no matter how careful you may be. Once you accept this inevitable fact of programming, you can learn how to find and fix your mistakes.
In the world of computers, mistakes are commonly called “bugs,” which gets its name from an early computer that used physical switches to work. One day the computer failed and when technicians opened the computer, they found that a moth had been crushed within a switch, preventing the switch from closing. From that point on, programming errors have been called bugs and fixing computer problems has been known as debugging.
Three common types of computer bugs are:
Syntax errors are the easiest to find and fix because they’re merely misspellings of variable names that you created or misspelling of Swift commands that Xcode can help you identify. If you type a Swift keyword such as “var” or “let,” Xcode displays that keyword in pink (or whatever color you specify for displaying keywords in the Xcode editor).
Now if you type a Swift keyword and it doesn’t appear in its usual identifying color, then you know you probably typed it wrong somehow. By coloring your code, Xcode’s editor helps you visually identify common misspellings or typos.
Besides using color, the Xcode editor provides a second way to help you avoid mistakes when you need to type the name of a method or class defined in the Cocoa framework. As soon as Xcode recognizes that you might be typing something from the Cocoa framework, it displays a pop-up menu of possible options. Now instead of typing the entire command yourself, you can simply click on your choice in the pop-up menu and press the Tab key one or more times to let Xcode type your chosen command correctly as shown in Figure 23-1.
Figure 23-1. Xcode displays a menu of possible commands that you might want to use
Syntax errors often keep your program from running at all. When a syntax error keeps your program from running, Xcode can usually identify the line (or the nearby area) of your program where the misspelled commands appears so you can fix it as shown in Figure 23-2.
Figure 23-2. Syntax errors often keep a program from running, which allows Xcode to identify the syntax error
Logic errors are much harder to find and detect than syntax errors. Xcode can identify syntax errors because it recognizes the differences between a properly spelled Swift keyword (such as “var”) compared to a similar misspelled Swift keyword (such as “varr” instead of “var”).
However, logic errors occur when you use Swift code correctly, but it doesn’t do what you intended it to do. Since your code is actually valid, Xcode has no way of knowing that it’s not working the way you intended. As a result, logic errors can be difficult to debug because you think you wrote your code correctly but you (obviously) did not.
How do you find a mistake in code that you thought you wrote correctly? Finding your mistake can often involve starting from the beginning of your program and exhaustively searching each line all the way until the end. (Of course there are faster ways than searching your entire program, line by line, which you’ll learn about later in this chapter.)
Finally, the hardest errors to find and debug are runtime errors. Syntax errors usually keep your program from running so if your program actually runs, you can assume that you have eliminated most, if not all, syntax errors in your code.
Logic errors can be tougher to find, but they’re predictable. For example, if your program asks the user for a password but fails to give the user access even though the user types a correct password, you know you have a logic error. Each time you run your program, you can reliably predict when the logic error will occur.
Runtime errors are more insidious because they don’t always occur predictably. For example, your program may run perfectly well on your computer, but the moment you run the same program on an identical computer, the program may fail. That’s because conditions between two different computers will never be exactly the same.
As a result, the same program can run fine on one computer but fail on the exact same type of computer somewhere else. The problem is that unexpected, outside circumstances can affect your program’s behavior. For example, your program may run just fine until the user presses a number key on the numeric keypad instead of the number key at the top of the alphanumeric keys.
Even though the user may be typing the exact same number (such as 5), the program may treat the 5 key above the R and T keys as a completely different key as the 5 key on the numeric keypad. As subtle as that may be, it could be enough to cause a program to fail or crash.
Because runtime errors can’t always be duplicated, they can be frustrating to find and even harder to fix since you can’t always examine every possible condition your program might face when running on other computers. Some programs have been known to work perfectly – except if the user accidentally presses two keys at the same time. Other programs work just fine – until the user happens to save a file at the exact moment that another program tries to receive e-mail over the Internet.
Usually you can eliminate most syntax errors and find and fix most logic errors. However, it may not be possible to find and completely eliminate all runtime errors in a program. The best way to avoid spending time hunting for bugs is to strive to write code and test it carefully to make sure it’s as error-free as possible.
Simple Debugging Techniques
When your program isn’t working, you often have no idea what could be wrong. While you could tediously examine your code from beginning to end, it’s often faster to simply guess where the mistake might be.
Once you have a rough idea what part of your program might be causing the problem, you have two choices. First, you can delete the suspicious code and run your program again. If the problem magically goes away, then you’ll know that the code you deleted was likely the culprit.
However if your program still doesn’t work, you have to retype your deleted code back into your program. A simpler solution might be to cut and paste code out of Xcode and store it in a text editor such as the TextEdit program that comes with every Macintosh, but this can be tedious.
That’s why a second solution is to just temporarily hide code that you suspect might be causing a problem. Then if the problem persists, you can simply unhide that code and make it visible again. To do this in Xcode, you just need to turn your code into comments.
Remember, comments are text that Xcode completely ignores. You can create comments in three ways:
Note Xcode color codes comments in green (or whatever color you may have defined to identify comments). After creating a comment, make sure Xcode color codes it properly to ensure you have created a comment. If Xcode fails to recognize your comments, it will treat your text as a valid Swift command, which will likely keep your program from running properly.
By turning code into comments, you essentially hide that code from Xcode. Now if you want to turn that comment back into code again, you just remove the // or /* and */ symbols that defines your commented out code.
If you commented out code by choosing Editor Structure Comment Selection (or pressing Command + /), just choose Editor Structure Uncomment Selection (or press Command + / again) to convert that commented code back to working code once more.
Besides turning your code into comments to temporarily hide it, a second simple debugging technique is to use the print command. The idea is to put the print command in your code to print out the values of a variable.
By doing this, you can see what values one or more variables may contain. Putting multiple print commands throughout your program gives you a chance to make sure your program is running correctly.
To see how using the print command along with commenting out code can work to help you debug a program, follow these steps
func applicationDidFinishLaunching(aNotification: NSNotification) {
var myMessage = "Temperature in Celsius:"
let temp = 100.0
print (myMessage + "(temp)")
myMessage = "Temperature in Fahrenheit:"
print (myMessage + "(C2F(temp))")
}
func C2F (tempC : Double) -> Double {
var tempF : Double
tempF = tempC + 32 * 9/5
return tempF
}
Figure 23-3. The print command displays text in the Debug Area of the Xcode window
To view or hide the Debug Area, you have three options:
By peeking in the Debug Area, we can see what the print commands have displayed. If you know anything about temperatures in Fahrenheit and Celsius, you know that the boiling point in Celsius is 100 degrees and the boiling point in Fahrenheit is 212 degrees. Yet our temperature conversion program calculates that 100 degrees Celsius is equal to 157.6 degrees in Fahrenheit, which means the Fahrenheit temperature should be 212 instead of 157.6. Obviously something is wrong, so let’s use the print command and comments to help debug the problem.
func C2F (tempC : Double) -> Double {
var tempF : Double
tempF = tempC + 32 //* 9/5
return tempF
}
This comment will let us check if the tempC parameter is properly coming into the C2F function and getting stored in the tempF variable.
func C2F (tempC : Double) -> Double {
var tempF : Double
tempF = tempC + 32 //* 9/5
print (tempF)
return tempF
}
Temperature in Celsius: 100.0 132.0 Temperature in Fahrenheit: 132.0
By commenting out the calculation part of the code and using the “print (tempF)” command, we can see that the C2F function is storing 100.0 correctly in the tempC variable and adding 32 to this value before storing it in the tempF variable. Because we commented out the calculation part of the code, we can assume that the error must be in our commented out portion of the code.
Although the formula might look correct, the error occurs because of the way Swift (and most programming languages) calculate formulas. First, they start from left to right. Second, they calculate certain operations such as multiplication before addition.
The error occurs because our conversion formula first multiples 32 by 9 (288), and then divides the result (288) by 5 to get 57.6. Finally, it adds 57.6 to 100.0 to get the incorrect result of 157.6. What it should really be doing is multiplying 9/5 by the temperature in Celsius and then adding 32 to the result.
func C2F (tempC : Double) -> Double {
var tempF : Double
tempF = tempC * (9/5) + 32
print (tempF)
return tempF
}
For simple debugging, turning code temporarily into comments and using the print command can work, but it’s fairly clumsy to keep adding and removing comment symbols and print commands. A much better solution is to use breakpoints and variable watching, which essentially duplicates using comments and print commands.
While comments and the print command can help you isolate problems in your code, they can be clumsy to use. The print command can be especially tedious since you have to type it into your code and then remember to remove it later when you’re ready to ship your program.
Although leaving one or more print commands buried in your program won’t likely hurt your program’s performance, it’s poor programming practice to leave code in your program that no longer serves any purpose.
As an alternative to typing the print command throughout your program, Xcode offers a more convenient alternative using the Xcode debugger. The debugger gives you two ways to hunt out and identify bugs in your program:
Using Breakpoints
Breakpoints let you identify a specific line in your code where you want your program to stop. Once your program stops, you can step through your code, line by line. As you do so, you can also peek at the contents of one or more variables to check if the variables are holding the right values.
For example, if your program converts Celsius to Fahrenheit, but somehow converts 100 degrees Celsius into -41259 degrees Fahrenheit, you know your code isn’t working right. By inserting breakpoints in your code and examining the values of your variables at each breakpoint, you can identify where your code calculates its values. The moment you spot the line where it miscalculates a value, you know the exact area of your program that you need to fix.
You can set a breakpoint by doing one of the following:
Stepping Through Code
Once a breakpoint has stopped your program from running, you can step through your code line by line using the Step command. Xcode offers a variety of different Step commands but the three most common are:
The Step Over command examines the next line of code, treating function or method calls as a single line of code.
The Step Into command works exactly like the Step Over command until it highlights a function or method call. Then it jumps to the first line of code in that function or method.
The Step Out command is used to prematurely exit out of a function or method that you entered using the Step Into command. The Step Out command returns to the line of code where a function or method was called.
All three Step commands are used after a program temporarily stops at a breakpoint. By using a Step command, you can examine your code, line by line, and see how values stored in different variables may change.
Such variable watching lets you examine the contents of one or more variables to verify if it’s holding the correct data. The moment you spot a variable holding incorrect data, you can zero in on the line of code that’s creating that error.
The best part about breakpoints is that you can easily add and remove them since they don’t modify your code at all, unlike comments and multiple print commands. Xcode can remove all breakpoints for you automatically so you don’t have to hunt through your code to remove them one by one.
To see how to use breakpoints, step commands, and variable watching, follow these steps:
func C2F (tempC : Double) -> Double {
var tempF : Double
tempF = tempC + 32 * 9/5
return tempF
}
var myMessage = "Temperature in Celsius:"
Figure 23-4. A breakpoint appears to the left of your Swift code
Figure 23-5. A breakpoint halts program execution so you can examine its current state
Figure 23-6. By watching how variables change, you can see how each line of code affects each variable
print (myMessage + "(C2F(temp))")
Figure 23-7. The Step Into command lets you step through the code stored in a function or method
Managing Breakpoints
There’s no limit to the number of breakpoints you can put in a program so feel free to place as many as you need to help you track down an error. Of course if you place breakpoints in a program, you may lose track of how many breakpoints you’ve set and where they might be set. To help you manage your breakpoints, Xcode offers a Breakpoint Navigator.
You can open the Breakpoint Navigator in one of three ways:
The Breakpoint Navigator lists all the breakpoints set in your program, identifies the files the breakpoints are in, and the line number of each breakpoint as shown in Figure 23-8.
Figure 23-8. The Breakpoint Navigator identifies all your breakpoints
Since the Breakpoint Navigator identifies breakpoints by line number, you might want to display line numbers in the Xcode editor (see Figure 23-8). To turn on line numbers, follow these steps:
Figure 23-9. The Line numbers check box lets you show or hide line numbers in the Xcode editor
Click on the close button (the red button) in the upper left corner of the Xcode Preferences window. Xcode now displays line numbers in the left margin of the editor.
To see how to use the Breakpoint Navigator, follow these steps:
Figure 23-10. The Breakpoint Navigator lets you see where you have placed breakpoints
When you create a breakpoint, you must place it on the line where you want your program’s execution to temporarily stop. However, this often means guessing where the problem might be and then using the various step commands to examine your code line by line.
To avoid this problem, Xcode offers a symbolic breakpoint. A symbolic breakpoint stops program execution only when a specific function or method runs. In case you don’t want your program’s execution to stop every time a particular function or method runs, you can tell Xcode to ignore it a certain number of times such as ten. That means the function or method will run up to ten times and then on the eleventh time it’s called, the symbolic breakpoint will temporarily halt execution so you can step through your code line by line.
To create a symbolic breakpoint, you can define the following:
To see how a Symbolic breakpoint works, follows these steps:
Figure 23-11. The Symbolic Breakpoint pop-up window lets you define a breakpoint
Figure 23-12. The Symbolic breakpoint halts program execution in the C2F function defined by the Symbol text field
Note Another way to set a breakpoint without specifying a specific line of code is to create an Exception breakpoint. Normally if your program crashes, Xcode displays a bunch of cryptic error messages and you have no idea what caused the error. If you set an Exception breakpoint, Xcode can identify the line of code that created the crash so you can fix it.
Breakpoints normally stop program execution at a specific line every time. However, you may want to stop program execution on a particular line only if a certain condition holds true, such as if a variable exceeds a certain value, which can signal when something has gone wrong.
To see how a Conditional breakpoint works, follow these steps:
print (myMessage + "(C2F(temp))")
Figure 23-13. The Symbolic breakpoint halts program execution in the C2F function defined by the Symbol text field
Troubleshooting Breakpoints in Xcode
Bugs occur in every program from operating systems and games to spreadsheets and word processors. Keep in mind that because Xcode is a program, it also has bugs in it, which can sometimes frustrate you until Apple updates and fixes these bugs in another version of Xcode.
One common problem with using breakpoints in Xcode is that they may not always work. A breakpoint is supposed to stop program execution, but if Xcode seems to be ignoring your breakpoints, there may be several reasons why this is occurring that doesn’t involve possible bugs in Xcode.
First, make sure you didn’t accidentally deactivate all your breakpoints by pressing Command+Y or choosing Debug Deactivate Breakpoints.
Second, Xcode can compile your program with or without debugging information. Usually you want debugging information turned on so you can debug your program. However, when your program is ready to ship, you want to strip debugging information out to make your program take up less memory and storage space. If you accidentally turn off debugging information, then Xcode will ignore any breakpoints you may have set.
To make sure debugging information is turned on, follow these steps:
Figure 23-14. Turning on debugging information
Summary
Errors or bugs are unavoidable in any program. While syntax errors are easy to find and fix, logic errors can be tougher to find because you thought your code would create one type of result but it winds up creating a different result. Now you’re left trying to figure out what you did wrong when you thought you were doing everything right. Even harder errors to track down are runtime errors that occur seemingly at random because of unknown conditions that affect a program
To help you track down and eliminate most bugs, you can use the print command along with comments, but for most robust debugging, you should use Xcode’s built-in debugger. With the debugger you can set breakpoints in your code and watch how values get stored in one or more variables.
A conditional breakpoint only stops program execution when a certain condition occurs. A symbolic breakpoint only stops program execution when a specific function or method gets called. Once a breakpoint stops a program, you can continue examining your code line by line using various step commands. The Step Into command lets you view code stored inside a function or method while the Step Out command lets you prematurely exit out of a function or method and jump back to the function or method call.
By using breakpoints and step commands, you can exhaustively examine how your program works, line by line, to eliminate as many errors as possible. The fewer errors your program contains, the happier your users will be.