An Introduction to Software Design - With Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody and welcome back so in this tutorial what we're gonna do is talk about software design now software design is a fundamental concept in computer science in programming and creating applications or projects and something that I know a lot of people don't ever learn or they don't ever formally learn it now the purpose of this tutorial is gonna be to give you a general introduction to what software design is and show you some of the tools and tricks that you can use in Python to better write and write cleaner code so this is gonna help you a ton especially when it comes to debugging and to designing programs and I'm gonna hopefully kind of engrave in your brain the way that you should be thinking about actually writing code so this is really designed for people that have kind of moved from the beginner stages and now are now trying to jump into their own projects or trying to do some more complex things this is the next step that you really need to master and you're gonna notice that what ends up happening is you get better and better and programming is that the coding itself is not actually very difficult but what is very difficult is when you're working in large projects how you actually structure organize and write your code so what I mean by write your code is in which method do you do it it's not about can you get the task done it's about how well do you get the task done how organized is the codebase can I jump in there and read your code and be able to modify and use it very intuitively and that's hopefully what I'm gonna start to introduce to you guys here I know this has been a long introduction but stick with me for another few seconds because these next parts are important so I'm actually teamed up with Manning for this video now Manning publishing has a ton of different books on their website I'm gonna pop it up here so you guys can see and what we're gonna be doing for a few pieces of content on this channel is they've actually given me access to some great books that I would like to read from their website I'm gonna be taking some small snippets from the book and some things making some notes on them and then presenting that to you guys here now if you would like to learn software design more in-depth obviously you can purchase this book there will be an affiliate link in the description as well as a discount code but just full disclaimer I read the few chapters of this book beforehand I think it's very good I haven't read the entire thing and that's where I'm taking some of this content from and it's kind of a reference for this video alright so practices of the Python pro again link in the description alright so let's go ahead and get started with software design now what I want to do is just introduce to you a few differ pieces of code and I want to talk about why software design is important before we get into actually learning how to really do this so let's have a look at example one here I just have some code that I've written already and essentially the goal of this program is to print a list of words delimited by commas now delimited just means separated by and you can see that this is the first solution that I've come up with and there's many different ways to do this so I have my list of words hello yes goodbye last and then I say list of words 0 plus comma plus list of words 1 plus comma plus list words 2 so on so looking at this can we determine and take a second to think to yourself what might be wrong with this piece of code yes it works it accomplishes the task I'll show you that when I print this out we actually do get this list separated by commas but what could be improved well looking at this there's a few different things so when we design code the first thing that we want to think about is designing for flexibility and this is a huge beginner mistake so what happens now if I decide to add another word to my list so let's add another hello here and I print this out you see that this code does not change this means we have not designed this flexibly and when we change the problem very slightly by adding say another word to our list this whole piece of code falls apart right it works for the initial thing that we designed it for but it's not flexible enough to work for something else okay so that's one mistake with it but what's another one well the next one is what if I decide that I want to change the delimiter so let's say that the current thing that we're gonna use is commas right and that's what we have here now what happens if I change the problem and I say let's do this with periods well now you have to go and change every single one of these commas to be a period now it doesn't seem like that big of an inconvenience here but will happen quite often in larger projects when you do something like this and say hard code a and a delimiter or hard code in some variable is this may appear many many times in the program especially as it gets larger so when you now want to change say all of those periods are all of those commas to be something different you need to go through and change every single aspect which obviously is not good and it's probably gonna lead to a little bit of human error and that's what we want to avoid is any potential for when we're changing or fixing this code later to make mistakes and here this is a Hugh potential because if I forget to change one comma or one period first of all it's gonna be difficult to find that error but second of all it's just a big issue right so let's move to example two so that was the worst example I kind of have here example one and showing you that yes this works but this isn't a good design this is not how we should write this and when we think about this code we should think about how we can design this more flexibly and scalably so that if we change the problem slightly this still works okay so now we're on e 2 and this is my solution too so we can see that I've defined a list of words I have a variable here called to print and what I've done is said for I in range 4 so that's the amount of things that I have in this list here what we're gonna do is add the list to this to print and then if I is not equal 3 so if it's not the last element we will add a comma and we'll print this out so let's look at this we can see that we get the same thing but looking at this solution what is wrong here well take a second to think about it but it's kind of the same thing as the previous one yes ok so this works a little bit better for us now but not much right so if we want to change the comma at least all we need to do now is change one thing instead of three so it's a little bit of an improvement from beforehand but what about this 4 here right this again is hard coding how many elements were gonna have in this list so what happens when we add another element if we go ahead and do hello here right we still get the same thing so if we want to make this more scalable and more flexible we'd likely change this to be the Len of list of words so that way when this changes our solution can adapt and it can add all of those extra words so now what's the issue here though right we said if I does not equal 3 and we hard-coded that variable in but now that we've increased the length of our words this doesn't work anymore we can see these commas are getting all messed up so what we really should do is change this to be be the land of the list of words minus 1 and now we can see that we fixed the solution slightly now this one is a little bit complex that's ok you know it has a few more lines of code than we really need but this is showing how we can kind of get more flexible and redesign our programs that it works for different variations of the problem so let's go to e3 now ok so this one is a little bit better so what we've done is we've said lists of words equals we have or words and we do comic join list of words and that already is much better so what we've done now is we said okay so this will work for any number of words right no matter how many are in there we only have the comma one time which means if we need to change something we can change it right here that's pretty easy and if we go ahead and print this we see that this works fine so this is a better solution I would mark this as you know acceptable this is okay if I saw this that wouldn't be upset but an even better solution now moves us to e4 where what I've done is I've actually defined a delimiter constant and now instead of having comma dot join I have a delimiter dot join now the reason this is better is because to change this now all I have to do is change the constant which is denoted in all capitals which tells me this is a constant this is used throughout the program and this is where I should change it and now let's say that we change the problem even so slightly and we want to print this twice now rather than putting another comma here right like we would have in a different solution we simply write to limiter again and it will use that same delimiter from above so this is much better what is the issue here oh I haven't asked at the end ok so let's run that and we can see that this continues to work so this is hopefully starting to make you think about the way in which we write code we want to make things flexible scalable and we want them to be able to work well if we adapt or modify the problem which happens quite often when we're writing large programs we initially started with some algorithm that does something very well but then the problem may change or adapt and what you really need to think about is ok what can I do now to make sure that if this changes in the future my code still works or it only needs very minor adjustments to adapt to that and that's really what I want to start getting you guys thinking about and that's why I've done this example here of example 1 which again I just called common design mistakes ok so let's move to example 2 now and let's have a look at this so we're gonna get into some more complex examples this is just the beginning to get the kind of wheels spinning and get you guys thinking about it so I have this game here this is a number guessing game right we have a guess up here we have some while loop and what we do is we essentially get the user to guess a number between 0 and 100 if they get it correct you know it tells them how many guesses they got it in otherwise it tells them if they were under or if they were over so it's actually added some periods here just to make this is consistent okay and now let's run this so let's look at the program down here actually don't know if this is gonna work in my sub line so I'm gonna have to bring up a terminal window but if I click 45 yep so this doesn't work in here for some reason but let's just go into a terminal window and I'll run this for you so CD example to Python II 1 dot PI okay so please guess a number between 0 100 let's guess 34 says your guest was under okay let's guess 40 your guest was under I think the number is like 44 your guest was under 45 you guessed it in four guesses so that's the basic game now looking at this what do you think that we could improve on or why might this code might not be optimal right why may it not be optimal especially if we want it to say reuse this in the future well I'll start listing a few things that I see immediately if we want to change the number right what we need to do is change both instances of 45 here so if we want to change what number we need to guess and we know the number here is 45 we need to change both of these immediately not optimal this means there's room for human error we could change one and not change the other one we could break our program quite easily and if I want to change this number it's not that easy especially if this game gets more complex for me to go through this code and find where 45 exists right so number one problem there is that 45 is hard-coded we shouldn't have that we should add a constant to the top here right where we do like number and then set that ok anything else well what about this range so between 0 and 100 so we haven't actually really defined that like anywhere else in this code that this range needs to be set but you know what happens when we change our number now to be 800 this range up here now needs to change as well so that is obviously not good that means that now if we change this number to be anything lower than 0 or higher than 100 we also have to figure out that this is gonna be a problem what we're printing we need to change that so immediately again not optimal okay so now what if we want to run this guessing game 500 times but with a bunch of random numbers well we aren't really able to do that without just copying and pasting this and then making all those modifications that we made before so this really is not an optimal solution yes this works for what needed to do and in a smaller program where you're only going to use this once this would be acceptable that would be fine but let me show you a better approach to this which is e2 so now what I've actually done is I've taken this program and I've converted it into a class a class that is scalable flexible and that will work multiple times and this is the idea behind software design right when we start making things even something like this that's simple we should think about the idea that we may want to use this again and that we need to say what's gonna happen if we change this problem right if we change the range from you know zero to 100 to 400 to 700 or something like that right what do we need to do we actually have to go in here and change that value we shouldn't have to do that we should set up what I've done here which is a class which will handle that change for us and we design that at the beginning because we've thought about the problem and we've said okay well we're gonna need to potentially have something different so let's design this so it's very flexible and then it doesn't work for only the specific problem we're solving right now but it works for future problems so what's gonna just dive through this guest number here and look what's happening so what I've done as I've said we have a class called guest number and here in the initialization it takes some number and then two optional parameters for the minimum and maximum range now notice these are optional I don't need to put them in if I don't want to and that again is another thing designing for flexibility is if you know this standard case is just that they need to guess some number between 0 and 100 it's totally fine to leave your min and you're max like at some standard value right which is what we have here so we define the guesses we have the min we have the Max and then what I've actually done is I've taken some of the functionality that's in here and I've split it up into separate functions now the concept behind this is that what we want to do is we want to have things that are cohesive that work together that are combined in the same class and then inside of the same class we want to have operations or methods functions right that do of a specific task be separated and grouped out so that if something goes wrong or we need to change one aspect of the code it's very easy to do so as opposed to here where everything's in one kind of mainline program if we want to change anything we will likely have to modify the entire thing so let's just have a look at here so we have defined get guests so what get guests does is gets a guess from a user now notice back here what we did to get the guests was these few lines of code here that's totally fine but again now if we want to change this we need to change all of this code to do something else so here kind of be you know complement I guess if you want to say that is this we have a method what it does is it gets the guest from the user so it says gasps equals input please guess a number between notice this is not a hard-coded value the minimum and the maximum then what we do is we save self-taught valid guests then return into guess and guess what this is a method here and by doing this what we've actually done is made this a lot more readable for ourselves so I can read through this method and understand exactly what it does because I've split up some functionality into another method so I can read through this understand that self thought valid guess what is that doing exactly what it says determining if this is a valid guess if it is we would turn the integer for the guess otherwise we'll ask them to enter a valid number again and then we will just recursively call this same method to get that number now if we go down here to valid guess we can see that all this does is make sure that the number that we put in is actually an integer and that it's in the correct range so we can see that here we're checking that invalid guess and then if we go down to play we can see that play actually implements playing this game and then to actually create the game we have game equals guest number game dot play so hopefully you can appreciate the difference between this code here and why this might be better than this code because if we go back to the examples of things that were wrong with this code right we were saying well we have this constant value that's in that's no good we fixed that here by allowing our user to simply enter a constant value when we want to create a new guest number game right and then we had some other issues with this as well like we need to change the range potentially here that range is handled for us we already have that inside an F string we enter the minimum and maximum range when we create this game and anything else that was wrong with this well if we wanted to reuse this we couldn't but here now what I can do to use this is simply copy this change this number to say seventy-five and now we have two games of guest number and we can use this as many times as we want wherever we want by simply using two lines of code sweet so that is kind of the idea behind this and notice that I've separated things out into functions or methods and these methods do one thing and they do one thing very well a common mistake that I see quite often is what people will do is rather than kind of separating these things out into three different functions that are all good at what they do is they'll write one mega function and one mega function is something like this right you define like you know clay so you could define play and instead of writing all of these separate kind of cohesive blocks that make sense what they do is they would just take all of this code right that we have here and just paste it inside of this one method and have everything happen inside of here now again the problem with that is especially when you're writing larger programs is that when you run into any bug it's very difficult to determine where that bug was because all that code is in the same area but if you split these code this code up into different functions and methods what you can do is you can test each method individually make sure that that method is functioning and then as soon as you get to a method that's not functioning is very easy to determine what the problem is you can write cohesive small functions or methods that do one thing and do one thing very well that's kind of the standard design principle I think it's called something of separation then that's gonna help you tremendously and even just reading through this we understand we have a get guess we know what that does we have a valid number we can understand what that does and then we have play we understand what that does and we don't even need to call play if we don't want to until maybe later on in the code so we could initialize the game have it set up and ready to go and then when we need to play we could play right and we could implement this into a program where we ask the user would you like to play they say yes boom we go ahead and we start playing so the design principle is here again that I teach here really Nayland is that we need to separate things out into functions or methods that do one thing and they do one thing very well and it's quite common that what we want to do is make sure our methods or functions do not have side-effects and what I mean by side effects is that they modify some value or they make a bunch of changes or they call a bunch of other methods if that's not necessary the reason for that again is because you want these functions do one thing and do one thing very well and if they can do that it makes your life a lot easier again notice just in the play here I was able to clean this code up quite a bit because now instead of having to implement all this stuff to get the guess I can simply call the method self don't get guess this one can use the method valid number right these things work well for the one task that they can achieve and if I needed to reuse them later on to check if a number was valid for some other purpose it'd be quite easy to do that and then here we implement these few lines of code everything's very clean easy to read and this is what you define house good clean code alright so that has been it for this design now we're gonna move into example three we start talking about packages modules and namespaces in Python which we're gonna allow you to organize your code even more so I hope this is making sense I hope I'm giving you a few design principles and things to think about and again that's the idea behind this whole tutorials to get you thinking like a proper programmer get you thinking about your design and how you want to actually make things and a good idea is to always think about these things before you actually go ahead and start programming okay so what I have here is a math functions kind of module right so I've said math functions PI and the idea here is that I would like to use some of the functions there inside of here in this script test op PI so we'll talk about how we could do that in a second but let's just have a look at this module it's fairly long you know it's 64 lines and has a few different functions that seem to do different things or act on different objects so first of all we have these list functions that I've denoted here these all act on lists we have get max get men get average and get median clearly some operations just to test you know those specific things from a list then we have some hash table functions what I mean by hash table is simply dictionary and here we have get keys hash key or has key sorry max value min value right so we have all these different functions but they're all in one module or one block so right now if I want to use these what I have to do is import this math functions module right so by import math functions because I'm in the same folder here example 3 I should have access to all of these different functions so that's fine let's see how we can use that so let's print math functions dots get averaged and then here let's pass a list let's go 1 4 5 8 whatever some random stuff so let's print that we run this here and we look up we get five point six six six six six seven as our average so that's how we use that now what if we want to use one of those hash table functions so if we make a hash table here we'll say hte equals and let's just do high one Tim we get this in here six and let's call one of those hash table functions now instead of get average let's do get max I think get max is the correct one let's see or max value I believe this would this actually so max value and then in here we'll pass HT and we'll see that we get the value of six so this is fine we have these math functions all in this one module but can you think of what might be wrong with what I've done here or why this might not be the best thing so think back to that property that I talked about of cohesiveness we want things that are similar to be in similar areas right so if we go back to example two and we look at this everything that pertains to guessing a number is stored inside of this guessing number class right we have those three different methods so get guest valid number and play which are all pertaining to our you know in the same context as guessing a number we have the attributes that are involved in guessing a number stored in this class and then display method you know uses the appropriate methods it's all cohesive right whereas get guests all the things that are involved in getting a guess are in this one function or in this one what do you call it method right cohesive that's what we call it this in math functions I would not call cohesin the reason for that is because we have two different purposes for this module one for hash table functions and one for list functions yes these are all utility functions that's fine we could call it that but what I want to show you is how we can separate these out into separate modules that make this a little bit more clear so let's go ahead and let's make two new files in here and let's call the first one HT functions so for hash table function stop by and let's make our next one so new file called was Yellin list underscore functions now these are not the best names in the world but hopefully out you're getting the point here and what we're going to do is just take all of these list functions that we had and put them inside of list functions and we'll take all the hash table functions that we have and put them inside of hash table functions so here I'm going to show you how we can import these properly so don't worry about that for now but now we have two cohesive modules so we have the module HT functions which has an appropriate name that stores all of the functions related to hash tables we have a module called list functions that stores all of the functions related to lists right so these are now cohesive and if I was say looking at this program a year later and I wanted to figure out oh there's something wrong with a list function I would very easily be able to find that because I know that it should be in the module list functions again this is organizing this is kind of setting up for the future and thinking if I was gonna add more functions do I want to add them into this huge script called math functions or do I want to separate things out into separate modules that make sense so right now that's what we've done we've separated them out so to use these now instead of just importing math functions we're gonna import HT underscore functions and we'll import so just do a separate line list underscore functions as well so now if we want to use this instead of math function we have to say HT underscore functions like that and run this code and we see that we get the same answer of six so now say we only wanted the list functions we don't need to implement or import all of the hash table functions as well in fact we can just remove that line or comment it out and we just get the list functions great so this is splitting things up this is working well and now I'm just gonna show you what happens if we do something that's quite common which is named methods the same or named modules are the same that are inside both things here so are inside both modules so HT functions list functions so here notice in list functions I have get max get men get average get median and then here I have get keys has key max value min value ideally when we're creating something like this we want our naming to be consistent so that it's intuitive enough that if I had to guess what a specific function was doing or guess a function name I could do so so if I knew you know list functions was named with gap prefixes I would hope that this one would be getting on named with gap prefixes as well so that it would be easier for me to you know guess or understand what is happening so what I'm actually gonna do is go ahead and change these names to say instead of max value get max like that and here we'll say get min now we'll leave the house key because that kind of makes sense to be named what it is but get keys get Max and get min let's name them the same and notice that now we have duplicates so get Max and get min okay so I've named both of the functions in here the same thing so get min and get max is now named the same as get min and get max what I want to do is show you what happens when we actually import the specific functions from these modules so I haven't showed you yet how to do this but we can import HT functions which is gonna be the module here right but if I want to actually not have to do something like HT hundreds core functions dot then what I can do is import the specific function itself so I can actually say from HT underscore functions imports get max and I can import get min as well if I wanted to and then we'll do the same thing from list functions with those same name so we'll import get Max and get me in as well and I want to show you what happens when I actually do get Max of HT so in theory this should work right because we've imported get max we know that we have a function that works in HT functions that's called get max so let's see what happens when we call kept max on HT we run that and we actually run into a problem and it says that string and float cannot be others there's a type error right something's wrong now the reason for that is because look what module it's actually running in it's running in list functions so a lot of people probably would have guessed this but what happens is when you import methods that have the same name or it functions that have the same name from different modules the most recent import is what's used so in this case get max and get min are going to be used from list functions instead of HT functions because in the namespace is what it's called for this program there was two versions so it took the most recent version and use that so if I wanted to explicitly use the hash table one right instead of the list one I would have to do with HD functions but I can't even do that right now till I decide to go here in import HT underscore functions this is kind of weird but you need to actually import explicitly the module if you're gonna use it like this so with the dot operator and if you import these names right so get max get min you have to make sure they're not gonna be named the same as something else or you can fix this using what's called an alias so what I'm actually gonna do is say okay well I want to import it like this I want to import the function itself but I want to fix this naming collision so what I'm actually gonna do is I'm going to import get max as HT underscore get max now what this does is called an alias it simply renames this get max to HT get max in this scope so in this file scope this means now if I want to use HT get max I wanna use get max from HT functions I use HT get max so to do that I'm gonna say HT underscore get max and run this and notice that there's no issue that works fine so we can use this as to what's called alias something we can also say import H to underscore functions as say H right and now if we wanted to use something specifically from there so we say you know H dot get keys from hash table notice that we get the dictionary keys hi and Tim and that works fine okay so that's kind of the basics on importing modules and using modules and to separate things into cohesive kind of blocks but what I'm gonna do now is show you how we can actually create something called packages so notice that since list functions and HT functions are kind of similar right they kind of do a similar thing they both work for functions what we can actually do is separate these into what we call a package and use a package now what a packages is essentially just a folder full of Python modules now to make a package you have to make a new folder so we'll make a new folder first I'm gonna call this let's just call this the functions package or actually let's call it you tale because it's gonna be like utility things for our programs we'll call it you two like that and what I'm gonna do is drag in HT function so I don't think I can drag it but I think I can move it in to example three util /hd functions okay and let's move this one in as well so move into if I can get this off my screen it's just being annoying all right so let's move this into util like that so now they're both inside of you till now all I have to do if actually want to make this folder what's called a package which means I can import the package so the entire folder rather than the specific modules is I have to put a new file in here and I have to call it an it so underscore underscore net underscore underscore X dot PI now what that does is pretty much just tell Python that hey we're initializing this package this is a Python package which means rather than having to import HT functions and list functions separately I can actually import them both at the same time by simply importing this package so I'll show you how that works now so X we don't need math functions anymore but if we go to test op PI rather than having to do something like this so from HT functions from list functions what I can actually do is import and in this case I can import util now importing util will actually let me have access to this HT functions and this list functions so now essentially what I've done is I've created a folder that is cohesive and the reason it's cohesive is because it contains two modules right these two modules are similar they both act on functions and then I have two modules inside of this folder and these modules right have functions inside of them that are cohesive that are similar they both act on hash tables or they act on lists right and then these functions themselves do one thing and do one thing very well they're not doing this huge amount of things so my program or my whatever you want to call it project is cohesive which is what we wanted to get to we wanted to organize things so that they make sense where they are so we have that now so how do I use util so to import a package you can simply import it like this but there is some kind of tricky things in terms of how this works so since this is a package you can just import the name but if I want to actually use the modules that are inside of it what I have to do is say something like this so from util and you've probably actually seen this before so from util import and then we'll do the name of those files we want so we'll import HT functions and we'll import in this case the list underscore functions now you might say well what was the point of that that seems like if anything you just made it more complicated from before well the point of that was to organize things well in my program so that I now have functions that are kind of grouped together and modules that are grouped together inside of the same folder so now if I wanted to say if I had a larger project with a bunch of files it'd be very easy for me to find where my utility functions are because they're in the util package and then inside that package I have modules that are organized correctly which means I can find what I need to find so we'll import HD functions and lay import lists functions and now we can use them like we would before so I can say HD functions dot get keys and use that that works totally fine nothing wrong with that and if I wanted to Elias these what I could do is simply say import HD functions as HT and then do the same import here so from util import list functions as LS right and then if I wanted to use a list function I could say LS dot get max right and I gets to LX get max of 1 4 5 like that and my HT max will still work so I can say print HT dot gets underscore max of and here we'll just put actually the hash table like that and what is the issue here oh I've called this HT 1 HT is what that is called so let's just make this H instead of HT so that was an issue there and do that let me see that this works fine so that has been packages modules and the hierarchy kind of goes package module and then classes methods functions and then you have everything else right which just attributes are regular lines of code so this is the way that you can organize and separate your code out so that it's easier to find in to tweak later and remember every time you're programming what you want to be thinking about is okay how can I make this as simple for myself as possible how could I make it so that when I look back at this in two years if I need to change a file I can do that because everything's organized and clear and hopefully this kind of video I know there's a lot of repetitive stuff in it really drilled that into your head and I know that this probably was a lot for for some people there was a lot of things shown here but the idea is to make things cohesive and that's the introduction to software design there's a lot more to talk about and I am planning on doing some more videos in this domain if you want to read up more on this again there is that book in the description that has so much more detail than what I've provided here this was just to give you some examples get you thinking about how software design actually works why software design is important and showing you some of the tools like creating packages and modules in Python that allow you to separate out your code and again to make that package you simply make a folder called an it dot pi this folders in the same directory as my math functions and my test dot pi which means that from either of these files I can import that entire package and use anything that's inside of it obviously there's a lot more to talk about with packages but I think that's where I'm gonna leave it here and hopefully this gave you a good introduction to software design so if you have any questions do leave them down below let me know any series you'd like to see in the future and if you enjoyed the video of course leave a like subscribe to the channel and I will see you guys in another YouTube video
Info
Channel: Tech With Tim
Views: 348,571
Rating: undefined out of 5
Keywords: tech with tim, software design patterns, software design principles, software design python, how to design a software using python, python software design, python software design patterns, software design, design patterns python, python
Id: -njsRb8Tn70
Channel Id: undefined
Length: 34min 4sec (2044 seconds)
Published: Thu Apr 02 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.