Hi, this is the second lecture about implementation. In this lecture, we're going to talk about the debugging and also configuration management. So we all know that the debugging is painful and it's going to be time consuming. So what are the things that we can do to try to get your code right and try to avoid the debugging? The first thing that we can do is try to use defensive programming. Try to avoid making mistakes and this is the topic that we have covered in the last lecture. And try to perform testing to uncover problems and also increase the confidence of your program and then eventually if bugs exist, then you have to debug and find out why a program is not functioning as intended. And they notice that testing is not the same as the debugging. For testing, we try to find the existing of a problem. For debugging we try to pinpoint the location of a problem and also try to figure out the cause of a problem. And this is the first computer bug discovered in September 9th, 1947. And yes, it's actually a bug that exists in some hardware components and eventually make the entire system not working. And that's how the term bug came from. And this is the first bug. So to defend against bugs, right? Because we all know that the debugging is going to be painful and also time consuming. These are the things that you can do. Okay, you can try to make errors impossible by design or try not to introduce defects or try to make errors immediately visible by for example using assertions and the last resort is debugging. So we can make errors impossible by design. For example, if you choose a language like Java, then you make memory overwrite bugs impossible. For example, if you're using TCP/IP, it will guarantee that all the packages that you send out and receive will not be reordered. And if you are using Java Big Integer you avoid overflow. You may also use some self-imposed id conventions. For example, if you're using some hierarchical locking outcomes, then you can avoid deadlocks or if you ban the use of recursion, then you avoid infinite recursion kind of bugs. Or if you're using immutable data structure, it will guarantee behavioral equality. And notice that if you choose to use a particular strategy, then you must maintain the discipline. That means for example, if you avoid the use of recursion, then you should avoid using recursion in your entire program. The second way to defend against bug is try not to introduce defects. So you should try to get things right the first time. Do not code before you think. Think before you code and notice that if you are making lots of easy to find bugs, you're also making hard to find bugs at the same time. And do not use the compiler as a crutch. Just because compiler can only find syntax errors but not semantic or logical errors. And it's going to be especially true when debugging is going to be hard. For example, if you have a concurrent system or you have difficult tests and instrument environment or you have to meet some deadlines and you don't have enough time, then you should try to avoid making mistakes. And simplicity is the key. At the very beginning of the semester we have said that modularity is one thing that you can do to deal with the complexity of a large software system. You try to divide your program into many small chunks that are easy to understand and then you try to deal with one chunk at a time. And you try to use abstract data types with well defined interfaces and also use defensive programming to avoid making mistakes. You should also try to write specifications for example writing specifications for all modules so that all the stakeholders, they know exactly what's going to happen within the modules. And you should also prepare well defined interface for each module that clients or users they can rely on. And eventually if bugs happened in your program, then what you should do is you should try to localize bugs to a small part of the program and you should take advantage of modularity. For example, you can start with everything and then take away pieces until bugs disappears or you can start with nothing and then add pieces bug into the program until you see the bug. And this is how you can localize bugs to a small part of your program. And also you should take advantage of modular reasoning. For example, you should trace through the program step by step and then view the intermediate results and then see what's happening within your program. And you can also use binary search to speed things up. For example, if you have a large program then you can chunk your program into two halves. If the error exists in the second half, then you can focus on the second half. And then you can further check the second half into two halves. And if the error exists in one of the halves then you can simply focus on one of the halves again. And this is how we can speed up the localization of bugs using binary search. And you should make use of assertions whenever possible. And we have covered what is assertion in the last lecture. And this is an example code fragment. And in this code fragment we try to look for the value k within an array a. And we're going to use this wild loop to look for the value k within an array a. If the value k exists in the array, then I'm going to break and we're going to move on. And what's wrong or what's not good with this code fragment? What if k does not exist in the array. Then notice that we will go into a forever loop because we have a wild true statement here and if we never find k in the array then we will never stop. And that's what's not good with this first example. And to avoid having infinite loop in this example, this is what we can do. Instead of using while true, now we have this condition while I is less than the size of the array. We're going to do the checking. Now the loop will always terminate but so what's not good about this example? What if k does not exist in the array? Now it is no longer guaranteed that k will exist in the array. If the rest of the code relies on the fact that k should exist in the array, then problems may arise later on. Now to make sure that you have achieved the condition that k should exist in a, you may use a session to double check for this condition that you want to satisfy within your program. And you can use assertions to document and also check for invariants and an error message will pop up as soon as a problem is detected within your program. So that's why I've said that whenever possible try to use assertions to double check for all the conditions that you want to satisfy within the program. So where is the bug? Notice that the bug is not where you think it. So where is the bug? Notice that the bug is not where you think it is. Ask yourself. So where is the bug? Notice that the bug is not where you think it is. Ask yourself where it cannot be and then explain why and they ignore those places. And look for stupid mistakes. For example, we reversed order of arguments that you feed into a particular operation and also misspelling of identifiers. And when you double check whether two objects, they are the same or not within your program, make sure that if you're using double equal, you're simply double checking whether the addresses of the two objects they are the same or not. Versus a.equals(b) which is going to double check whether the content of the two objects they are the same or not. And the failure to reinitialize a variable, deep copy versus shallow copy whether you're simply copying the address or whether you're copying the content of the object. And make sure that once you fix the bug and then you have the correct source code, remember to recompile everything before you link the files up and get the executable. And insert a lot of checks within your program to look for mistakes. For example, precondition checks, consistency check or bugs specific checks. And the goal is to trace through the program and then stop the program as close to the buck as possible. And should we include assertions or check in our final production code is yes, right? If you want to stop the program whenever errors or mistakes happen in your program and you don't want to take a chance that the program will do something wrong, then you should include instructions and check in your production code. But if you want a program to keep going on and maybe the bug does not have such bad consequences, then you may not include assertions and check in your final production code. Let's say if it's just a game and you want to allow the game to keep going on, then you may not include assertions and check in your final production code. So the correct answer is going to be depending on the context of your software system. So whenever you find and fix a bug, you have to keep the input that you can use to reproduce the bug as a regression test for your system and then you have to rerun all your tasks. So why is this a good idea? Because it's going to make sure that if you fix something, it's not going to affect other things. Just because it's very often that we may reintroduce old bugs while we are fixing new ones, it's going to help to populate a set of test cases that we can use for the system. And if a bug happens once, it may happen again in the future. So that's why we have to run regression tests as frequently as you can afford to. And you should automate the process and also make concise test sets with fewer unnecessary tests. Bugs happens and in the industry you're going to generate 10 bucks per 1000 lines of code. And bugs that are not immediately localized will be fined during integration tests or will be reported by the users. And once you get a report from the user, you should clarify the symptom and then find and understand the cause and then create a test and then fix it. And then finally we run all the tests. So when debugging is getting tough, try to reconsider all the assumptions. For example, did you change the OS? Is there enough room in the hard drive? You try to debug the code but not the comments. And then start documenting your system because by documenting your system, you know exactly the throw of events within your system. And also trying to get help. You may get help from your teammates or from more experienced programmers. And the last thing that you can do is to walk away because it's better to work early in the morning rather than late at night. Once you have a clear mind, you may be able to figure out where the bug is.