Arjan, you are soo good at what you‘re doing. Your voice is calm and relaxed. And you are able to transport your knowledge to your audience in the same manner, calm and relaxed. Learning new stuff suddenly becomes like a therapy. No more stress, no more struggle. Thank you very much. Please keep going.
Good stuff, thanks Arjan. I have a feeling you only scratched the surface on error handling. In the example you gave, the code became neater but much more vulnerable. Now if one of your functions raises an exception, the whole code breaks. Would you plz explain the full cycle of your error handling logic?
Yes I agree! I have loads of code that handles cases where a function that returns None, but it's in a long running automation script, that needs to continue and not fail - would be really helpful to see how to handle the exceptions works as per your suggestion and how this differs to retuning None, especially when it's a few layers deep! Thank you @arjancodes!
When refactoring, I like a safety net. Before refactoring, run unit tests with coverage to verify the area I’m refactoring has unit tests, adding any that are missing. Then the process is test, refactor, test. Just want to know I’m not adding any errors…
I get that you are trying a more functional approach, but that invoice is beeeeeeegging to become an object. When you keep passing the same arguments to a lot of functions, this is a sign there's an object structure hidden in your code. You create a factory classmethod "create_from_stripe", a "send" method, a "book" method, and badabim-badabum, you're golden. That's actually the remaining tip Uncle Bob gives about clean code: keep the number function arguments to a minimum. The strategies he suggests are to rely more on data structures and on objects, which is, in my view, pretty much the same strategy.
Well spotted! I’m currently working on an SDK for the accounting system that works exactly as you suggest, with classes for the main API objects. My idea is to use Pydantic so I can also add validation and have easy handling of JSON. When I have a first version ready, I’ll do another video about that.
This is one of your best videos. You use your own production code, you explain your thought process clearly, and also hint at further improvements. This could be expanded into a whole course - especially the last tip.
You accidentally divided the application fee by 100 twice. Both in the InvoiceData (which is where it was originally) and in the get_application_fee function. I did like the changes you did in general. It makes a lot of sense =)
Very interesting, I would like to see a video about how to read code and analyze it in order to catch all these mistakes. In this video you show the problem and tthe solution but now how do you catch the problem
Something that's never discussed in videos like this (although this is a good video, not throwing shade at all, Arjan) is the overhead of learning all these new APIs and until you really grok what they're doing, what problems they're solving and how they work -- there really is a wall of ignorance that's hard to overcome. After all, you don't know what you dont know. All I know is , "Use this to solve your problem" but beyond that -- it really does seem like an endless (turtles all the way down) problem of "How much of this new thing do I have to learn to solve my problem?" Is this basic, fundamental knowledge thst. I should know, or concrete implementation minutia that doesnt matter beyond this API. All of that is where I find myself way more often than not knowing how to use clear variable names and short functions. >_< Edit: Hey, you just mentioned it! Becoming a domain expert without becoming a domain expert. That's the rub right there.
Thank you Arjan for the videos you upload, they're very helpful and have a great quality, both in teaching and technical quality, I have learned a lot from you.
By the way, VSCode's Python extension now allows you to "extract function" after selecting a block of code, although it doesn't seem to add type hints along with it.
Very nice video, thanks! It reminds me of older videos like the ones about dependency I jextions or design patterns:) Also very nice to talk about domain knowledge. From my experience it is that who makes the difference at some point, even if you're technically good. It's only by knowing what you're working on and your clients' needs that you'll be able to make good decisions
Much better now, but it's still far from ideal. The last point about domain knowledge is crucial because otherwise you can hardly figure out the right data structures and function signatures. It's all about using expressive language and increasing information content while keeping the coupling low. I'd probably remove most of your comments and express them by function names. But that's just my personal taste. Also a huge issue with the code is what happens when a transaction completes partially. Maybe you wanna roll back the payment entirely when it's not persisted in your accounting system, etc. You should make this more explicit. But in any case, it's also important to not overengineer. When an occasional manual rollback is cheaper than programming bulletproof error handling, then keep the code as is. After all, the code is supposed to save money by automating a manual process.
as always, thanks alot Arjan! I am still wondering how you would handle errors in each function. When there is a need of chaining a long list of task you have to do each method must raise some kinde of custom processing exception which should be handled in the main, or what is your approach? I´ve looked at taskflow and pypeln which should offer some kind of task processing framework. Maybe that would be nice for a next video to compare some workflow libs. What are you using?
You raise exceptions when errors happen and handle them when you can actually do something useful with them. For example, sometimes the only thing you can do is print an error message to the user asking them to try again or something, so you let the exception bubble up all the way to the UI layer and handle it there. Or maybe you want to log the exception somewhere in between so you catch it, log it, and raise it again. You don't need to create your own custom exception types for everything, but they're useful when you have to do different things depending on the error, and it's much cleaner when you can write multiple except clauses that catch different exception types.
The comment above is pretty accurate. I'd also add that exception handling for most things is typically neater if done as closest to top level as possible. 15:11 Here, he raises value errors in his unit function getApplicatiomFee(). You could handle this in the direct calling function constructInvoiceDataFromStripe() or that function's calling function, main(). Since main() is as top level it can get, has pretty simplified code, and is where all the main logic starts/branches out, that's where I'd put the handling.
I follow a ‘let it burn’ approach. Especially with these types of integrations, it’s often hard to predict the kinds of errors you’ll get, and on top of that you can’t do much about it at the moment you get the error. So what we do is let it crash and log the error. This integration has been running for almost 2 years now and I’ve been able to gradually increase robustness of the code to the point where it now rarely crashes.
@@ArjanCodes "So what we do is let it crash and log the error." I cannot understand how this can work for a production application. The worst case scenario should be to tell the user that "There was an unknown error and we cannot do what you wanted - but we will fix as soon as possible." Ensure the error trace goes to a log, that your logs are monitored and then you can dig into the underlying cause & patch as soon as possible.
@Derekhohls - there is no direct customer interaction in this automation since this purely a backend business operation to map a sale to a booking in our accounting system. Therefore, we employ the most cost-effective mechanism of handling errors, which is simply crashing and reporting the error instead of spending a lot of time writing code to prevent all possible errors. It may sound counterintuitive but handling occasional errors manually is the cheapest option for us. If we notice an error occurs more often than we like, or we think there’s a really simple fix, we’ll implement that.
I feel like most of the comments are useless. Stuff like #retrieve the customer from stripe and # determine the application fee. Or # retrieve payment intents. None of these add to the code that's below it. Also, i think compute_timestamp immediately violates naming descriptively. compute_cutoff_timestamp would be better imo. Those are the two things that i noticed most.
speaking of reducing cognitive load, how do you feel about prefixing names like d_ for class data members (not so pretty in the presence of python dataclasses of course - more a relic of languages with explicit getters/setters ;-), g_ for the dreaded globals, l_ for local variables, p_ for passed parameters, etc.?
I don’t use docstrings all that much. I prefer to spend some extra time making sure function names and code structure is self-explanatory. The main way I use comments is to clarify the flow of code where that’s not immediately obvious. I also use it to formulate what I need to do while writing the code, and then often leave the comments in so it’s easier for me to understand later why I setup things in a certain way.
@@ArjanCodes Docstrings can be useful for the functions that are used in multiple places by your code (or if if there is code that is going to be used by external apps). That way the IDE can quickly provide info on their use and purpose. For deeply buried code; or code only cross-referenced inside a module, good inline comments are probably more useful.
What’s your take on uncle Bobs’s opinion that comments should be avoided at all costs? You got some more or less redundant comments there, and others that could easily be replaced for example by assigning an extra variable with a meaningful name
Why is there no try - except block to actually handle the errors? If your "error handling" just crashes the program, then it's not error handling, thats just a quit() if there's a problem...
Join my free Code Diagnosis workshop that teaches you a 3-factor framework using Python examples from production code ➡ arjan.codes/diagnosis
Arjan, you are soo good at what you‘re doing. Your voice is calm and relaxed. And you are able to transport your knowledge to your audience in the same manner, calm and relaxed. Learning new stuff suddenly becomes like a therapy. No more stress, no more struggle. Thank you very much. Please keep going.
That is so kind! I’m happy that you find the content helpful.
Here is a man who appreciated the value of functional programming through experience.
Good stuff, thanks Arjan. I have a feeling you only scratched the surface on error handling. In the example you gave, the code became neater but much more vulnerable. Now if one of your functions raises an exception, the whole code breaks. Would you plz explain the full cycle of your error handling logic?
Good suggestion for a follow-up video, thank you!
Yes I agree! I have loads of code that handles cases where a function that returns None, but it's in a long running automation script, that needs to continue and not fail - would be really helpful to see how to handle the exceptions works as per your suggestion and how this differs to retuning None, especially when it's a few layers deep! Thank you @arjancodes!
When refactoring, I like a safety net. Before refactoring, run unit tests with coverage to verify the area I’m refactoring has unit tests, adding any that are missing. Then the process is test, refactor, test. Just want to know I’m not adding any errors…
I get that you are trying a more functional approach, but that invoice is beeeeeeegging to become an object. When you keep passing the same arguments to a lot of functions, this is a sign there's an object structure hidden in your code. You create a factory classmethod "create_from_stripe", a "send" method, a "book" method, and badabim-badabum, you're golden.
That's actually the remaining tip Uncle Bob gives about clean code: keep the number function arguments to a minimum. The strategies he suggests are to rely more on data structures and on objects, which is, in my view, pretty much the same strategy.
Well spotted! I’m currently working on an SDK for the accounting system that works exactly as you suggest, with classes for the main API objects. My idea is to use Pydantic so I can also add validation and have easy handling of JSON. When I have a first version ready, I’ll do another video about that.
Raise errors is great! But what about handling?
This is one of your best videos. You use your own production code, you explain your thought process clearly, and also hint at further improvements.
This could be expanded into a whole course - especially the last tip.
Glad you enjoyed it!
You accidentally divided the application fee by 100 twice. Both in the InvoiceData (which is where it was originally) and in the get_application_fee function.
I did like the changes you did in general. It makes a lot of sense =)
Very interesting, I would like to see a video about how to read code and analyze it in order to catch all these mistakes. In this video you show the problem and tthe solution but now how do you catch the problem
Something that's never discussed in videos like this (although this is a good video, not throwing shade at all, Arjan) is the overhead of learning all these new APIs and until you really grok what they're doing, what problems they're solving and how they work -- there really is a wall of ignorance that's hard to overcome. After all, you don't know what you dont know. All I know is , "Use this to solve your problem" but beyond that -- it really does seem like an endless (turtles all the way down) problem of "How much of this new thing do I have to learn to solve my problem?" Is this basic, fundamental knowledge thst. I should know, or concrete implementation minutia that doesnt matter beyond this API. All of that is where I find myself way more often than not knowing how to use clear variable names and short functions. >_<
Edit: Hey, you just mentioned it! Becoming a domain expert without becoming a domain expert. That's the rub right there.
Thank you Arjan for the videos you upload, they're very helpful and have a great quality, both in teaching and technical quality, I have learned a lot from you.
Glad to hear that Jonito!
Been watching for a while and this is the best video yet. I’m going to link to it from our coding standards document, as it provides good examples.
Glad it was helpful!
By the way, VSCode's Python extension now allows you to "extract function" after selecting a block of code, although it doesn't seem to add type hints along with it.
Very nice video, thanks! It reminds me of older videos like the ones about dependency I jextions or design patterns:)
Also very nice to talk about domain knowledge. From my experience it is that who makes the difference at some point, even if you're technically good. It's only by knowing what you're working on and your clients' needs that you'll be able to make good decisions
I'm happy to hear you liked it! :)
Much better now, but it's still far from ideal. The last point about domain knowledge is crucial because otherwise you can hardly figure out the right data structures and function signatures. It's all about using expressive language and increasing information content while keeping the coupling low.
I'd probably remove most of your comments and express them by function names. But that's just my personal taste.
Also a huge issue with the code is what happens when a transaction completes partially. Maybe you wanna roll back the payment entirely when it's not persisted in your accounting system, etc. You should make this more explicit.
But in any case, it's also important to not overengineer. When an occasional manual rollback is cheaper than programming bulletproof error handling, then keep the code as is. After all, the code is supposed to save money by automating a manual process.
as always, thanks alot Arjan! I am still wondering how you would handle errors in each function. When there is a need of chaining a long list of task you have to do each method must raise some kinde of custom processing exception which should be handled in the main, or what is your approach? I´ve looked at taskflow and pypeln which should offer some kind of task processing framework. Maybe that would be nice for a next video to compare some workflow libs. What are you using?
You raise exceptions when errors happen and handle them when you can actually do something useful with them. For example, sometimes the only thing you can do is print an error message to the user asking them to try again or something, so you let the exception bubble up all the way to the UI layer and handle it there. Or maybe you want to log the exception somewhere in between so you catch it, log it, and raise it again.
You don't need to create your own custom exception types for everything, but they're useful when you have to do different things depending on the error, and it's much cleaner when you can write multiple except clauses that catch different exception types.
The comment above is pretty accurate. I'd also add that exception handling for most things is typically neater if done as closest to top level as possible. 15:11 Here, he raises value errors in his unit function getApplicatiomFee(). You could handle this in the direct calling function constructInvoiceDataFromStripe() or that function's calling function, main(). Since main() is as top level it can get, has pretty simplified code, and is where all the main logic starts/branches out, that's where I'd put the handling.
I follow a ‘let it burn’ approach. Especially with these types of integrations, it’s often hard to predict the kinds of errors you’ll get, and on top of that you can’t do much about it at the moment you get the error. So what we do is let it crash and log the error. This integration has been running for almost 2 years now and I’ve been able to gradually increase robustness of the code to the point where it now rarely crashes.
@@ArjanCodes "So what we do is let it crash and log the error." I cannot understand how this can work for a production application. The worst case scenario should be to tell the user that "There was an unknown error and we cannot do what you wanted - but we will fix as soon as possible." Ensure the error trace goes to a log, that your logs are monitored and then you can dig into the underlying cause & patch as soon as possible.
@Derekhohls - there is no direct customer interaction in this automation since this purely a backend business operation to map a sale to a booking in our accounting system. Therefore, we employ the most cost-effective mechanism of handling errors, which is simply crashing and reporting the error instead of spending a lot of time writing code to prevent all possible errors. It may sound counterintuitive but handling occasional errors manually is the cheapest option for us. If we notice an error occurs more often than we like, or we think there’s a really simple fix, we’ll implement that.
What about error handling? As in how you deal with them in after you have raised them?
15:40 but you make no exception logic, you simple ignore your new exceptions.
23:40 but your function does now not do what it names stands for.
Thanks for video
I feel like most of the comments are useless. Stuff like #retrieve the customer from stripe and # determine the application fee. Or # retrieve payment intents. None of these add to the code that's below it. Also, i think compute_timestamp immediately violates naming descriptively. compute_cutoff_timestamp would be better imo. Those are the two things that i noticed most.
speaking of reducing cognitive load, how do you feel about prefixing names like d_ for class data members (not so pretty in the presence of python dataclasses of course - more a relic of languages with explicit getters/setters ;-), g_ for the dreaded globals, l_ for local variables, p_ for passed parameters, etc.?
Please don't do this. Let your IDE help you, or your colleagues will definitely curse you
Is there a reason you don't have doc strings for your functions (in some cases they seem to be comments...)?
I don’t use docstrings all that much. I prefer to spend some extra time making sure function names and code structure is self-explanatory. The main way I use comments is to clarify the flow of code where that’s not immediately obvious. I also use it to formulate what I need to do while writing the code, and then often leave the comments in so it’s easier for me to understand later why I setup things in a certain way.
@@ArjanCodes Docstrings can be useful for the functions that are used in multiple places by your code (or if if there is code that is going to be used by external apps). That way the IDE can quickly provide info on their use and purpose. For deeply buried code; or code only cross-referenced inside a module, good inline comments are probably more useful.
Do you use Rye?
What’s your take on uncle Bobs’s opinion that comments should be avoided at all costs? You got some more or less redundant comments there, and others that could easily be replaced for example by assigning an extra variable with a meaningful name
Why is there no try - except block to actually handle the errors?
If your "error handling" just crashes the program, then it's not error handling, thats just a quit() if there's a problem...
"Do. Or do not. There is no try." 😉
There are other ways to deal with errors. I'm working a follow-up video to share what I typically do.
I am still waiting for game making series :)
yeah.. I def have too many unnecessary return Nones in my code