The Ultimate Guide to Writing Functions

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
writing great well-designed functions is hard and takes a lot of practice I'm going to cover the seven most important things you should do when writing functions over the past years I've seen a lot of Code by different people code written by some of the students following my owner course or code written by a development team in a company that's actually used in a production environment almost all of the code that I've seen applied everything that I'm going to cover today if you do apply all these things consistently it's going to completely change the way you write code from now on if you're reviewing someone else's code this is just one of the many things to look at if you want to have a more complete picture of how to do code reviews quickly while still detecting the most important issues check out my free code diagnosis Workshop it's based on my own experience reviewing code for over 20 years trying to make that process more effective and more efficient because I'm lazy you can sign up for the workshop at ironbot code diagnosis from Saints Donald useful advice practical code examples that you can apply right away to train your own diagnosis skills so ironbot codes slash diagnosis the link is also in description of this video now we have a lot of ground to cover so let's Dive Right In first step is to do one thing and do it well so in principle your functions should perform a single task but often functions also perform soft tasks so how do you delineate what a single task is one way to think about this is to think about the level of abstraction the things that a function does should in principle be on the same level of abstraction that's going to help you identify whether you should split off things or not for example suppose you have a function like this so This basically goes through items in a collection that's not valid python code by the way is simply to illustrate it goes to items in collection then if it matches the item with something that you passed as an argument it's going to update that I item and then break out of the loop if you look at the levels of abstraction in this function are actually two on the higher level the function goes through a collection of items and then perform some operation on the item on the lower level the function matches an item to something and if that matches it updates the item so this is typically a function that you could split up into one to do the navigation part and one to do the actual operation here I have another example so this is a bit more complete example we have a customer data class customer's name phone number credit card data and so on we have a validate card function that takes a customer and then determines whether the the card number is valid so it uses an algorithm called The Loon checksum for that and then if the checks on checks out then it's going to be valid and of course the expiration year and month need to be after today in order for the credit card to be valid and then we have a main function that creates a customer checks if the card is valid and then prints out the result so if I run this then this is what we're going to get it's got valid through and then prints the customer if you look at the validate card function it also has this problem of doing too many things and also here there's a problem with the abstraction level because there is very low level thing which is Computing The Loon checksum and there is a more higher level thing which is checking whether the credit card as a whole is valid and you also see that in the data that's needed so in order to check for the credit card validity all the credit card information is needed but for computing the loom checks on we don't even care if it's a credit card number of or any other type of number it doesn't matter that's not the only problem in this function though there is another problem that I'm going to talk about in a minute but let's first solve this abstraction level Difference by splitting up this function and what we can do is basically take this whole part which computes the loom checksum and move that to a separate function instead so I'm going to take this code I want to copy that I'm going to put that here and then I'm going to call this Loom Jackson and this doesn't need an entire customer this simply needs a card number and that's going to be a string and on here we're simply going to write card number like so and then what we return well that's basically this part so that's a Boolean variable that indicates whether the loom checksum is valid and now validate cards can be much simpler so this we can throw away because that's in the loom checksum function and then here we simply write the checks on customer Dot credit card number like so this already helps it makes the validate card function much smaller and we split off the Loon Jackson computation so now we have two functions and we'll check some invalid card that both operates within their own level of abstraction the next two points are sort of spin-offs of the first one so if you look at validate card you see that it actually still does too many things it changes the customer object by changing the credit card valid Flag by Computing all these things and it also Returns the result of this computation so that's actually two different things in general you have to make sure the function either retrieves some information does a query or it performs an action a command and this also called the command query separation principle that was invented by Bertram Mayer he's the one who came up with the idea of design by contract he thought of the open closed principle and he also created programming language called eiffel of course his French makes sense right and you call or programming language the main thing from your culture it's something that Dutch people don't understand we have a programming language python that was invented by dots guy but there are no pythons in the Netherlands it doesn't make any sense we should have called the programming language tulip or cheese or prostitutes that would be something anyway we're not talking about this anymore we're going to continue talking about functions and what you should do in order to improve them so how can we change validate card to separate commands from queries well that means that in principle it shouldn't store the result of this in the customer so what we can do instead is we take this part and we're simply going to return the result of the validation so this will return a Boolean just like before and it gets a customer and then here we simply store it inside the customer which is not called customer here but Alice and then here we can write Alice Dot credit card fellas like so let me run this just want to check that this still works and yes it still does the third Point that's related is that a function should only request information that it actually needs in this case a validate card requests a full customer but it doesn't need everything inside the customer class for example it doesn't need to know what the name and the phone number is of the customer it only needs to know credit card information and that's kind of annoying because now if you want to use validate card we need to pass it a full customer and it only needs credit card information so we can't really use it anywhere else if we also want to use this for I don't know suppliers or something then this is not going to work because they're not customers they're suppliers the simple fix is this is not to pass a customer but pass actual credit card information so let me remove this and then we're going to add a number which is a string we're going to add the expiry month and the expiry here and then here the variables are going to change of course so this is going to be expiry here expiring month and that's it so this makes validate card even shorter there's one issue now if you want to check whether it's valid we can't simply pass the whole customer we actually need to unpack this and and pass the credit card number and the expiry month and expiry year like so this is actually not ideal that's a way to do it that I'll show you in a minute but at least now validate card doesn't request any more information that it doesn't need and now we can use it with anything that has a credit card one extra tip if you want to make your function calls clearer you can use keyword arguments so then instead of writing this we can also write number equals this experiment equals this and expiry year equals the expiry year like so now it's a bit easier to see which argument corresponds to which parameter you can even Force the use of keyword arguments in Python and that's very simple you simply add an asterisk here in front of the function parameters and now if I remove number experiment on expired year we're going to get an error because that also allows we have to use keyword arguments now before I continue to the fourth tip if you're enjoying this video so far give the like that's going to help me reach more people on YouTube who are then going to learn how to code better which they're going to apply to building better products which means that you're going to be using better products in the long term so if you don't do it for me just do it for yourself anyway fourth tip keep the number of parameters minimal and that's an issue here with valid card obviously we have number experiments an expiry year which is a lot of I actually accidentally forgot to indicate the type here so we have function with three parameters now three parameters that's not really a huge problem or something it's still pretty easy to follow but you see that we're starting to get issues here when we're trying to call that function and every parameter we add is basically going to add to the complexity of understanding the functions and there's another issue which is that if a function has lots of parameters it also means that you run the risk that the function is going to try to do too many different things so the number of parameters you pass to function is kind of a good indication of how much the function is trying to do and whether you've focused the function enough in terms of doing a single task one thing you could do is provide default values than at least on the calling side of things it becomes a bit easy to read but sometimes it's not possible to provide sensible default value values I mean what's a sensible default value for the credit card number there isn't one because there's no default credit card number that you want to use in your webshop or something so it doesn't work here another thing that you can do is introduce some abstraction for example I could create a class called card info and that's going to be a protocol and then that's going to have a couple of properties so we have a number which is a string and we have an expiry month which isn't it and we have another property and that's the expiry year which is also an integer like so so that's the card info and then validate cards we replace this by passing a card that's of type card info and now we can use card dots number has cardstock expiry here and card dot expiry month so when you look at the customer you see that the names actually don't really match up exactly so for now I'm just going to remove these CCs in front of the credit card information I'm going to show you a better way of doing this later on and then here what we do is we also remove those like so and then now we can simply call validate card again with the customer like so and when I run this we should get exactly the same output and we do now the nice thing is because of the abstraction validate card doesn't need to know any information about the customer it simply gets an object that has a number it has an expiry month and it has an expiry year just the card info so introducing abstraction can help you separate things a bit more and making sure that the function doesn't get any more information that it needs and now we've reduced the number of parameters again to a single parameter so that's great now next to introduce an abstraction what you can also do is think a bit better about how to structure your data and that's in particular problem here with customer class because it contains a lot of variables it has name phone it has the credit card information perhaps later on we want to add address information multiple email addresses etc etc so customer potentially will become a really big class and what you can do of course is structure that by introducing classes to represent sub-objects so for example I could create here another date class gold card that contains all the card information so this information here for example we can move that to the card class like so and now also it makes sense that it's just called number expiry month and expiry year and then here we simply have a card object that's part of the customer whether the card is valid is also something you could potentially move to the card class I'll just leave it here for the moment and then instead of setting the data directly in the customer we can create a card object like so so now we have cards and we have Alice and validate card we don't pass the customer we pass the card this is sometimes also a nice solution because now we have a separate card object and we can also use that in different places so if you have a supplier we can also store a cart with it or if we have a customer that adds multiple cards to their account we can have multiple instances of the card class and then a customer can have multiple credit cards so structuring your data a bit better often helps separate things more and it can also add some flexibility to your application by the way you may have noticed I talk about parameters and arguments parameters and arguments are not the same thing parameters are part of the definition of a function so you can say that a validate card has one parameter or that loon checksum has one parameter arguments are the values that you set to these parameters so I'm calling here digits of with a single argument which is the value of card number and here I'm calling um validate guard with an argument card so that's the difference I try to do it consistently in my video sometimes I also mess it up so apologies for that but that's the that's the main difference you should remember the fifth tip is to not create and use an object in the same place if you look here this is another example we have a glass stripe payment Handler the single function handle payment there are some prices constant value and there's a function order food that gets a list of items so it creates the total the sum of the prices it locks on stuff it creates a payment Handler and then it handles the payment so already you could say that order food has too many responsibilities right because it computes the total but it also handles the payments but another thing is that it creates a payment Handler and it also actually handles the payment and that's an issue because that makes all the food harder to test because now if you want to test order food we're going to need to patch the stripe payment Handler and that's just the pain if you have to do that and also means that ordering food is not tied to a very specific payment Handler if you want to add PayPal support for example then I would have to go and change everything in this function by the way I don't want to add PayPal support because the PayPal API really sucks there's no documentation whatsoever and it's completely counter-intuitive so much preferred stripe but anyway if you want to change strive to something else now that's going to be really annoying to solve this you should use dependency injection so instead of creating the payment Handler here we should inject it as a dependency in an order of food can use it so let's add that here so we have the payment Handler which is a stripe payment Handler and then this line we remove from here and we put that in the main function and then all the food gets that as an argument payment Handler there we go actually what happens if we run this is well we're getting a total order of seventy dollars charging it using swipe and the New Order is completed if you want to do an even better job you can also introduce some abstraction so we could introduce an protocol class called a payment Handler so I'm going to use this as a basis and let's use a protocol class for that and now instead of passing a stripe payment hello we can just pass a payment Handler and if you really want to implement PayPal you can now create a class that implements the handle payment method that follows the protocol and then you can also use it to order your foods but just don't final tip we're currently ordering burger fries and drink instead of fries order a salad it's way healthier the sixth tip is to not use flag arguments here I have another example there is a row class by the way this using the new string enum from python 3.11 it's awesome then we have a class employee employee has a name and row and the number of vacation days and we have a function sorry a method called take a holiday that gets a Boolean flag whether it should be paid out or whether it should be subtracted from the vacation days and the number of days we would like to take a holiday so if payout is set through then we're going to do this otherwise we're going to do that and in the main function gradiently and then I take a holiday and I want the holiday to be paid out that's how it works if I run this then well this will get paying out a holiday and we have 20 days left when you see Boolean flag like this like payout it often means that the function is should actually be two functions one for the case where the Boolean is true and one for the case for the Boolean is false and you actually see that here very clearly it's not always this clear card but I just made the example like this so we have if payout then well if we don't have enough vacation days then we're going to raise an error and otherwise we're going to remove the number of days from the vacation day so in this case number of days isn't even used because the payout is always a fixed number of days in this example otherwise we just check under enough days if so we subtract the number of days Boolean Flags like payout are an example of a code smell code smell is something that in your code you can see it clearly and it indicates that there is an issue with your design now this whole series of videos covering all kinds of different cold smells that I think you're going to find really useful if you haven't watched those yet I've put a link to the first video in the top how do we solve this flag issue here well we just have to split up the phone into two functions so for example let me take this first part here and I'm going to add another function here that's called payout holiday we don't need the flag we don't need the number of days and we don't need the if statement like so so then this is what we get and then take a holiday well that doesn't need the flag anymore we don't need to cover this first case as we can de-adapt this and now we have our two functions and it makes a lot more sense so now take holiday I can pass a number of days let's say I want to take six days I can run this and then we're going to get this as a result and now it's much cleaner because we have two methods they each do a different thing and we don't have the Boolean flag anymore last tip is to remember that functions are objects because everything in Python is an object a function is also an object and that leads to all kinds of interesting patterns that you can use where functions can get other functions as an argument or they can even return a function as a result so in this case we have the order food example again that I showed you before and if you look at the table Handler well in this case that's just a single function so instead of using a class we could also just use a function and normally this will probably be more complex but it's just to show you the idea so let me remove this class and then let me take this and I'm going to the intent that and I'm going to call this function handle stripe handle stripe payment like so and of course there's no more self and now instead of a protocol class what we can do is use the type Alias so I can have a handle payment function which is going to be a callable which I'll import from typing and that's going to get an integer and amount and it's going to return nothing so this is the signature of the handle payment function and now order food doesn't get a payable handle object it gets a handle payment function and my main function is then also going to be simpler I don't need this line anymore but I can order food and then I can pass the handle stripe payment function like so so this results in a bit shorter code than we had before but there's more you can do with functions especially if you use the funk tools package and they have really cool feature called partial function application I've been using that all over the place on my channel over the past month I'm really really enthusiastic about it I'll try to restrain myself this time but I'll use it just a little bit to show you where you can do with it so let's say you have to order food function but you're going to call that a bunch of times in different places and you just don't want to pass the stripe payment handle function every time you have to order food so what you can do as well is simply create an order food stripe which is going to be a partial application of the order food function but we're going to pass the payment Handler and that's going to be handle strike payment so now if I want to order some food and I just want to use stripe I don't have to do this but I can simply call order food stripe like so and then I don't need to provide this argument anymore and when I run this this is going to give me exactly the same result except that I'm getting an attribute error because obviously I forgot to update the order food function so this should Simply Be payment Handler like so and let's try this one more time and now we get the result that we expect why is this 27 that's expensive oh the salad is very expensive yeah that's the price of living healthy huh here are a few quick bonus tips related to naming functions good naming is really important if a function has and in the name check that the function does one thing names like create user and store in DB or register and send welcome emails are red flags these functions should probably be split up second choosing good argument names helps with readability and allows your function names to be shorter in some cases for example publish info to library lib can be shortened to publish info to library function names should be actions that should be a verb in there and arguments should be nouns so instead of greeting say hi to use grid name make sure you use the same vocabulary everywhere if you go something in article somewhere don't call it a story or an essay somewhere else if you call a collection of Articles a library use that everywhere don't use l or lib in half of the places and Library everywhere else use the naming scheme that the Lang language prescribes in case of python it's snake case not camo case I'm looking at you by QT and finally make sure there are no typos or grammar issues in your function name it's especially frustrating if you're a grammar Nazi like myself to have to use is member or our order page and then you actually have to write that when you call the function I hope you enjoyed this video if you want to learn more about what you can do with functions in Python watch this video next where I dive deeper into the funk tools package it has completely changed the way that I write code thanks for watching and see you next week
Info
Channel: ArjanCodes
Views: 56,009
Rating: undefined out of 5
Keywords: functions writing guide, writing functions, guide to writing functions, how to write functions, how to write functions in python, functions python programming, functions python, python functions and return, python functions guide, python concepts, python programming, functions python return, best way to write function, tips for functions, tips for writing functions, function writing, function writing in python, python programming basics, web development
Id: yatgY4NpZXE
Channel Id: undefined
Length: 24min 30sec (1470 seconds)
Published: Fri Dec 02 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.