My name is Ashish Mahabal and we'll be continuing with the best programming practices module. So one things that we'll see this time is what it is that one should do before the project starts. Of course during the project you'll be doing lots and lots of things, but equally important are various things you must do before the project starts, and that includes digging for requirements. Be thorough about trying to find out what it is that will required in the project. Then document those requirements as much as possible. Make a use case diagram. Who is it who will be using that project? Who will be running it? And what are the conditions in which the project will be run, in which the program will be run? Because once you know that, once you have those diagrams, and it will give you a better idea about as to how to go about making that particular program or project. Similarly, maintain a glossary. Many times people who write the programs do not have enough domain knowledge or the domain in which the programs are being written. So it is very important to get to know which are the terms in the domain that you'll be needing, which are the computing terms you'll be needing. And once you have a glossary of that, how they're connected, how they work with each other, that is important to know. Similarly, I cannot or emphasize the importance of documentation. Do as much documentation as possible at every stage. This not only includes comments that go into the programs, but also meta comments that can go into configuration file and external readme files, et cetera. So document, document, document as much as possible. One tradeoff one has between maintenance and development is that clearly, if you do quick development or if the development seems easy, very likely the program will not be maintainable. Maybe some short cuts have been used, et cetera. So be thorough in your development process. As a result of that you will find that the maintenance becomes that much easier. You, yourself will very likely be maintaining the programs or one of your teammates will be. But in either case, if you want to save everyone a grief, some grief, then maybe the development process should be thorough, deliberate and in appropriate short steps. Because the projects live much more longer than one expects them initially. And for that, what you may have to do is to adopt a more complex language. By complex language, I do not mean complicated language, but simply complex enough that incorporates all the structure that is needed in the specific project. And that will automatically make it readable, it'll show the relationships between different parts of the program and you'll be better off. So again, what should be involved when you start a project and take various steps in order to complete it, is check the requirements of the specific module, specific part that you are working on. Go ahead, do a design related to that, implement that design, and then integrate that design into what has existed until then. And of course one critical step at that point is validation. You should validate the work that has been done. This also applies is to when you take libraries or programs written by others. So, when you are basing your work on that already done by others, you should validate various items within that. For instance, even things like numbers and characters, what is being called a number, is it really a number? What happens if you give a character's input or vice versa? If something that's being called a character, what happens if you give a number as an input? Similarly, make sure that all the constraints are correctly met. Here you see a diagram of the sky where a particular parameter called declination goes from minus 90 to 90. And if it appears in one of your programs and someone goes in as input number 100, what is it that's going to happen? Then check consistency. Are you seeing contradictory things about the same point at different places in the program? If that is so, then better you have to resolve that. So, whenever you take someone elses programs make sure you do that. But then you should not trust yourself either. All of those above things you should do to your own program. Whenever you have written a piece of code, make sure you validate that with consistency, for consistency, and for constraints as well. But despite all this, what is going to happen is there will be cases when things will go wrong. Some parameters will not be correctly input. And at, on such an occasion what you should make sure is that your programs crash early rather than late. For instance, if a particular subroutine needs four arguments and you go through a lot of competition and then try to validate whether the fourth argument was in the correct form. And you find that actually you did not like a little bit over there. Then crashing at that point is not a good idea, because already you have spent some time doing computation that involves a foot on the computer time spent during computation. So it may have been much better if you have looked for all the arguments that are being given, whether all of them are consistent, and they follow the constraints that you wanted them to be. Then if you have to crash, if you are to come out of the program, you better should do it in a way that does really trash other than crash. By trash, what I mean here is leaving a lot of messages which either do not make sense, or do not provide important or useful information to the person who is trying to run the program. So perl has a different phase of coming out of a program. If the die command is used, the program simply dies. It doesn't do anything else. It just comes out and doesn't tell you anything that is reasonably important or useful. But instead of that, you could use a sub croak which then blames the caller. It tells you which subroutine called that particular line which caused the problem to stop, and the program to stall. Or you could use something like confess, which gives even more details. It can go into a very fairly long trace, telling you this routine was called here, that routine was called there, and where is it that the problem really originated. So related to that is try and catch, where you can say that the program should attempt to do something, and if it fails to do that, then one other possibility is that it could possible steps that it could be taking. And that is where exceptions can be raised. If an exception has to be raised, you are to ask, why did that situation come about? Was it a valid input or not? And then whether you knew such a situation will arise, and then take appropriate cases. So let's see a specific example of the try/except here. You'll see in the screen two parts. The top part is one way you should be using try/except, and the second part is where it's too broad, so you should not be using try/except there. So in the top part you see that we are using a dictionary and five columns called collection. It has various keys. And we are saying take apart low key and find out what its value is. And we are asking the program to try getting the value of that key from the dictionary. And then we say that if it doesn't find that, then it should return a function called key not found, when this key is an argument. And if it is successful in getting the value, it should simple handle that value using another function called handle value. So, this would go through quite find, if it doesn't find the key it will tell you that the key was not found. If it was found, then it will do an appropriate thing with the value. But on the other hand, you can try to make the try/except too broad, as is shown in the second case. Where you are combining the top two things by saying a return handle value off the value that is returned by the collection for that particular key. Clearly, if it works, it works and you get the correct answer, but if it does not work, then your exception says, a key not found here. Well, that may be part of the problem, but it is also possible that the key self was found and error was in the handle value function. And that is not clear at all from the exception that is being indicated here. So you have to be careful that you do not have too broad a case of try/accept. Then forget about finding or eliminating bugs and so on. You should try not, not to optimize the code, but try to benchmark it. Let us try to find out where more time is being spent. What is a better way of using a particular structure? With the optimizing structure, you can measure them rather than optimizing them. Where, you find out what is the size of each element of the structure. And if you're going to have a very big list involving that structure, how many bytes it's going to take. And if there is that appropriate form, then an important thing to do is to cache data when you can. And this is very important especially when, when you use this, either recursive functions or list which involved a large number of competitions. And possibly, some of the competitions are going to be called in again and again, either within the same competition or by different users in the same program. So, we'll see an example of that next. The other thing to do is also benchmarking various strategies for caching, because even for caching there can be various algorithms. And for your particular situation, one algorithm may be better than another one. So, it's a good idea to look at different ones there. The same thing about optimization goes for applications. You don't have to try to optimize an entire application, but you should provide it. That is, see which part of the program takes up most of the time, takes up most of the resources and try to reduce that. That way you'll comparing apples to apples rather than trying to say oh this, the length of the subroutine looks longer and that is why maybe I should try reuse the, the number of lines whether than the time that is being spent on that particular one. So, here is the example of memorization. It's a simple program in some simple subroutine, a fact, a factorial. And a factorial of a positive integer is of course multiplication of that number with all smaller positive integers. So, factorial of 4, as you know is 4 times 3 times 2 times 1, which is 24. Or factorial 5 is 5 times the factorial of 4, 120. So here, what's being done, is that we start with an empty dictionary, Factorial_memo. It doesn't have anything to start with. And then when the first factorial call comes as is shown in the last line of this program, call factorial(10), it'll find that factorial(10) is missing in the dictionary. So it'll try to calculate factorial 10, for which it needs to calculate factorial nine, and then factorial eight, and factorial seven, and so on. But the moment any of those is now calculated, that gets put in that particular dictionary. So, whenever that call comes again, you'll already have an automatic answer, and you can just return that. And so, after factorial k has been calculated once, if you call factorial five, the answer will be ready. You call factorial seven, the answer will be ready. Only if you call factorial give it a higher number, say factorial 13, it won't be ready. But it can then get factorial 13 it will be 13 times 12 times 11 times factorial 10, which already you have and then you'll have all numbers, all factorials up to 13 in your program. So this is how you'll go ahead and do the memorization and will help you caching your earlier results. Similarly, here's how profiling can be used of the Python module cProfile can be used. And here we are seeing how cProfile will work with a particular regular expression of compiling hello world. And just that single call to cProfile indicates that there are 238 function calls including 233 primitive calls. And of course this single statement. So it practically runs in 0 seconds. And all the different calls here show that they ran in 0 seconds. But for a real example of what would happen, is that it will show you which of the internal statement, either primitive or one of your function calls is taking more time. And then that is where you can try to reduce the amount of time. So in general, you should try to benchmark things, and there are utilities available for that. The Benchmarking game allows you to compare different installations, different programming languages or all. But if you go to a specific programming language like Python, you have routines you have website and there are websites available to just compare radius, modules within five [INAUDIBLE] definitely take a look at that. Then let's look at some different aspects of programming. What are the necessary ingredients of a good program. A program should be robust, it should be efficient, and it should be maintainable. So, what does one mean by a program being robust? That it should be able to handle different cases easily, like h cases in particular, those can be the most troublesome ones. Is the first case handled correctly? Is the, does the list start with zero or one? And whether that has been handled correctly. Or is the last case that can come about handled correctly? Similarly, whether there are appropriate tests for different kinds of errors. How is the error in handling itself done, are exceptions correctly. So if you build in all these things properly, then you're going to have fairly robust program. And one can check for all loaded cases in this fashion here. What we have is a function simply called square, and it has a single statement which is X times X, where X is the input. But then we run something called a doctest within the comments that are given up there. So, we have radius examples, it can take two as input and then it should return four. And you have minus two as input and it should return four. You can even input a complex number and get an appropriate response from that. So you should be making sure that if you have a whole load of cases they are correctly tested, and they in fact give what you want to come out of them. Similarly, with efficiency of course a chain as we all know is weakest when you find the weak point and that. So you should try to ensure that you are working with strengths of whatever language you are using, whatever model you're using. Or whatever combination of those you may be using, and that you should avoid weaknesses. A specific example can be especially with compatibility, where Python 2.7 and Python 3, many people have not made a switch from Python 2.7 to Python 3, because something as fundamental as the print statement has changed. And obviously, people don't want to go back and change all their print statement. So this is a case where microcompatility has not been built in, for whatever reasons. But one has to be aware of that. And when one is writing programs, one has to make sure that the trade offs are taken into consideration correctly. Then coming to maintainability, just remember that more time will be spent in maintaining codes then it took to writing them. And that can be a painful thing if the code is not well written or if it is not maintainable. Because when time goes by, you don't understand your own code, forget about the code that other have written, others have written. So make sure you have amply commented the code. Then, in most cases, you, yourself will maintain it. So help yourself by making your code maintainabled right from the word, go. And that can be helped by using consistent practices, as we talked earlier, a little bit about braces and brackets, and spaces, and line lengths and tabs. And line lengths, and all of those. So make sure that you have consistent practices. If you are managing a big team, make sure that they follow consistent practices as well. Next time we'll be looking at design by contract and about comments, and arguments, and some related aspects.