0% found this document useful (0 votes)
1 views34 pages

Avoid Debugging

Uploaded by

raghadshaar20
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views34 pages

Avoid Debugging

Uploaded by

raghadshaar20
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Advanced Software Engineering

Part 08 – Avoiding Debugging


Dr. Amjad AbuHassan

2/21/24 Dr. Amjad AbuHassan 1


Objectives

● Write code that either avoids debugging entirely,


● or at least makes it easy when we must do it.

2/21/24 Dr. Amjad AbuHassan 2


First defense: make bugs impossible

● The best defense against bugs is to make them impossible by design.


● Static checking eliminates many bugs by catching them at compile
time.
● Dynamic checking.
● Immutability is another design principle that prevents bugs.
● Unreassignable references: variables declared with the keyword final

2/21/24 Dr. Amjad AbuHassan 3


Example

final char[] letters = new char[] { 'a', 'e', 'i', 'o', 'u' };

● The letters variable is declared final, but is it really unchanging?


● Which of the following statements will be illegal (caught statically by
the compiler), and which will be allowed?

letters = new char[] { 'x', 'y', 'z' };


letters[0] = 'z';

2/21/24 Dr. Amjad AbuHassan 4


Second defense: localize bugs

● Localize bugs to a small part of the program if we can’t prevent them


● When localized to a single method or small module
● bugs may be found simply by studying the program text.
● the easier it is to fix
/**
* @param x requires x >= 0
* @return approximation to square root of x
*/
public double sqrt(double x) { ... }
2/21/24 Dr. Amjad AbuHassan 5
Example 2

● Suppose we calls sqrt with a negative argument.


● What’s the best behavior for sqrt?
● Since the caller has failed to satisfy the requirement that x should be
nonnegative, sqrt is no longer bound by the terms of its contract, so it
is technically free to do whatever it wants:
● return an arbitrary value, or enter an infinite loop, or melt down the CPU.

2/21/24 Dr. Amjad AbuHassan 6


Example 2 cont.

● The most useful behavior would point out the bug as early as possible,
by inserting a runtime check of the precondition.
/**
* @param x requires x >= 0
* @return approximation to square root of x
*/
public double sqrt(double x) {
if (! (x >= 0))
throw new IllegalArgumentException("required x >= 0, but was: " + x);
...
}

2/21/24 Dr. Amjad AbuHassan 7


Example 2 cont.

● When the precondition is not satisfied, this code terminates the


program by throwing an unchecked IllegalArgumentException.
● The effects of the caller’s bug are prevented from propagating.
● Checking preconditions is an example of defensive programming.
● Defensive programming offers a way to mitigate the effects of bugs
even if you don’t know where they are.

2/21/24 Dr. Amjad AbuHassan 8


Assertions

● assert is a built-in statement in the language, not a method .


● The simplest form: takes a Boolean expression and throws
AssertionError if the Boolean expression evaluates to false:
assert x >= 0;

● Assertions have the added benefit of documenting an assumption


about the state of the program at that point,
● assert x >= 0 says “at this point, it should always be true that x >= 0.”
2/21/24 Dr. Amjad AbuHassan 9
Assertions cont.

● An assertion is executable code unlike a comment,


● Assertion may also include a description expression, which is usually a
string, but may also be a primitive type or a reference to an object.
assert x >= 0 : "x is " + x;

● If x == -1, then this assertion fails with the error message


● x is -1

2/21/24 Dr. Amjad AbuHassan 10


Assertions cont.

● Along with a stack trace that tells you where the assertion was found in
your code and the sequence of calls that brought the program to that
point.
● This information is often enough to get started in finding the bug.

2/21/24 Dr. Amjad AbuHassan 11


Assertions Problem
● A serious problem with Java assertions is that assertions are off by default.
● If you just run your program as usual, none of your assertions will be checked!

● Java’s designers did this because checking assertions can sometimes be costly to
performance.
● For example, a function that searches an array using binary search has a
requirement that the array be sorted.
● Asserting this requirement requires scanning through the entire array.

2/21/24 Dr. Amjad AbuHassan 12


Assert vs JUnit Assertions
● Java assert statement is a different mechanism from the JUnit
methods assertTrue(), assertEquals(), etc.
● They all assert a predicate about code but are designed for use in different
contexts.
● The assert statement: used in implementation code, for defensive
checks inside the implementation.
● JUnit assert...() : used in JUnit tests, to check the result of a test.
● The assert statements don’t run without -ea, but JUnit assert…() always run.
2/21/24 Dr. Amjad AbuHassan 14
What to Assert

Some things you should assert:


● Method argument requirements, like in sqrt example.
● Method return value requirements. This kind of assertion is
sometimes called a self check.
● For example, the sqrt method might square its result to check whether it is
reasonably close to x:

2/21/24 Dr. Amjad AbuHassan 15


What to Assert cont.

● Write runtime assertions as you write


public double sqrt(double x) {
the code, not after the fact. assert x >= 0;
double r;
● you will have the invariants in mind. ... // compute result r

● If you postpone writing assertions, assert [Link](r*r - x) < .0001;


return r;
you’re less likely to do it, and you’re }

liable to omit some important invariants.

2/21/24 Dr. Amjad AbuHassan 16


What not to Assert

● Runtime assertions are not free. They can clutter the code, so they
must be used with good judgment.
● Avoid trivial assertions, For example:

// don't do this:
x = y + 1;
assert x == y+1;

2/21/24 Dr. Amjad AbuHassan 17


What not to Assert cont.

● Never use assertions to test conditions that are external to your


program, such as
● the existence of files,
● the availability of the network,
● or the correctness of input typed by a human user.
● Assertions test the internal state of your program to ensure that it is
within the bounds of its specification.
2/21/24 Dr. Amjad AbuHassan 18
What not to Assert cont.

● Assertion failures indicate bugs.


● External failures are not bugs, and there is no change you can make to
your program in advance that will prevent them from happening.
● External failures should be handled using exceptions instead, like
FileNotFoundException or NoRouteToHostException.

2/21/24 Dr. Amjad AbuHassan 19


What not to Assert cont.

● Many assertion mechanisms are designed so that assertions are


executed only during testing and debugging and turned off when the
program is released to users.
● Since Java assertions may be disabled, program correctness should
never depend on whether or not the assertion expressions are
executed.

2/21/24 Dr. Amjad AbuHassan 20


What not to Assert cont.

● Asserted expressions should not have side-effects. For example, if we want


to assert that an element removed from a list was actually found in the list,
we don’t write it like this:
// don't do this:
assert [Link](x);

● If assertions are disabled, the entire expression is skipped, and x is never


removed from the list. We write it like this instead:
boolean found = [Link](x);
assert found;
2/21/24 Dr. Amjad AbuHassan 21
What not to Assert cont.

● If a conditional statement or switch does not cover all the possible


cases, it is good practice to use a check to block the illegal cases.
● We don’t use the assert statement here, because it can be turned off.
● Instead, throw an exception in the illegal cases, so that the check will
always happen:

2/21/24 Dr. Amjad AbuHassan 22


What not to Assert cont.

switch (vowel) {
case 'a’:
case 'e’:
case 'i’:
case 'o’:
case 'u': return "A";
default: throw new AssertionError("must be a vowel, but was: " + vowel);
}

2/21/24 Dr. Amjad AbuHassan 23


Incremental Development

● A great way to localize bugs to a tiny part of the program is


incremental development.
● Build only a bit of the program at a time, and test that bit thoroughly
before move on.
● When we discover a bug, it’s more likely to be in the part that we just
wrote, rather than anywhere in a huge pile of code.

2/21/24 Dr. Amjad AbuHassan 24


Incremental Development cont.

● Two techniques that help with this:


● Unit testing: when we test a module in isolation, we can be confident
that any bug we find is in that unit – or maybe in the test cases
themselves.
● Regression testing: when we are adding a new feature to a big system,
run the regression test suite as often as possible. If a test fails, the bug
is probably in the code you just changed.
2/21/24 Dr. Amjad AbuHassan 25
Modularity & Encapsulation

● We can localize bugs by better software design.


● Modularity means dividing up a system into components, or modules,
each of which can be designed, implemented, tested, reasoned about,
and reused separately from the rest of the system.
● The opposite of a modular system is a monolithic system – big and with
all of its pieces tangled up and dependent on each other.

2/21/24 Dr. Amjad AbuHassan 26


Modularity & Encapsulation cont.

● Encapsulation means building walls around a module so that the


module is responsible for its own internal behavior, and bugs in other
parts of the system can’t damage its integrity.
● One kind of encapsulation is access control, using public and private to
control the visibility and accessibility of the variables and methods.
● A public variable or method can be accessed by any code (assuming the
class containing that variable or method is also public).
2/21/24 Dr. Amjad AbuHassan 27
Modularity & Encapsulation cont.

● A private variable or method can only be accessed by code in the same


class.
● Keeping things private as possible, it limits the code that could cause bugs.
● Another kind of encapsulation comes from variable scope.
● The scope of a variable is the portion of the program text over which
that variable is defined

2/21/24 Dr. Amjad AbuHassan 28


Modularity & Encapsulation cont.
● A method parameter’s scope is the body of the method. A local
variable’s scope extends from its declaration to the next closing curly
brace.
● Keeping variable scopes as small as possible makes it much easier to
reason about where a bug might be in the program. For example,
suppose we have a loop like this:
for (i = 0; i < 100; ++i) {
...
doSomeThings();
...
}
2/21/24 Dr. Amjad AbuHassan 29
Modularity & Encapsulation cont.
● … and you’ve discovered that this loop keeps running forever – i never
reaches 100. Somewhere, somebody is changing i. But where? If i is
declared as a global variable like this:

public static int i;


...
for (i = 0; i < 100; ++i) {
...
doSomeThings();
...
}

2/21/24 Dr. Amjad AbuHassan 30


Modularity & Encapsulation cont.
● … then its scope is the entire program. It might be changed anywhere
in the program: by doSomeThings(), by some other method
that doSomeThings() calls, by a concurrent thread running some
completely different code.
● But if i is instead declared as a local variable with a narrow scope:
for (int i = 0; i < 100; ++i) {
...
doSomeThings();
...
}

2/21/24 Dr. Amjad AbuHassan 31


Modularity & Encapsulation cont.

● Minimizing the scope of variables is a powerful practice for bug


localization. Here are a few rules that are good for Java:
● Always declare a loop variable in the for-loop initializer.
● Declare a variable only when you first need it, and in the innermost curly-brace
block that you can

● Avoid global variables

2/21/24 Dr. Amjad AbuHassan 32


Minimizing the Scope of Variables
● Always declare a loop variable in the for-loop initializer. So rather
than declaring it before the loop:
int i;
for (i = 0; i < 100; ++i) {

● which makes the scope of the variable the entire rest of the outer
curly-brace block containing this code, we should do this:
for (int i = 0; i < 100; ++i) {

● which makes the scope of i limited just to the for loop.

2/21/24 Dr. Amjad AbuHassan 33


Minimizing the Scope of Variables cont.

● Declare a variable only when you first need it, and in the innermost
curly-brace block that you can. Variable scopes in Java are curly-brace
blocks, so put the variable declaration in the innermost one that
contains all the expressions that need to use the variable.
● The languages without static type declarations, like Python and
JavaScript, the scope of a variable is normally the entire function, so
we can’t restrict the scope of a variable with curly braces, alas.
2/21/24 Dr. Amjad AbuHassan 34
Minimizing the Scope of Variables cont.

● Avoid global variables. Using global variables is a very bad idea,


especially as programs get large.
● It’s better to just pass the parameter into the code that needs it, rather
than putting it in global space where it can be reassigned.

2/21/24 Dr. Amjad AbuHassan 35

You might also like