I once nested 20 layers deep. Much like inception, time reading the code becomes exponentially longer the deeper you get... It's a miracle I'm even here today.
don't worry, people on the internet are always nitpicking like that. Your editing style and video structure definatly make up for minor mistakes like that haha.
@@beqa2758 I don't allow conditionals without braces in my projects. I don't care if the braces are on the same line as their contents, but you _must_ have braces. The reason is very literally almost every time I have seen if or else statements without braces, I have seen another person break them unintentionally.
I think this is actually crucial to teach to beginners. Holding exceptions and conditions in your head makes a code base really hard for others to read.
Imma be honest. At least in my situation, I fail to see any reason why any of this matters. I'm a solo dev, nobody else is working on this code but me so nobody else is gonna be seeing it but me. And I'm looking at this "un nested" code and it's honestly HARDER for me to read than nested code. The unnested code blurs together, while nested code creates immediate, obvious separations.
Just wanted to point out a common error when inverting logic. At 2:20, the inversion technique is demonstrated. Before, the "happy" case was (top > bottom), which made the "sad" case (top = bottom). This creates a classic "off by one" error. In the case where top and bottom are equal, the former returned 0 and the latter returns one iteration of the sum equal to the result of filterNumber(). I know that isn't really the point of the example, but you do need to be careful when using logic inversion.
True, a lot of people get inverting boolean logic wrong. Another one I saw recently was inverting (a && !b) to (!a && b). Instead, first invert the whole expression and then apply De'Morgan's law: !(a && !b) => (!a || b).
Update: He fixed it. Nice! :-) I'd suggest using a lighter background when highlighting code and darkening the rest instead of blurring the rest, so that it's faster to find the line.
I just want to applaud the visual design of this video, especially the code. The blurring, previewing important definitions, uncluttered diagrams, use of color, typewriting, introducing motion to draw/hold attention. It really communicates the intent of the code better than I've seen anywhere else.
Seconded. The whole presentation is incredibly elegant. It doesn't seem like much but managed to deliver on a lot of aspects. Subtle but refined, directing the viewer's attention while not distracting and letting them think on what is being said.
@@LinkEX I do have to though sometimes when the code was blurred it took me a while to find it. idk if thats just be being slow or perhaps an optimization could be made there. Maybe a little more contrast in the part thats not blurred, or a small arrow would help
Well yes, but in modern frameworks you can easily create components. In Angular, you can even create templates in the same html file - which is similar to extracting a function and also saves you all the boilerplate to create a new component. Probably you cannot adhere to the 3 deep rule everywhere, but it's not impossible.
Out of control nesting is definitely bad but sometimes when the guts of the code are extracted into smaller and smaller functions it also makes following the flow of the logic hard as well. You still have to try and keep track of what the parent of the parent is doing. At that point you are still in a nesting scenario, you’ve just shifted into smaller chunks of separated code. It’s a fine balance. Guard clauses help to get rid of unnecessary if/else conditions.
@@bufdud4 nested code is often untestable because you don't have control over it. When you have code in small chunks, then it becomes easier to test individual components. Test driven design is great because it forces you to design testable code in the first place. As long as the interface is testable, then there is nothing wrong with nesting, but you need to have tests in the first place, which rarely is the case.
@mrosskne If you ever worked in a legacy back end system. 95% of things are the nested kind. Way easier to follow. Especially if you have 30 scripts to get by the end of the week.
@janiscakstins2846 That sounds crazy, any piece of logic should be testable. If you can't get to a nested part of code in your tests, either the test or code is flawed.
Just code for clarity. Sometimes that means breaking complex methods in several simpler routines, other times that means combining several micro routines into one larger method. Sometimes nesting gets really confusing, other times nesting adds clarity to the code. Sticking to any one type of code structure religiously just adds complexity in different ways.
I've never, and I mean _never_ seen clear code that is nested more than 4 deep. At 4 deep it becomes super tedious already. There is a reason they made this a kernel rule. I highly suggest you increase your tab width in your IDE of choice and you will quickly realize that there is almost always a way cleaner and easier method without nesting. It forces you to code better and come up with cleaner solutions, which ultimately leads to easier to read code that is much more maintainable. Gate clauses are a very easy example for that. Having a nested code means if you come across more exceptions your code needs to handle you need to escalate nesting into deeper and deeper parts of the code which will eventually lead to refactors anyways. Instead, having gate clauses at the top makes it a million times easier to add exceptions and still maintain readable code. Like sure, your 4 deep nest might "not look too bad" now, but wait until 16 different people have started adding conditions and you'll quickly end up with more nests than you can count. Nesting - not even once.
@@Finkelfunk I'm 5 deep right now, about 100 lines of code. It's quite clean, clear and concise. Each section of the code is purposefully labeled and does it's own little bit just fine. I think this instance, tho, is one of the few times this makes sense, and that's a select/case, where the logic going into the select and under each case needs to work individually, so it's already naturally broken into sub functions, that's just how cases work. I guess I could break out my Line function and my Square function, etc. but there is no need, they are all right here, easy to see, edit and compare.
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” - John F. Woods That quote came in my mind while reading your comment. It may be ok for you personally but if you start working professionally you have to write clean and maintainable code because someone else could be forced to maintain it. Nested code in switch-cases is always smelly code at least.
Extraction should be done carefully. I've often seen code with lots of once-used & poorly named extracted functions, just to hide nesting. So the code is just as nested as before but you have to jump from function to function to understand whats going on.
I swear. What people don't realise is any added function is one extra layer of indirection. So only add a function if you have a good reason for it. I have seen people adding functions just for the sake of keeping method under a few lines
@@nazeefahmadmeer I find that the right limit for extraction is when you start struggling to name the new functions. If it does not bear any meaning, then it probably does not deserve to be a function on its own. Also, respecting the single responsibility principle helps a lot (in the OOP paradigm, that is). If a class has too many methods, and you can't easily find your way around, it probably does too many things.
The problem is not extraction but the poor naming. Yes, coming to with good names is sometimes hard but if you don't like doing that, maybe programming is not something you should do.
Never extract something that’s only used once is my philosophy. A function needs to be called at minimum twice before it deserves defining. If it were up to me, thrice.
I'm a huge fan of "guard clauses" (simple if's at the start of the function which basically just return because the return value is obvious due to a special case input). So I was VERY bewildered when in my early programming classes (In 2020!) it kept being blanketly repeated that "a function should never have multiple returns". If you have one return 50 lines in and another 100 lines in, sure... but then you probably haven't split up the function correctly to begin with. You can write much simpler and cleaner code when you filter out all the special cases at the beginning as much as possible. One simple example would be a power function: pow(base, exponent) if(base == 0 && exponent == 0) throw Exception; (if you'd want to treat it as undefined) if(exponent == 0) return 1; if(exponent == 1) return base; ...rest of function to actually calculate the power Now this definitely goes against a different technique called branchless programming, but unless your function is called very very often in some heavy computing task, the readability far outweighs the speed.
The best thing about guard clauses is that they are a perfect match to how you should be thinking when designing your program and writing your tests for it when following TDD practises. If you are designing complex functionality, the way any business describes it to you should be massaged into first the error conditions, then the happy path once all business concerns are satisfied. This lets you go and immediately write a test structure from the ticket, and then implementing the test structure automatically creates a flattened guard clause implementation. I find it incredible how many professional programmers haven't figured this out yet.
The problem with branchless programming is, it is often written in a way that you need to write comments to explain what kind of monster you have created, because the next programmer who will be taking up your project is likely more stupid than you.
"Never have multiple returns" was a good rule for the time. Because way back a "stack trace" meant manually adding debug lines to each function: "entering Q" and "leaving Q with value X". Return-from-middle (which is what I've heard it called) made old-time debugging extra-tough. So you may have been taught by someone who got their first job in 1982 and never worked anywhere else.
I think that decomposing your mega function into a bunch of single responsibility micro functions definitely makes unit testing a lot more straightforward. But one thing that I have found is that I still need to write the mega function first. Once it's all down and working, I can then refactor it into something more sensible, but trying to write out all the single responsibility functions first, takes me more time than doing a "first draft" and then editing. It's like premature optimisation. Don't do it until you know that you have something you need to fix. I'm also a big fan of kicking out bad paths at the start. Not only is it easier to read, but when you discover a new unexpected bad path, you can just add another concise return block instead of having to nest something deeper in the function, and possibly missing a spot where you also needed to return rather than execute.
I'm the complete opposite. I find it way easier to create a main function and complete that with unimplemented functions and then go and implement these one at a time. This way I can start by planning out the overall process of what I want to do and then handle each individual implementation of sections independently. Sometimes if when I then implement a function and it ends up just being a few lines of code I'll then move it back it back to the main function - but I still got the benefit of using placeholder functions to write out how I would approach the problem
I find it certainly depends on what I'm doing; currently, I'm working on a PHP project with a bunch of POST forms, and the flow of those is generally extremely predictable, so I've been creating a main function with unimplemented functions to start off with for that. But when the structure of the task isn't so predictable from the start, I do generally find that jumping in and getting my hands dirty with a rough draft, and then refactoring that rough draft as needed, is a better way to quickly start turning that structure from nebulous to definite so I can start encountering and resolving unknown unknowns.
I feel like once you get more and more experienced you don’t need that draft function anymore. But it might be just a way that you think / work it out. I hardly do it anymore unless really rapid prototyping a method or several implementations. I almost instantly know what piece of code should be a private method or even its own class.
I'm a never nester myself and I've used the extraction and condition inversion techniques many, many times. I just wanted to comment and say I found this video very pedagogical. I really liked how you visually showed how you refactor a function (with extraction and inversion) using animations rather than just live coding it. It's more work for you to edit that stuff but I think it's easier for beginners to follow along. The only thing I would suggest you to change is to more clearly highlight the code you're talking about. The blurring didn't quite work for me all the time and I had to really concentrate to see what parts where blurred or not. Maybe it's just me and it's time to get some glasses though. Over all a very nice video!
just popped here to say if you have too many nests, then yeah, you are screwed,, also just a question for all other folk down here: branched/branchless?
I agree, my eye was drawn to one of the blurred texts first, then I'd see the other blurred text, then I'd be able to focus on the code he was talking about.
my brain were exercised well, but my eyes were straining hell
Год назад+389
100% on what you said about separating the error paths from the good paths, it makes it so much easier reading the code months or years down the line. That approach really shines when writing code: you think of the error conditions first, handle them, then continue writing the good paths, but with a lot less to think about.
trouble is when you need to follow for example MISRA and there you have: "A function should have a single point of exit at the end." - you can use only one return in function
@@gangulic That's a silly rule, but if it must be followed, you could always utilize some sort of "proceed" boolean. At the top of the function, set it to true. Do your input validation steps, and if one of those steps fails, set the proceed boolean to false. When you get to the meat of the function, check that boolean first and only perform it if it's true (you can also utilize this boolean at each validation step after the first). Does this make it more complex than simply nesting? Maybe, maybe not, it depends on how many conditions you are checking, but it should satisfy the rule and allow for some de-nesting. Basically it turns a bunch of nested if statements into a flattened list.
@@gangulic does exception counts as a point of exit? If you can't use those, then "do {} while(0);" and use break instead of return if you want to be polite or use "goto" if you want to send a message on what you thing about this rule.
@@gangulic This is silly. Not sure why people teach this. Validating inputs and some other data before proceeding to calculation should end the process/function as needed. This can be in the form an exception, null/empty return, just exit/end the function, etc. Depending on the situation, the validation could be its own function and the main function can proceed based on that output. Recursive functions have multiple exit points. I have yet to see one with a single exit point that wasn't poorly written or convoluted. Someone else made a comment so I going to quote it: "Code for clarity". And I'll add to that - add comments, if you can't read minds don't assume I can read yours.
I particularly like Extraction because then you can give the function really descriptive names, then when you read thru the top function it's almost like you're just reading a book, instead of interpreting the logical flow of the code. I often combine Extraction and Inversion, in that after I invert, I'll extract the inverted code into its own (descriptive) function.
Choosing descriptive names for your variables and functions is a big part of making code easy to read. The less thinking you have to do to understand what a variable is for, or what a function does, the better.
"you're just reading a book" Thats what I expect from my code, consistency and appropriate semantic naming are key. The code should be as readable and understandable as a book telling a story.
And that’s why you shouldn’t need comments in 99% of all cases. Extract all logical units into their own function and give it a descriptive name. Much better than lengthy comments that never get updated when someone changes the code.
Biggest thing with this is to group the extracted methods logically. I can't tell you how many times i had to ctrl+click a method only to end up at the bottom of a 5000 line file because the developer who implemented new code couldn't be bothered.
It's also makes the call stack much more informative when an error occurs. Easy to see "error in the foo method" vs the 1k line method had "object not set to an instance of an object"
Reminder to not go overboard with extractions as it creates a readability issue having to excessively jump from function to function. Especially if they’re not placed near each other and/or poorly named.
Skill issue lool (Skill of your IDE, that is, ayy) Even the VSCode basic C extension will show tooltips when you mouse over a function that show the signature and grab any preceding comments Rest of this is a stupid rant I wrote because I’m procrastinating studying, but I want to post it anyway. Sorry eh Also disclaimer, I’m new, and the fullest featured IDE I’ve used is IntelliJ for like 2 months I just think it’s kind of sad and ironic how much bending over backwards, or even thought at all, still has to be done in the name of readability, when this is literally how you create a UI. Like some people refuse to admit they’re bound by the same kinds eyes, hands, displays, and memories as someone doing anything else on a pc. Like anything beyond static, possibly colour coded text on solid background is some perversion. Like you should be able to just understand and hold the whole thing in your head, who needs eyes to know what code means? “What, a function reference that drops down when you hover or click to show you the definition? Comments being extracted and shown to the side or on hover or any other way than standard inline with the code? Shaded areas instead of a squiggly people can’t agree on because it wastes space wherever it’s put? Brackets that resize like you would on paper instead of alternating colours and fucking with the rest of the colour code? Any other idea you’ve had yourself for rendering the code window and then forgotten?” “Hell naw that’s for bitch-ass kids, I will continue to deal with the same basically-notepad limitations and inconveniences that my predecessors did. While I work on the dynamic UI for this CAD tool or map visualizer or browser game or whatever.” Like, is scratch not presented the way it is so that the reader can intake information visually, separate from encoding the information in text on the screen you have to read? Isn’t the readability issue one of us being limited in how easily or quickly we can understand the code on screen by reading the text, and wanting to increase it by being able to infer program behaviour or structure based on visual structure? Just feels like it sucks that in the age of OLED, VR, web frameworks, chatGPT, etc, some cool tabs can still be peak fkn technique
It is very frustrating when trying to debug someone's code, you jump from one function to another, to another file, and then you realize that you are looking at the wrong section because the errors is in the previous step that you have to find what is calling that function and what is calling that function and so on. I come across this too often as a web debugger and mod creator, so thanks, i hate it
The "validation gatekeeping section of the code" can be summarised as "guard clauses". We use it all the time, especially in the backend of our stack where requests must be validated. I didn't think I was a never nester before this video but I've got a bit of a problem with long line lengths so avoid unnecessary nesting if possible.
To add to the "fail fast" concept, I will typically go a step further and put my guard clauses into a guard function, abstracting all the negative scenarios away.
As a comp sci student, I'm loving these videos! These topics aren't really talked about at my university, and the way you present them really makes me consider things from a different perspective. Keep it up! :)
I was already doing this but not aware of all the benefits outlined here. I've always used extraction and inversion (without knowing their names) simply because it's cleaner and easier to read. Great video.
I used to be a never nester myself. Now I’m more of a it-depends-on-the-case nester. Does extracting code into a separate function make it easier to understand? Well, sometimes. Do early returns make it easier to focus on the actual core of the procedure? Again, not always. It’s good to know these techniques of extraction and inversion, but as everything else in programming, you shouldn’t use them just because you can. That said, the video is really well done! The animations make it easy to follow all the moving parts, and the explanation is clear and concise. Great work!
Could you elaborate? I would argue that if you can you should. Reading heavily nested code sucks balls. Reading code can take quite awhile for you to finally absorb what's going on, and minimizing nesting helps keep the high level logic in the foreground for the readers. Things like guard clauses just seem like good coding practices, up there with making sure you comment your code and try to use descriptive/accurate names for variables and functions.
I said in my own comment, and that kinda answers Ade's question, so I'll repeat it here. It does depend, a lot. Sometimes, but not always, extracting code to a function can help, provided everything still remains in one file or one compact section of the file. I've had the unfortunate experience of trying to dig around programs and, in order to understand the full procedure, I end up clicking around to find the function declarations. This is the worst for triple or quadruple nested functions, where you basically have to scroll up and down constantly just to follow the flow of code. Nested code might be bad, but similar to reading a book, I'd rather stick to one or two continuous pages instead of bouncing around dozens of pages just to read a couple sentences here and there. My general rule is that it needs to be a big chunk of code to be it's own function, or so self descriptive and simple that you don't need to keep the implementation in mind. A good example of the big and small functions would be the big "handle http error" and "is even" in the video. However, sometimes I see functions that are two or three lines, and aren't so descriptive that you actually need to keep the implementation in mind everywhere you see it used. An example might be an error logging function, which depending on a boolean variable, may call another function that can affect the code going forward, like terminating the program or changing a variable being used. In that case, it would definitely be better to either split that function in two and just call what you need, or take the nested function call out and call it when needed. Or, best idea, don't use the function if it's only called in one place and just leave the code where it came from originally.
@@ade8890 I definitely think guard clauses are the best way to go, and should be used whenever possible. I can't really think of a reason to not invert code like that. Worst cases, where there's a lot of clauses interspersed between big sections of code, adding a comment just before some part reminding you "Hey, the number cannot be 5 here anymore, that was covered above" is great. I've written code with over five clauses like this, with enough code between to push it out of view, so having those comments to remind me and others is very helpful. This does get a little hairy when you have multiple different variables being filtered, so instead of "number isn't 5 here" it'd be more like "the number isn't 5, the object is null, and this enum color is either RED or BLUE now." But still, that's where I write the comment stating that. Good IDEs also understand this in static analysis, so I sometimes use that to check myself by testing a variable, and pray the IDE tells me "that is always true/false here."
@@ade8890 For example, you know which parts of the code get executed when everything goes well simply by looking at the indentation. Having a single exit point is tidy and organized. Also, sometimes it’s difficult to give a name to a specific part of the code, so it’s better to leave it inlined without a name. A name that is unclear and/or inaccurate can make things worse than just plain inlined code. At least, this is what works for me. It’s how my mind thinks and processes information. But that might not be everyone’s case.
@@almicc " Sometimes, but not always, extracting code to a function can help, provided everything still remains in one file or one compact section of the file." Yeah I agree with this. You shouldn't extract code to a separate file if where you are extracting it is only ever used in one spot. " I've had the unfortunate experience of trying to dig around programs and, in order to understand the full procedure, I end up clicking around to find the function declarations." I'm not sure if I understand the issue here. Most IDEs allow you click into a function call to auto-navigate to the declaring section. And aslong as that function is only used once, you can click the function signature to be brought straight back into the calling body. This gives you the best of both worlds, where you can click into more specific logic that you need to understand, or you can jump back out and just focus on the high level details.
The blur is really cool, but I think also including the more traditional background highlight rectangle is still helpful because it pops out more. (in other words, maybe it still looks cool using both techniques at the same time!)
I've love the way that you animate code code changes. It makes it really easy to follow. I like the disgust-o-meter, it is often how I decide whether or not to request a change on a PR and now I've got a name for it. This was a nice way to advocate for the Single Responsibility Principle, better than the 7-line methods Bob preaches about.
I've never heard of the "never nester" concept -- but take "single responsibility principle" very seriously. Just checked my code and it looks like never nester.
I mean... not really. Single Responsibility is for classes, not methods. And while you COULD apply it to methods, it doesn't always work out as well as you think. However, it's much easier to enforce it with classes because classes should be performing 1 type of operation and be handing off other operations to other classes.
@@Dyanosis You're right. I misused Single Responsibility here. It is supposed to be "this code is fulfilling one person requirements, and no one else's." I was looking at "this code should only do one thing and do it well." I suppose both principles have the number one in them, but they are very different. Good catch.
At 2:24 in your inversion, "if (top > bottom)" doesn't invert to "if (top < bottom)", that potentially introduces a bug. It probably should be "if (top
I'm an old school programmer that cut my teeth on COBOL and C. We were taught to only nest to 3 levels and use functions to simplify code. This is how I still program today nearly 40 years later and works well for modern scripting languages such as PowerShell which I use extensively.
This is interesting. 40 years ago that was the time when compilers did not yet produce good code and every avoidable If condition and every function call was expensive and function inlining was also avoided because memory was also limited. In order to save computing time, it made sense not to outsource performance-hungry code to functions and to nest If conditions so that the code was as efficient as possible. A multiple nested if else construct was often more performant or meant fewer steps for the CPU than executing each IF query multiple times just so that you don't have to nest. How did you manage to nest on only 3 levels? Was a fast runtime speed not a requirement for the code? Did you have plenty of memory available? Today that may not be such a big deal, the compiler optimizes it all away anyway and inlines the functions again, but it just wasn't like that in the past.
@@OpenGL4ever 40 years ago was 1983, so not quite at the punch-cards age. 😉 Compilers didn't have quite the level of optimization they do now, of course -- four decades of improvement are a beautiful thing -- but they could handle function calls and conditionals to a reasonable degree in most languages I'm aware of. Of course, if you were writing highly-responsive or real-time code all bets were off, just like today, but for most purposes, human readable code was a goal then as now.
@@autochton The Microsoft C compiler was improved a lot between the release of Windows NT 3.1 in 1993 and Windows NT 3.5 in 1994. That resulted in a big performance improvement of the newer Windows NT 3.5 solely because of the much better compiler. This was 10 years later than 1983. The C compilers in the 80s where still in its infancy.
I totally agree. This was a nice example of refactoring code. When I start a new piece of code I always force myself to not go into the details right away but create a fundament that outlines what I'm trying to build. Extracting routines and give the names of what I need to do in them and handle any results. This way I can start to think about the bigger picture before diving into the details. It helps me tackle the whole problem more easy. When I get to the nitty gritty of the details I may need to refactor some of the outline I made but most of the time it is not needed. Chopping the problem up like this lets me focus on each little detail independent of the whole problem. When I find that an extraction is becoming too big I create a little more outline before diving into the details again. Works very well for me. Sometimes I find that the way I thought about the fundament is totally wrong. No problem. I didn't write much code so far, so starting over is not very costly and I've learned a lot setting it up the wrong way.
What? Did you just say? What the what what? What was that? Did you say what? 10 years and this garbage is the best? And you... you teach? 10 years and you teach for 10 years and this garbage is the best you have ever seen and you teach. That is scary. I guess so your students must be hyper dense. Job security for me, I guess. No competition. Lol. 10 years huh. And this is it.
In general I agree. I especially like early returns and splitting conditions/validation from operations. But sometimes nesting is better. For example when you have algorithm that is inherently nested (like you have 4 tight for loops indexing some arrays etc.). Better to have the whole looping/indexing in one function and extract the "doing" into another function than to extract part of looping to another function. Because it makes the algorithm less clear and if you do 4 loops with complex indexing you better be aware what you're doing.
@@pizzapunt3960 there are algorithms that inherently require 4 or more loops. Some faster versions of matrix multiplication for example. Or pathfinding.
@@ajuc005 they don't need nested loop. If you are using nested loops you're being slow. You need parallel processes (might be called differently, every programming language implements it differently). If you look at a* for example, doesn't require a single nested loop. It's not something inherent to the algorithm but to your lack of skill.
I've generally found people call the block of returns at the top of a function after inversion to be "Guard Clauses" I've always found them to make everything amazingly clear just like the content in your video shows and suggests. Keep up the good work, can't wait to see more.
This style of coding really should be what is taught, but a long time ago the idea that you should only return at the very end of a method was very popular. I'm sure there may have been a good technical reason back then, but it is an objectively worse way to code. Today we don't have a valid reason to stick with the only return once at the end dogma.
@@evancombs5159 I think it's a misunderstanding of the "single entry, single exit" principle, which is actually something that object-oriented languages automatically enforce: that you should only enter a function or routine at one point (the top) and only exit _to_ one point (right after where you came from). Older languages like BASIC allowed you to jump to any line by using a command such as GOTO or GOSUB, or by changing the pointer stored by GOSUB to tell RETURN where to jump to -- so in the case of BASIC, the principle meant "don't use GOTO; use GOSUB only to jump to lines immediately after a GOSUB, RETURN or END; don't change the RETURN pointer; and RETURN from every GOSUB". The one thing it's still useful for is freeing allocated resources (which you have to do before any exit) -- but Java lets you use "try" and "finally" instead of duplicating that code, or allocate the resources in the opening statement of the "try" if they can be freed automatically when it's exited.
@@evancombs5159 In my company, I teach beginners the guard clause method because it makes a massive difference in readability. I tell them that else should never be used.
"Guard Clauses" that's a great name for it. Super great pattern, although I notice I implement a second tier of them within a nested scope if the right situation calls for it
One other positive to this approach is that because you extract pieces of logic into functions, you can give them a function name that describes the logic. This alone goes a long way to making your code more immediately comprehensible.
Sometimes this makes sense. And sometimes it doesn't. Also, every function call has significant overhead: Saving the execution frame of the calling function onto the stack, and then throwing all those values onto the stack for the function being called... then when returning, restoring the execution frame from the stack to resume the calling function. Do that inside a loop, and the execution overhead can be ENORMOUS. That's this being a hard and fast rule that should never be violated is retarded. And let's not forget how much nesting of decisions structures there can be in an interrupt handler. Putting a function call INSIDE an interrupt handler is equally retarded, because interrupt handlers should be as fast as possible. If you can't understand the code, then you're not commenting enough.
@@akulkis "that should never be violated" → As they say, never say never. In art and engineering I think rules are more like best practices that you have to learn first before you understand when to break them. In a lot of cases, I think readability (i.e. maintainability) is more valuable than cutting edge performance. If performance is an issue there are likely algorithmic changes you can make before changing whether something is extracted out into a function or not. About comments, I'm personally persuaded by the idea that the code should be as readable as possible without comments. I.e., if the code requires comments, that's a sign that maybe the code is getting quite complex. Sometimes that's unavoidable, but a lot of times it isn't. It would be one thing if comments were free but they're not: they require diligent upkeep or else they're liable to confuse rather than help. Learning a good balance between code readability and commenting is just another skill acquired on the journey of becoming a somewhat competent developer.
@@shugyosha7924 "that should never be violated" → As they say, never say never. In art and engineering I think rules are more like best practices that you have to learn first before you understand when to break them. -- that's why I wrote that saying it's a hard and fast rule is retarded.
@@shugyosha7924 "About comments, I'm personally persuaded by the idea that the code should be as readable as possible without comments. I.e., if the code requires comments, that's a sign that maybe the code is getting quite complex. Sometimes that's unavoidable, but a lot of times it isn't." The GREATEST thing that learning to program in various assembly languages is this: 1. Write the comments for the function BEFORE writing the function itself. A. You are creating a "mission statement" for that function B. You are going to describe the strategy used to go about solving whatever that function is supposed to use. C. If you can't describe the algorithm(s) in plain old everyday language, there's literally no way that you're going to write it in code correctly. Too many programmers treat commenting as an afterthought. In assembly language, you literally can't get away with that. You're going to comment before you code, and then do more commenting while you code. ... every single line. I haven't written anything in assembly in decades, but those habits described above have always garnered heartfelt appreciation from others who have had to read and/or modify what I wrote. Remember this: "Self documenting code" is a myth. Yes, use meaningful variable names, but believe me, what one person thinks is clearly readable without comments will leave someone else scratching their heads. Suppose you have a for loop in C/C++, and for whatever reason, you do this loop_variable += skip; If you're not commenting SOMEWHERE (I would suggest both before the loop AND next to that line) why you're screwing with the loop control variable, SOMEONE is going to be confused, and chances are, you aren't going to be around to explain it. Or its' 20 years later, and your thinking and problem solving style has changed so much that you literally cannot understand why you yourself wrote something like that. I've never seen source-code with too many comments. I've seen source-code with lots of comments that are unnecessary because they tell you what the code itself tells you: int counter; /* counter is an integer */ But that's a different problem -- it's just being redundant. Comments should generally focus on answering the question: "WHY? " and occasionally on "How?" (comment blocks for functions, and generally longer for loops and long if statements and long assignment statements ( x = LOTS of factors and terms)) and occasionally WHAT (data structure declarations) And remember, the more CLEVER your code, the more comments you need so that someone can understand what you're thinking without you being there to answer their questions. Compiler writers added commenting ability for a reason. If it were simply a matter of teaching "write self-documenting code" then we wouldn't need comments. But the fact of the matter is, a good portion of code is NOT self-documenting, and for most programmers, it's a FAR larger proportion of their own code than what they think.
I'm all for extracting code into functions as long as these functions are given names which clearly convey what they do. If they are not named properly, I would rather have the code written out explicitly than having to jump though the functions in order to find out what it is actually supposed to do. If one finds the process of naming the functions difficult, it might be a strong indication that the block of code should be extracted in a different way - perhaps it is better to extract the code into two different functions rather than one, or perhaps the rest of the code needs to be sorted out first. Too many times, I see developers hastly jump to the conclusion that "extraction == good" while forgetting the larger goal at hand, which is to improve readability.
Exacly. The problem with multi-nesting is that you have to understand a lot of code at once while also making big chunks obfuscating flow of code. If we have one very short (thus not obfuscating) nest then replacing it with badly named function actually makes problem WORSE because it forces reader to jump over multiple places to process one procedure. Obvious solution is to name stuff descriptively, but naming is hard sometimes. There is no golden bullet.
Also comments and documentation helps, I know not everyone likes commenting and likes self descriptive code and not everyone thinks documentation even exists, but sometimes a short sentence of intent helps, even if it's in the reference code and not in the compiled code or the documentation.
Sometimes I need to extract first in order to figure out the name. I'll extract a function and give it a temporary nonsense name like "Banana." Then see how the function is used, what goes in, and what comes out. From that I discover a name and I rename Banana to the name it ought to have had all along. If the name is long-winded and complicated, that's a hint that I need to extract it further, or perhaps I extracted the wrong thing.
And that is a problem you do run into when applying this too much. You'll get a lot of small functions for which it's sometimes damn near impossible to think of a sensible name. Sometimes just keeping the nesting is a fine solution as well; and add a comment if it's somewhat unclear.
Same! When writing graphics code I noticed that if there was some check I needed to do, like if the renderer initialized or not, I would return out of the function rather than nesting all the "happy code" inside passing checks. I didn't realize this was an actual concept lol, I just thought it made it way easier to read.
I think this happy path / sad path disctinction is also called a "guard" clause - in which you guard the important part of a function with the guard(s) that are checking for invalid conditions and then bounce if the conditions are not ok before ever reaching the important parts.
Yep. I recently started programming in Swift and there "guard" is an actual keyword. I have been doing much more "happy path" programming before but will refactor some of the code I done in Kotlin.
Guard clauses aren’t “never nest”. Guards simply mean you simply have a flat set of “if” validation checks on your inputs (and error handling) before you start using them in the function’s business logic. In effect you’re writing all your functions like they were main(), weeding out as many error conditions as possible as early as possible. This is a good design pattern that has nothing to do with obsessively avoiding nested logic. It simply eliminates many wasteful and unnecessary layers of nesting by doing error checks in sequence (with error returns). There are times where 5-6 or even more layers of nesting is perfectly good, efficient code for the business logic. Especially if you’re doing something functional, like creating several layers of nested objects in one statement, where indenting the nested calls on new lines makes the code way MORE readable.
@@zackyezek3760 100% agreed, the thing these videos often ignore is that you have to apply common sense, logic, and your own experience to decide whether to nest or not, whether to split something out into a function, whether to chain calls on the same line, or split it up. Etc etc. Obsessively applying some arbitrarily rule to all situations results in code that is consistent, but often consistently wrong. You have to both consider what you gain by making a certain style choice, and the disadvantages as well. Normally readability, performance, and maintainability (ease of making changes in the future) will fall on one side or the other, and you have to compromise. But always prioritising readability at the expense of all other factors is often the wrong choice.
I would consider myself a "never nester" too, but I can also recognize that there are drawbacks to it that might be good to point out, with some tips to avoid them. 1) The inverted conditions can sometimes be less obvious, especially if there are some logical operators (and, or, not) that can be hard to invert correctly or understand afterwards. I think a good mitigation for that is to avoid compound conditions by either splitting up individual conditions, i.e., instead of "if any of these conditions are not met, return early", itemize each condition with its own early return, which also encourages more helpful error codes or exceptions, or putting the compound condition in a function with a good name. 2) Although putting all the error conditions up front cleans up the "good path" below, it can also put distance between checking for a problem and the code that would fail. So, either you can't understand the check before you see the code that needs it or you can't remember that the check was already done by the time you read the code below. The way to remedy this is typically to decompose into smaller functions (e.g., instead of 20 checks up front, and 5 chunks of "good path" code below, you split into 5 functions, each having around 4 checks up front). 3) In a similar vein of scoping issues, decomposing into many functions can encourage broadening the scope of code and variables. This is especially common with object oriented code when decomposing member functions (aka methods). It's easy to create new "decomposed" functions that are not self-contained, i.e., they take in and/or return variables or (self-)objects in a state where they are in the middle of some broader processing (the parent function), and invariants are temporarily invalid. That can be error-prone in future changes or additions to the code. It's also easy to make what should just be local variables of the parent function into member variables of the class or (God help us) global variables to avoid having to pass them in and out of the child function. And finally, it's easy to overly expose functions that are really just implementation details. Avoiding this just takes discipline to minimize the scope of variables and functions as much as possible. 4) Another obvious issue is that splitting up code into many single-use functions, and often, for other practical considerations, having to scatter them in different places in the code, does create readability problems due to having to jump around, and also because it can be hard to name functions that don't have a well-defined purpose outside of that one call-site. This can sometimes be mitigated by using "local" functions (or closures, or lambdas, or whatever, they go by many different names). A relatively common pattern you see is a bunch of checks up front, then some helper functions defined (typically closures or lambdas), and finally the main "good path".
@@raducuvlad2187 Best book in the universe for any programmer EVER, it is a must read. In my opinion a lot of these nesting issues aren't so bad when you add a lot more comments to the code, which all of these examples is lacking
Also if you deconstruct into a bunch of small methods, that makes it harder to understand in a different manner because it obscures the real functionality. "clearDownloads" doesn't mean anything at all, it's the code inside you need. Don't break up the code into many small functions especially if it's only gonna be used in that one place
In addition to your 2nd point. I noticed putting checks upfront goes against the paradigm of try now fail later. Instead of checking whether a file exists, just try to read it and throw your exception (try-catch blocks). The nesting needed is just one level if the language allows multiple catch blocks per try block.
The bit about mentally discarding the unhappy path is what I try to drive into my junior devs brains! Less stuff in working space of my brain = less stuff to think about = less stuff to go wrong. The mental model is so important! Very glad you mentioned this and I'll be sharing it :)
Not really the case, if you dividing your code into a lot of tiny functions it can make it difficult to read since you spend a lot of time scrolling around for all the functions called, plus if you modify several function & it breaks something, but scrolling around all those functions trying to determine which function is the culprit, and you still have to track it all in your head. Worse is when you use nested functions: function A() { if (x > y) { result = Function B(x,y); } //endof if (x> y); } //endof functionA Function B (x,y) { if (x == (y +1) ) { z = y +1; result = Function C(x,z); } //endof if (x == (y +1) ) else result = Function D(x,y); } //endof Function B (x,y) All your doing to making it harder to read and debug. I would be much easier if all this was in Function A, and not have to jump arround all those functions trying to figure out the bug, especially if the functions are either in different source code files, or your source code file has thousands of lines of code and the functions are located far away from the main function. You can use comments to divide your code blocks into groups to make them more readable. Also add comments to all closing brackets to make it more readible\trackable. ie "} //end of if (x == y)" . Code your levels like a FIFO stack, so that for each level you add the closing bracket before inserting the first line of code in the new level.
@@guytech7310 this is why we have tools that make it easier for us. Your comments on the ends of code blocks are handled automatically by modern IDEs showing you where braces are related, and you don't have to worry about changing functions and breaking stuff if you have unit tests. "But who has unit tests?" I hear you ask. You, because you didn't write your code to be testable in the first place. Or you inherited a codebase where someone else made that mistake for you. I don't know, I'm not your boss. The point is it's better to leave things better than you found them and this is one way of doing it. The gist of this all is, if I know that a certain function or piece of code works, I can *entirely* disregard it from my debugging process. Skip it entirely. Not even a thought is spared for it (unless it has side effects, in which case, you have bigger problems). I don't care about code jumping around because my debugger is handling it for me. If I have to have an entire god function's state in my brain's working memory, that's very taxing and slows me down. Too many possibilities and variables to keep track of. I'll let me brain do what it is good at: think of things logically one thing at a time. Break down the task into smaller chunks. Check my assumptions. "The result of this function should be x, and I got x, so my problem is not here" and I never need to remember that that code even exists. So I move on to the next thing. Keep doing that until a) I find something that doesn't match my assumptions or b) run out of assumptions, in which case, more often than not, the princess (bug) is in another castle.
@@guytech7310 what makes things hard to read is the inadequate naming. And unnecessary comments are a classic evil trying to compensate the lack of effort on finding expressive name for variables, functions, etc...
@@biltongza I program in C# and find so much relief that Visual Studio 2022 keeps track of darn near everything (how many references to any function anywhere, no matter if it's in another file, inside a dll, or what-have-you). Even deep nesting display is color coded and you can collapse code at any level (the best feature ever - though most IDEs have this) and keep it collapsed (not to mention, it's all customizable!). This plus intellisense, and keeping track of all external stuff, git, etc, makes programming a lot less stressful. This is the what ALL IDEs should be doing: keeping track of everything. Coming from a background of writing code on a text editor, devs today have no idea how good they have it!
I used to do a ton of nesting when I was a beginner, but as I invested more time, I automatically learnt better methods of writing code by watching tutorials and reading documentations, and also got used to them, its great to know that I have improved over the years and already use good practices without actively trying to.
Thanks for the high quality video as always! A little suggestion though, instead of blurring other lines, how about highlighting the lines viewers should focus on? It will be easier for eyes on mobile.
This video is a really helpful one. I have recently started trying out rust as my main programming language and although it's basically all I've ever wanted, its syntax and how it's written can quickly turn any app into a big mess, and these techniques really helped in making my rust code more readable. Stuff like the extraction method help with fixing some of the messy syntax and hiding it away somewhere else so that your main function that does all the work looks cleaner and more concise. I will say though, writing code while making sure that it doesn't nest more than 4 layers deep is quite hard, and I usually go with a "make a messy function that does a bunch of stuff first, then denest it" type of approach. Great video!
You'd be surprised how little attention stuff like nesting, single responsibility functions, etc. are taught at university / college. You will be very lucky if you have a programmer that actually appreciates that as well as good logic and on top of that is your/a teacher. I've found that your best option is to find several sources of people that have a lot of experience and tailor to expert/intermediate audiences (even if you are not). That's where you start learning about design principles, abstraction, nesting, composition etc. I'm a python developer myself, and i can tell you that what i got prepared with from uni vs what i met when i started working is vastly different. Was lucky enough to find a channel called ArjanCodes which is exactly the type of channel i'm referring to when it comes to enhancing your coding levels. They help you pick up good habits early on and keep you on the right path of good practices
@@GOTHICforLIFE1 I was about to point out the same things. Uni just gives you the basics of programming. Learning about coding best practices enhances your programmer level to another level.
@@GOTHICforLIFE1 mCoding is another good channel focused on intermediate/advanced programmers (particularly Python and C++ devs), but I particularly want to mention Corey Schafer, especially as he just posted a new video after like a couple years off; I've found that Corey's channel does a great job of at least mentioning more advanced concepts while keeping the videos very accessible and beginner-oriented. I believe his videos are almost all about Python though, but for anyone learning Python, I highly recommend Corey Schafer's channel, and for anyone new to programming who's starting off with a language other than Python, I highly recommend switching to Python (at least while you're learning the basics of programming) and also Corey Schafer's channel.
I was self taught, but I also kept reading to make up for it. This was eons ago, so books are less popular now, but The Pragmatic Programmer and Code Complete have timeless wisdom. Then there's the "Gang of Four" Design Patterns book. After that I would suggest learning SOLID design principles.
I remember working with an ‘ever nester’ once. Rapidly scrolling down his code, there were large gaps between function definitions. Weird, I thought. Scrolling to one of the gaps, then scrolling to the right, I found the missing code. It was nested so far it had disappeared off the right hand side of the screen!
YES! when multiple if statements are nested I feel like I need to keep it in mind it's a nightmare. I've always been a never nester without knowing. Great video!
This also gets into the idea of testability. If you have a lot of small functions like you do in your last example, it's easier to write a test case to verify each public API that you split out in isolation.
Wouldn't the extracted functions be private and therefore untestable? And no, you shouldn't then make them public instead just so you can test. That would be very bad.
I learned all my coding ways from the company I work for who hired me with zero experience. This video changed my life, and we are currently working towards implementing this on projects going forward.
I'm a new C# programmer doing hobby game design in Unity. I am so glad I got introduced to some of these ideas early in my programming journey, mainly inversion using the term "guard clauses". It's so nice now that I have a little more experience to see this elegant dive a little deeper into the topic. Thanks for the great material!
Thank you for this explanation of never nesting. It help this beginner see some very bad habits I was creating. The exciting part for me was seeing you refactor via inversion, moving all the conditions and pre-qualifiers to the beginning of the code block and grouping the actual functional code after that… begin able to examine a block of code after putting down all the initial tests and rules will really help me a lot. I am always trying to do something way beyond my capabilities, so this should bring huge benefits to me. Thanks again.
As always, follow coding paradigms with caution. Just because someone on the internet said don't do X thing, doesn't mean you should follow it all the time. There will always be edge cases to everything. While it is generally a good idea to keep nesting at a minimum, there maybe sometimes that you might want to nest a piece of code for conciseness.
I'm extremely fascinated by the never nesting technique. I actually started using inversions and premature returns and I sure feel satisfied of my codes. There are always ways to improve since I'm a self taught, but damn, the elegance and readability sure feel great.
They really do. Especially in cases where you're using rust or something. Although rust is quite awesome, the syntax can get quite messy at times and using these techniques really do clean up code and makes all of my rust code quite concise and elegant and easy to read, especially the extraction one.
Now in my work I'm reviewing some code and excessive extraction is my number #1 obstacle. To verify how some simpliest things are calculated I need to constantly jump across different sections of the code, often having to look for them in various files. It results in having much more to remember in the code, because not only you still need to keep those additional conditions in mind, now there's another matter on whether there are no type conflicts, whether the functions work together, because it's pain in the ass to debug if every function works on it's own, but they don't work together when you run the whole thing.
Personally for me, I find the extraction to be significantly easier after everything is written. Sure, when starting out, you may end up 4 or 5 deep, but once you have it working, I think refactoring to avoid excessive nesting makes the most sense at least for me
I agree that indentation should be avoided when possible, and also that guard clauses are one good strategy to do that, but I think breaking your code into a ton a little functions makes it harder to follow what’s happening and makes it easier for bugs to slip in. John Carmack wrote a memo about this a while ago, look up “John Carmack on Inlined Code” to see a more thorough argument. Regardless, excellent video! Can’t wait to see more!
I totally agree, I found my self going up and down, up and down around some files because I had all these function calls... Not everything has to be a function, guard statements are often enough, and well designed code will rarely if ever go deeper than 3 levels w/o extracting into functions.
@@coffeedude You do have a point! Nonetheless at least in my line of work that's rarely the case (I seem to eat spaggetti every day) and well I sometimes have a "nag" to see how a function operates if that makes sense?
I think it's a bit important to note that the kind of nesting you're talking about is mostly visual, and you're completely fine nesting to infinity as long as each function is only visually 3 deep.
I generally agree, after 3 or 5 indentation levels it gets harder to understand how deep you really are in the indentations, but I'm not really against nesting past 3 levels. I am against spreading the code around however, eg. "else" statements that are far apart so you have no idea what they're for at a glance. In fact I'm more likely to add an indentation or an immediately invoked lambda to contain relevant code in its own block. Mostly in situations where the block is relatively small, semi complicated but doesn't quite make sense to put it in its own function.
Back when I knew less I wrote some code with the else so nested and far from the if that I had to add comments just for knowing to which if the else belongs. With this video I could probably easily fix it but I'm afraid something will break so... my old code will just stay the way it is, at least until I get determined enough to do another larger update XD
@@absolutewisp Inversion/guard functions would perfectly solve it and make it way easier to read. The problem is that it's part of my updater subroutine which checks if there's an update and starts the update sub if there is, which means if I mess _anything_ up I can't send out anymore automatic updates, which would require all users to manually upgrade to a hotfix version, which is just... no. So I will probably do it if I do a major refactoring of all modules, which won't be anytime soon as I don't get to work on that all that often and there are more important things to update first.
@@cook_it don't be afraid, just do it. It's a bad idea to let code rot. Just need to create some good tests to make sure any chance doesn't break anything.
Wow, you just opened my mind to the realization that I too have always been a never nester. I never thought of it this way, and I never had a disgust-o-meter for function depth, but I do extractions and inversions ALL the time to minimize the length of functions and to keep everything easy to read.
You’ve managed to articulate a code style I’ve stuck with for years far better than I can! Regarding your last example with the download queue, you could also point out that it’s much easier to unit test the de-nested code compared to the big block
Actually, on the video sample, it doesn't change much the testing strategy. When testing, you want to target an unit which has a functional meaning, with scenario that makes sense. Generally, it is testing public actuators, controlled by public state, and asserted on public observable. For instance, with the download sample, one would really want that given some recoverable http error, then the execution is retried many times until a failure state. The handleHttpError method, or all other internal extracted methods are not testable as they don't have any particular functionality.
A fellow never nester here. The gatekeeping method is so much more elegant, and your points about how it shifts all the real code to the end and keeps it all together is exactly the reason I do it. I find it much easier to make sure I've validated everything necessary when all the "unhappy" conditions are all lined up before the "happy" code.
Even the nestiest of "nesters" agree that input validation belongs at the top of a procedure. I'm sure examples like 2:40 came from real (presumably novice) code, but to cast this as "never nest" advice is a bit of a red herring. The more interesting use case is algorithms and data structures which, as Niklaus Wirth pointed out, equals programs. Try "never nesting" code that perform B+-tree deletion or something like that.
@@DeGuerre No you're right, and I was using the term "never nester" a bit jovially. I agree that there is a place for most programming practices, even heavy nesting, and we must be careful following any advice that says "never" or "always" do something. While I think nesting should be limited as much as possible to improve code readability, in certain examples like the situation you mention it would actually improve readability compared to the alternative.
One of the things I learned from the Pragmatic Programmer is to write a function that does one thing well. And if it does that one thing well, you don't have to worry about it and can call it when needed. If your function is trying to do two things, that's pushing it, three things and you better start thinking about what you're doing and why. Sometimes you can't help it. But many times you can.
I'm an ex-content writer, now learning code. In the web writing world, we have headlines from H1 to H6 (and beyond), but it's generally considered sloppy and bad practice to go deeper than H3-subsections and H4-lists. If you find yourself building nested H4-subsections within your content, you need to revisit your structure. I see many parallels here!
As a web programmer, I have to say: I think not allowing multi-line links in HTML is a crime. I know having to change a one-line link is easier, but I just want shorter code goddammit!! It feels bad to have well indented and well nested HTML look awful once you put a link that's like 170 character wide ;(
Because in both case it's about readability, less complexity means more straight-forward access to the information. Ultimately a deeply nested piece of code will work the same, it's just gonna be easier to read, maintain and extend.
Thanks for posting, these are really great! I was a technical writer before I became a software engineer, and think about the aesthetic of code and cognitive complexity quite a lot. Other possible topics: • the declarative flow pattern you illustrate here (put every individual "thing your code needs to do" in it's own function, then compose in a main method at the bottom with a top-level try-catch), • phrase every boolean in positive terms (i.e. isLoaded vs. !isNotLoaded; every negative boolean in code as well as written/spoken word adds cognitive complexity because we have to mentally flip to understand), • factor out the individual parts of a compound condition into statements aliased to easy-to-read variables (if isUserAuthenticated && recentlyJoined) • writing code with the primary goal of minimizing the time-to-understanding of the next dev who has to read it
It's very interesting you mentioned this!! I am a software engineer and I find myself so frustrated with the way people code and organise their code, that I am now the defacto documentation person in my team!! I am constantly writing pieces explaining the flow, thought process, context, memory concerns and so much more. All of this for the - COGNITIVE LOAD. All I wish for is the next person doing this after me to not have to spend years understanding the same thing. I find that Code organisation, comments, variable names, fonts, colors etc make a very big difference in how I subconsciously read code and affect speed
Recently I was refactoring and cleaning up a single file of 13k lines and modularizing it. I work in C btw. I found myself telling the junior dev how to name functions. Subject-verb-object I.e. module_name-action-what_to_act_upon For example Custom_utils_generate_hash. This helped me create header files with almost tree like branching in names where I could easily read through it and find out what a module has to offer.
Denesting and functionisation only works if the same code will be used in multiple functions. If you HAVE to limit nesting, that's where the "inline" keyword comes in for the simpler functions.
I always compare coding to literature. As you grow more experienced and laureate, you will write less but deliver more. Given this, I always start writing code verbosely to meet all the requirements. Then, I trim down the fat by sticking to the core principle, being DRY. It's not ideal, but it works for me!
Completely agree. If I could write all my apps in one line of code I would. I’ve come to find that simple code tends to be the best code. Obviously not everything can be simple, but if it can be, it should be
I like this approach in moderation. When extraction goes overboard, it can get real annoying. Not a lot of functions are simple return statements. Trying to remember how 20 smaller functions work can be a lot harder than understanding the flow of a complicated function (if you document it well). I could possibly be traumatized by legacy code I'm working on right now...
this is one of the things that has me on the fence about this. Sometimes having everything in one function is very helpful. The metric I use to determine if I should refactor into another function is if we use it more than once. Or if the section of code can be replaced by a very well named function to make things clearer to read.
100% this. I might even say it's *worse* to spread the code across tons of other functions, methods, files, etc. At least if it's nested, it's all in one place. Then just use your code editor's code folding feature to make it easy to read.
That's what commenting is good for. The extraction is for readability and organization. You could argue that commenting nested code is the same, but it doesn't read the same.
Sad clause before happy clause, that makes so much sense now that you say it out loud. I just finished a uni assignment (using java to program a really shit interpreter) and did this without realising because I was trying to avoid messy nests, but I had no idea what I was actually doing. I’m glad there’s a name for it and I can do it knowing it’s a good idea, unlike many of my “good ideas”. “Oh I should program my ENTIRE interpreter around arrays, right now it doesn’t matter that their size can’t be changed and it definitely won’t bite me in the arse later” 2 days later: “oh shit I didn’t think about that specific case. Well I’m not rewriting every single method to take something other than an array so I guess it’s time to spend 2 hours “making the puzzle piece fit with a hammer”, as my mum described it.”
I wholeheartedly agree that keeping track of deeply nested code logic is overwhelming. I've been using these techniques you've shown in your video for a long time and I started doing it because I found it annoying to read through my own code, so I figured out how to make it more readable by moving things around and breaking up parts of code into separate functions.
As a beginning programmer, I always hear my professors tell me to separate complex problems into several methods. this video is a visual representation of what they mean. thank you very much!
I think inversion can work to make a function more readable, but extraction almost always makes a function less readable. Scrolling between different functions is way more annoying and confusing than looking at another nested layer which is right there next to the rest of the code you're trying to understand.
Most of the IDEs nowadays allow you to split the screen, so you can literally have the same file twice in front of you, and just go to whatever second function you need. Works well for me
Towards the end of the video, he mentioned Single Responsibility. The S in SOLID principles. This one practice helps so much to make code easier to read, understand and troubleshoot. I teach this very early on in my high school programming classes and show examples of how it helps to make things easier. I also teach the use of guard clauses to put the conditions first and the work last. all these techniques promote less nesting. I'm also a never nester. :D
Nesting isn't always bad or evil. Depending on your desired output, workflow & requirement, you may need to nest your code abit for the sake of clarity.
Yes, you do have to create functions if it goes deep, to take care of some parts of the processing and prevent having all the visual spaghettis in front of our confused face
@@hetuman nested, although not if/else: a definition of a recursive data structure that represents many possible "pathes" (for example in a game) or shows relationship between sub-nodes (just like xml)
Glad to know there is people like me out there. Also, it's wonderful that you didn't stick with the first trivial example and instead dug into a more complex and realistic problem to show the true potential of this kind of aproach to coding. Cheers!
Too many functions is spaghetti code. Properly nested code is beautiful. Your team won't wait for you to refactor your code just to fit your own idea of "pretty code". If you can't handle and organize nested code properly, you are gonna be a NeverCoder pretty soon.
Moving the unhappy code to the beginning is not always a good idea because you're throwing off the branch predictor. By default, your CPU is more optimized to take a reverse jump before the condition is fully evaluated (useful for for-loops) and *not* take forward jumps before the condition is evaluated (useful to prioritize the first if-block). You can "solve" this problem by using __builtin_expect(your_condition, 0) , which will allow the compiler to reorder the generated assembly code such that the block is moved further down in the function. I use this technique sometimes when I find myself nesting too much but sometimes I find it cleaner to keep the "more likely" branch high above in the function.
@@ChrisSchepman It's not really premature optimization until you've measured it and verified that the optimization isn't helpful. Modern CPUs rely heavily on pipelining and a missed branch prediction can be more than an order of magnitude slower.
@@ImranHaider All code optimisation is premature if you don't have a particular reason to optimise that code yet. You might have slowest piece of crap code ever, but if it only runs occasionally optimisation is irrelevant. Or you might have an already very fast beauty which you've already mostly optimised with barely any room to squeeze more performance out it, but if it runs hundreds of thousands of times per second it's probably still a better candidate for optimisation than the really slow shit.
@@tomcutts9200 You shouldn't optimize your code based on how frequently it runs, you should optimize it based on whether it runs within the time you expect it to execute. For example, in a video game, you would optimize your rendering code so that it can finish by the time the next frame is about to be drawn on screen. Many developers do what you mentioned and they give very little priority to code paths that they think won't run as frequently. The application start up procedure usually falls in this category and this is why so many "professional" applications (like Microsoft word or Photoshop) take so long to boot up. Users of these applications actually care a lot about load times but developers don't prioritize it enough. All I was pointing out in my original comment is that you should be aware of how your platform (aka your hardware) is executing your code. This enables you to write code that works with your hardware instead of against it.
@@ImranHaider I am a video game developer, and I have tools which tell me pretty much exactly how much of a frame's budget is spent in which function calls, so I don't need to guess what code is taking up all the time. If a significant chunk of time is spent in one place, that's the place to start looking for potential optimizations. I could try and find the bit of code which is doing it's specific thing the slowest, but if it's called once per frame it probably doesn't matter. Maybe it should take 1 ns but it takes 10ms, but who cares? There'll be some other code which should take 1ns, but it takes 1.1, but it executes 10000 times per frame. Even if I only shave off 0.1ns that's a much better choice for optimisation. You look at the thing that's taking up the largest % of your frame and you start there. And you prioritise the spikiest frames that take longer than average frames and see what can be done to optimise whatever is happening to cause the spike (and possibly see if you can flatten that spike by spreading it's work over multiple nearby frames). And yes, while application startup, and load times, are both important things that you don't want to get out of hand, they don't kill game sales nearly as quickly as framerates tanking and the game stuttering. Most customers would prioritise that you give them a stable fast game, over slightly annoying load times. People are prepared to wait a bit if the game is solid, but no matter how fast your game loads no-one will want to even start loading it up if the framerate is garbage.
For the most part I agree. If you’re going 3 or 4 deep you should be asking yourself if some of this should be deferred to another function. Too many conditions in a function can make code difficult to test because the test has to account for many conditions and that makes the tests themselves sort of brittle and too cumbersome to deal with over time.
Before this video, I never thought about it. But seeing code de-nested, I agree. It's far more readable. I will most likely incorporate these strategies into my coding technique. Thanks!
Another approach that can help with this is defining some local variables that act as flags and get the bulk of your computation before the final logic. That's one strategy we often take in Lisp. Defining predicate functions will also help greatly, Accessory functions can help too, even if they are only called in one function it'll help with readibility. Any conditional more than 3 levels deep is due for refactoring (with exceptions of course). EDIT, Whoops, I didn't wait until the very end of the video. :P
I’m beginning to appreciate this. I once held the belief that each function should have just one return/exit. I don’t even remember why; I think I read it somewhere/somewhen? It makes for some complex nesting and code paths!
The important thing is to split code into logical blocks rather than to mindlessly follow a style guide or a linter. The % 2 example is an example of doing this wrong. Extracting there adds no value and makes the code harder to follow.
So crazy, some people (like myself) are natural “never nesters” without even realizing it, just the way our brains choose to tidy things up or preplan ahead to write them in that order. I find it so fascinating that you were able to explain this “behavior” or “way of coding” so eloquently and in a fun way, especially for those of us who do it unconsciously or without really thinking about it! Thanks :)
Add me to this list. I have never heard the term "never nester" before this video, but this pretty much how I write my code. Quick returns first (unhappy cases) followed by readable work. I also try to avoid single-use functions/methods, though. It probably comes down to which annoys me the least, case by case
@@philrod1 I don't agree about single-use functions/methods, when you first do it, if it's a good one, it's likely possible to use it for other cases in the future, even if there's only a single case when you write it
A former colleague of mine used to nest entire programs in an if (true) {} at the top of all his code. Someone spent a day while this colleague was on vacation trying to understand why one of these programs was indented one additional, unnecessary level in all places. When the problematic colleague returned from vacation, he explained that this pathological behavior was simply for debugging and as a “safety switch” he could set while programming. Our boss gently but firmly ordered him to discontinue this practice.
honestly an indentation size of 8 is great even if you aren't doing it for the purpose of reducing nesting. it's incredible how much the boundaries between different sections of the code clear up. functions and methods become sort of like section headers in a document, and you can find everything easily.
I used to be all about spaces, but switched to preferring tabs just for this reason: the user can define how wide a tab is displayed. If they feel more comfortable with compact indentation, they can set the tab width to 2. If they are like you and prefer nice wide margins they can set the tab width to 8. But the code structure doesn't change, just how its displayed based on user preference. And I think that's one of the keys to happier programmers: choice without toe-stepping.
@@error.418 I would add that most correctly configured IDE should allow for changing the visual appearance of indents, even if they are spaces. I still think think that semantically tabs make more sense anyway (space separate words, tabs were thought up for aligned indentation) though, but both work practically.
@@error.418 Might be a text editor thing then maybe? I'll try to come back with an example later this week, though I think some of my old coworkers did have that.
In a year or two (even less maybe) when this guy blows up, these videos are going to be recommended as part of introductory CS courses in university the same way that 3Blue1Brown videos are for math courses at my university. Glad to be an early adopter!
Definitely a fan of inversion, and largely for the reasons you described. Early returns are powerful and I've seen programmers who forget they exist! But I think extraction has a problem: it forces additional abstraction that can decrease comprehension. This particularly happens when the extracted code is just one line and is used just once. The problems of making clear names has been mentioned, but I also usually see people doing extraction to keep function line counts down, which feels like the wrong motivation.
Any code which does a separate step/piece of the functionality is an abstraction, even if it is inlined. It doesn't simplify anything if we keep it inlined and unnamed. The tricky part is to correctly identify those pieces and layers and separate them in a coherent and reusable fashion. And naming them well, of course.
I used to make functions a lot when I was a solo dev mostly. Didn't need to read others code often. However, now I work on a large project with many people and I DESPISE unnecessary functions. So many functions that only do one thing and many are nested making it very hard to keep track of what you are reading. I much more prefer very long functions with comments now. Also less classes in general. I shouldn't need to go on a journey just because someone felt they need to name a piece of code three times.
Agreed, but one thing that helps is have the main orchestrating method either well commented, or use method names and var names that define what's going on. The main method would have a good comment as to what is going on, the inner methods would not be as important with regard to method name (but I agree all the sub-methods can hide meaining). Coming up wtih good names can be 50% of the battle.
My personal test is if I want to copy paste some lines of code. A single function call, copy away, but with multiple lines, you're probably doing something, and you might as well have a single location to debug said thing.
This is how I've been coding for idk, 6+ years. I've preached earlier returns to dozens of programmers. Good stuff. I hope many watch this and it sticks like a thorn in their brain.
I actually didn't expect to get a whole different perspective watching those videos, and even worse, I actually expected them to be a bit boring. Man, how I'm I wrong about my suspicion and false judgement. Your videos are well explained, intuitive, fun, professionally made and very useful to the point that I have never got any similar valuable info in my college so far. So, I would like to thank you for your hard work, and please do keep it up! :)
I think the only thing that someone needs to "keep up" is their studies in college. Where did you learn to use commas and what monstrosity didn't teach you about run-on sentences? Commas aren't periods, my fellow internet user.
@@Dyanosis I'll give it to you, you are kind of right. I haven't really put any effort into my punctuation, as I am completely aware that I'm not writing an executive letter but a RUclips comment. Otherwise, you would've not seen me writing like that, my fellow internet user. However, I do thank you for your comment and valuable advice.
Huh? Turn's out I'm a Never Nester also. I didn't even know that was a thing. Learn something new every day. 😀 Also, it really helps you follow the Single Responsibility Principle on a per-function level, which inherently helps make your classes follow it also. 😁
I definitely find it easier to understand if there's a chunk of validation at the top. I also remember reading that some branch predictors assume the last chunk of code in a function is the hot path if the branch predictor has never seen the code before. So this might also help with performance.
I am also a never nester! I hate nesting stuff, and always have, and employ both these techniques constantly. Nice to get names for them, and very pleasing seeing all that code organized
Great advice! I am definitely in this camp. I also like to minimize “if/then/else” constructs finding other mechanisms like call tables and switch statements to be cleaner. Thinking about code in terms of a finite state machine helps with both of these problems.
I once nested 20 layers deep. Much like inception, time reading the code becomes exponentially longer the deeper you get... It's a miracle I'm even here today.
I worked at a place that had SQL stored procedures calling stored procedures sometimes 10 or 15 deep. It was nuts.
Welcome. It's good to have you back!
Perhaps the dream became your reality.
it's a learning process. We all start there
LMFAO
A few of you, rightly so, pointed out that the inversion at 2:20 should be `
You are doing so well! I can already see you growing into a popular channel!
don't worry, people on the internet are always nitpicking like that. Your editing style and video structure definatly make up for minor mistakes like that haha.
Posted the same prior to reading this. Deleted mine.
My day was ruined until I read this.
@@beqa2758 I don't allow conditionals without braces in my projects. I don't care if the braces are on the same line as their contents, but you _must_ have braces.
The reason is very literally almost every time I have seen if or else statements without braces, I have seen another person break them unintentionally.
I will admit to being an out-of-control nester, but I'm recovering. It really does make code easier to understand.
Primer! ❤
Also I didn't know I was a never nester. I just thought I had to code like that. Also I am a strong single liner because I hate scrolling my code.
Oh boy how did you end up in coding? Can you make a video about it if you are able to?
and easier to test
It's a learning process. We all start there
I think this is actually crucial to teach to beginners. Holding exceptions and conditions in your head makes a code base really hard for others to read.
Not only for others but for oneself.
I find myself comming to a week old code, and wonder who nested all that code.
I'm quite glad I came across this video...
Imma be honest.
At least in my situation, I fail to see any reason why any of this matters.
I'm a solo dev, nobody else is working on this code but me so nobody else is gonna be seeing it but me.
And I'm looking at this "un nested" code and it's honestly HARDER for me to read than nested code.
The unnested code blurs together, while nested code creates immediate, obvious separations.
@greyrifterrellik5837 so you can't see why others would prefer this way?
@@chaosa928 Well I mean I can't see how it's easier to read this way, which is the claimed benefit, so no not really.
Just wanted to point out a common error when inverting logic. At 2:20, the inversion technique is demonstrated. Before, the "happy" case was (top > bottom), which made the "sad" case (top = bottom). This creates a classic "off by one" error. In the case where top and bottom are equal, the former returned 0 and the latter returns one iteration of the sum equal to the result of filterNumber().
I know that isn't really the point of the example, but you do need to be careful when using logic inversion.
I knew that mistake would probably happen before he made the change. Don't ask me why. Pls don't.
An excellent point and a mistake I've seen (and made) countless times.
Like in welding. Beautiful doesn't mean good.
True, a lot of people get inverting boolean logic wrong. Another one I saw recently was inverting (a && !b) to (!a && b). Instead, first invert the whole expression and then apply De'Morgan's law: !(a && !b) => (!a || b).
In this case, top==bottom would return 0 too, so it isn't logically incorrect. but yeah, I get your point.
Update: He fixed it. Nice! :-)
I'd suggest using a lighter background when highlighting code and darkening the rest instead of blurring the rest, so that it's faster to find the line.
true, my eye went to the animation blurring then I had to scan around.
Same for me
Came here to say this. Darkening (and blurring) the “unimportant” code would improve readability.
Yes please
A highlighting rectangle will help too
I just want to applaud the visual design of this video, especially the code. The blurring, previewing important definitions, uncluttered diagrams, use of color, typewriting, introducing motion to draw/hold attention. It really communicates the intent of the code better than I've seen anywhere else.
Seconded. The whole presentation is incredibly elegant.
It doesn't seem like much but managed to deliver on a lot of aspects.
Subtle but refined, directing the viewer's attention while not distracting and letting them think on what is being said.
@@LinkEX I do have to though sometimes when the code was blurred it took me a while to find it. idk if thats just be being slow or perhaps an optimization could be made there. Maybe a little more contrast in the part thats not blurred, or a small arrow would help
@@AliAhmad-qy2sf Thanks: You said what I wanted to say. Thanks also to LinkEX too for the thoughtful comments.
Yeah, it is a good approach. Code in videos are so boring, even for people who enjoy programming, it is a hard task to make them interesting.
Agreed. The visual aids make it very clear to understand. Excellent job.
Nobody tell this guy about HTML
xD
Yea
Or XML.
Or JSON.
Well yes, but in modern frameworks you can easily create components.
In Angular, you can even create templates in the same html file - which is similar to extracting a function and also saves you all the boilerplate to create a new component.
Probably you cannot adhere to the 3 deep rule everywhere, but it's not impossible.
This guy's focus is on algorithm, not data structure.
On instruction flow, not relation map.
Out of control nesting is definitely bad but sometimes when the guts of the code are extracted into smaller and smaller functions it also makes following the flow of the logic hard as well. You still have to try and keep track of what the parent of the parent is doing. At that point you are still in a nesting scenario, you’ve just shifted into smaller chunks of separated code. It’s a fine balance. Guard clauses help to get rid of unnecessary if/else conditions.
Facts. The flow of logic seems much more straight forward when nested.
@@bufdud4 less straight forward*
@@bufdud4 nested code is often untestable because you don't have control over it. When you have code in small chunks, then it becomes easier to test individual components. Test driven design is great because it forces you to design testable code in the first place. As long as the interface is testable, then there is nothing wrong with nesting, but you need to have tests in the first place, which rarely is the case.
@mrosskne If you ever worked in a legacy back end system. 95% of things are the nested kind. Way easier to follow. Especially if you have 30 scripts to get by the end of the week.
@janiscakstins2846 That sounds crazy, any piece of logic should be testable. If you can't get to a nested part of code in your tests, either the test or code is flawed.
Just code for clarity. Sometimes that means breaking complex methods in several simpler routines, other times that means combining several micro routines into one larger method. Sometimes nesting gets really confusing, other times nesting adds clarity to the code. Sticking to any one type of code structure religiously just adds complexity in different ways.
Being dogmatic about anything is wrong. Code for clarity, agreed!
Thank you! Exactly. Nothing is black and white. Everything has its place. Even a few nests deep...
I've never, and I mean _never_ seen clear code that is nested more than 4 deep. At 4 deep it becomes super tedious already. There is a reason they made this a kernel rule. I highly suggest you increase your tab width in your IDE of choice and you will quickly realize that there is almost always a way cleaner and easier method without nesting. It forces you to code better and come up with cleaner solutions, which ultimately leads to easier to read code that is much more maintainable.
Gate clauses are a very easy example for that. Having a nested code means if you come across more exceptions your code needs to handle you need to escalate nesting into deeper and deeper parts of the code which will eventually lead to refactors anyways.
Instead, having gate clauses at the top makes it a million times easier to add exceptions and still maintain readable code.
Like sure, your 4 deep nest might "not look too bad" now, but wait until 16 different people have started adding conditions and you'll quickly end up with more nests than you can count. Nesting - not even once.
@@Finkelfunk
I'm 5 deep right now, about 100 lines of code. It's quite clean, clear and concise. Each section of the code is purposefully labeled and does it's own little bit just fine. I think this instance, tho, is one of the few times this makes sense, and that's a select/case, where the logic going into the select and under each case needs to work individually, so it's already naturally broken into sub functions, that's just how cases work. I guess I could break out my Line function and my Square function, etc. but there is no need, they are all right here, easy to see, edit and compare.
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” - John F. Woods
That quote came in my mind while reading your comment.
It may be ok for you personally but if you start working professionally you have to write clean and maintainable code because someone else could be forced to maintain it. Nested code in switch-cases is always smelly code at least.
Extraction should be done carefully. I've often seen code with lots of once-used & poorly named extracted functions, just to hide nesting. So the code is just as nested as before but you have to jump from function to function to understand whats going on.
Truth should be shared 👉 The Connections (2021) [short documentary] 👀
I swear. What people don't realise is any added function is one extra layer of indirection. So only add a function if you have a good reason for it. I have seen people adding functions just for the sake of keeping method under a few lines
@@nazeefahmadmeer I find that the right limit for extraction is when you start struggling to name the new functions. If it does not bear any meaning, then it probably does not deserve to be a function on its own. Also, respecting the single responsibility principle helps a lot (in the OOP paradigm, that is). If a class has too many methods, and you can't easily find your way around, it probably does too many things.
The problem is not extraction but the poor naming. Yes, coming to with good names is sometimes hard but if you don't like doing that, maybe programming is not something you should do.
Never extract something that’s only used once is my philosophy. A function needs to be called at minimum twice before it deserves defining. If it were up to me, thrice.
I'm a huge fan of "guard clauses" (simple if's at the start of the function which basically just return because the return value is obvious due to a special case input). So I was VERY bewildered when in my early programming classes (In 2020!) it kept being blanketly repeated that "a function should never have multiple returns". If you have one return 50 lines in and another 100 lines in, sure... but then you probably haven't split up the function correctly to begin with.
You can write much simpler and cleaner code when you filter out all the special cases at the beginning as much as possible.
One simple example would be a power function:
pow(base, exponent)
if(base == 0 && exponent == 0)
throw Exception; (if you'd want to treat it as undefined)
if(exponent == 0)
return 1;
if(exponent == 1)
return base;
...rest of function to actually calculate the power
Now this definitely goes against a different technique called branchless programming, but unless your function is called very very often in some heavy computing task, the readability far outweighs the speed.
The best thing about guard clauses is that they are a perfect match to how you should be thinking when designing your program and writing your tests for it when following TDD practises. If you are designing complex functionality, the way any business describes it to you should be massaged into first the error conditions, then the happy path once all business concerns are satisfied.
This lets you go and immediately write a test structure from the ticket, and then implementing the test structure automatically creates a flattened guard clause implementation.
I find it incredible how many professional programmers haven't figured this out yet.
The problem with branchless programming is, it is often written in a way that you need to write comments to explain what kind of monster you have created, because the next programmer who will be taking up your project is likely more stupid than you.
"Never have multiple returns" was a good rule for the time. Because way back a "stack trace" meant manually adding debug lines to each function: "entering Q" and "leaving Q with value X". Return-from-middle (which is what I've heard it called) made old-time debugging extra-tough.
So you may have been taught by someone who got their first job in 1982 and never worked anywhere else.
@@owenreynolds8718 Now that you put it this way, it does look quite logical to have this rule.
Plus 1 for Guard classes. Had a coworker that insisted on writing out exception handlers every time. Needless to say his code was a mess.
I think that decomposing your mega function into a bunch of single responsibility micro functions definitely makes unit testing a lot more straightforward. But one thing that I have found is that I still need to write the mega function first. Once it's all down and working, I can then refactor it into something more sensible, but trying to write out all the single responsibility functions first, takes me more time than doing a "first draft" and then editing. It's like premature optimisation. Don't do it until you know that you have something you need to fix.
I'm also a big fan of kicking out bad paths at the start. Not only is it easier to read, but when you discover a new unexpected bad path, you can just add another concise return block instead of having to nest something deeper in the function, and possibly missing a spot where you also needed to return rather than execute.
Paul Graham (I think) once wrote: good code is like good prose: it requires constant rewriting.
I'm the complete opposite. I find it way easier to create a main function and complete that with unimplemented functions and then go and implement these one at a time. This way I can start by planning out the overall process of what I want to do and then handle each individual implementation of sections independently. Sometimes if when I then implement a function and it ends up just being a few lines of code I'll then move it back it back to the main function - but I still got the benefit of using placeholder functions to write out how I would approach the problem
Or you can ask GPT to de-nest
I find it certainly depends on what I'm doing; currently, I'm working on a PHP project with a bunch of POST forms, and the flow of those is generally extremely predictable, so I've been creating a main function with unimplemented functions to start off with for that. But when the structure of the task isn't so predictable from the start, I do generally find that jumping in and getting my hands dirty with a rough draft, and then refactoring that rough draft as needed, is a better way to quickly start turning that structure from nebulous to definite so I can start encountering and resolving unknown unknowns.
I feel like once you get more and more experienced you don’t need that draft function anymore. But it might be just a way that you think / work it out. I hardly do it anymore unless really rapid prototyping a method or several implementations. I almost instantly know what piece of code should be a private method or even its own class.
I'm a never nester myself and I've used the extraction and condition inversion techniques many, many times. I just wanted to comment and say I found this video very pedagogical. I really liked how you visually showed how you refactor a function (with extraction and inversion) using animations rather than just live coding it. It's more work for you to edit that stuff but I think it's easier for beginners to follow along.
The only thing I would suggest you to change is to more clearly highlight the code you're talking about. The blurring didn't quite work for me all the time and I had to really concentrate to see what parts where blurred or not. Maybe it's just me and it's time to get some glasses though. Over all a very nice video!
Agreed. I like it when they grey out the unhighlighted code, like a comment would
There wasn't any blurring in the video - it was just your eyes!
just popped here to say if you have too many nests, then yeah, you are screwed,, also just a question for all other folk down here: branched/branchless?
I agree, my eye was drawn to one of the blurred texts first, then I'd see the other blurred text, then I'd be able to focus on the code he was talking about.
my brain were exercised well, but my eyes were straining hell
100% on what you said about separating the error paths from the good paths, it makes it so much easier reading the code months or years down the line. That approach really shines when writing code: you think of the error conditions first, handle them, then continue writing the good paths, but with a lot less to think about.
trouble is when you need to follow for example MISRA and there you have: "A function should have a single point of exit at the end." - you can use only one return in function
@@gangulic That's a silly rule, but if it must be followed, you could always utilize some sort of "proceed" boolean. At the top of the function, set it to true. Do your input validation steps, and if one of those steps fails, set the proceed boolean to false. When you get to the meat of the function, check that boolean first and only perform it if it's true (you can also utilize this boolean at each validation step after the first). Does this make it more complex than simply nesting? Maybe, maybe not, it depends on how many conditions you are checking, but it should satisfy the rule and allow for some de-nesting. Basically it turns a bunch of nested if statements into a flattened list.
@@gangulic does exception counts as a point of exit? If you can't use those, then "do {} while(0);" and use break instead of return if you want to be polite or use "goto" if you want to send a message on what you thing about this rule.
@@gangulic This is silly. Not sure why people teach this. Validating inputs and some other data before proceeding to calculation should end the process/function as needed. This can be in the form an exception, null/empty return, just exit/end the function, etc. Depending on the situation, the validation could be its own function and the main function can proceed based on that output.
Recursive functions have multiple exit points. I have yet to see one with a single exit point that wasn't poorly written or convoluted.
Someone else made a comment so I going to quote it: "Code for clarity".
And I'll add to that - add comments, if you can't read minds don't assume I can read yours.
I particularly like Extraction because then you can give the function really descriptive names, then when you read thru the top function it's almost like you're just reading a book, instead of interpreting the logical flow of the code. I often combine Extraction and Inversion, in that after I invert, I'll extract the inverted code into its own (descriptive) function.
Choosing descriptive names for your variables and functions is a big part of making code easy to read. The less thinking you have to do to understand what a variable is for, or what a function does, the better.
"you're just reading a book"
Thats what I expect from my code, consistency and appropriate semantic naming are key. The code should be as readable and understandable as a book telling a story.
And that’s why you shouldn’t need comments in 99% of all cases. Extract all logical units into their own function and give it a descriptive name. Much better than lengthy comments that never get updated when someone changes the code.
Biggest thing with this is to group the extracted methods logically. I can't tell you how many times i had to ctrl+click a method only to end up at the bottom of a 5000 line file because the developer who implemented new code couldn't be bothered.
It's also makes the call stack much more informative when an error occurs. Easy to see "error in the foo method" vs the 1k line method had "object not set to an instance of an object"
Reminder to not go overboard with extractions as it creates a readability issue having to excessively jump from function to function. Especially if they’re not placed near each other and/or poorly named.
Skill issue lool
(Skill of your IDE, that is, ayy)
Even the VSCode basic C extension will show tooltips when you mouse over a function that show the signature and grab any preceding comments
Rest of this is a stupid rant I wrote because I’m procrastinating studying, but I want to post it anyway. Sorry eh
Also disclaimer, I’m new, and the fullest featured IDE I’ve used is IntelliJ for like 2 months
I just think it’s kind of sad and ironic how much bending over backwards, or even thought at all, still has to be done in the name of readability, when this is literally how you create a UI. Like some people refuse to admit they’re bound by the same kinds eyes, hands, displays, and memories as someone doing anything else on a pc. Like anything beyond static, possibly colour coded text on solid background is some perversion. Like you should be able to just understand and hold the whole thing in your head, who needs eyes to know what code means?
“What, a function reference that drops down when you hover or click to show you the definition? Comments being extracted and shown to the side or on hover or any other way than standard inline with the code? Shaded areas instead of a squiggly people can’t agree on because it wastes space wherever it’s put? Brackets that resize like you would on paper instead of alternating colours and fucking with the rest of the colour code? Any other idea you’ve had yourself for rendering the code window and then forgotten?”
“Hell naw that’s for bitch-ass kids, I will continue to deal with the same basically-notepad limitations and inconveniences that my predecessors did. While I work on the dynamic UI for this CAD tool or map visualizer or browser game or whatever.”
Like, is scratch not presented the way it is so that the reader can intake information visually, separate from encoding the information in text on the screen you have to read? Isn’t the readability issue one of us being limited in how easily or quickly we can understand the code on screen by reading the text, and wanting to increase it by being able to infer program behaviour or structure based on visual structure? Just feels like it sucks that in the age of OLED, VR, web frameworks, chatGPT, etc,
some cool tabs can still be peak fkn technique
@@uusfiyeyh hmm interesting idea
Not if your name it properly
It is very frustrating when trying to debug someone's code, you jump from one function to another, to another file, and then you realize that you are looking at the wrong section because the errors is in the previous step that you have to find what is calling that function and what is calling that function and so on. I come across this too often as a web debugger and mod creator, so thanks, i hate it
@@nik325007 You can't expect engineers writing 8500 line long functions because you "code" in Notepad++.
Ever heared of an IDE?
The "validation gatekeeping section of the code" can be summarised as "guard clauses". We use it all the time, especially in the backend of our stack where requests must be validated. I didn't think I was a never nester before this video but I've got a bit of a problem with long line lengths so avoid unnecessary nesting if possible.
+ also known as the “early return” pattern
Dad d
I love how Swift has an actual ‘guard’ keyword, that makes it just like an if statement, but forces you to return as part of it
I have always called it "short circuiting". nice to learn other vernacular
To add to the "fail fast" concept, I will typically go a step further and put my guard clauses into a guard function, abstracting all the negative scenarios away.
As a comp sci student, I'm loving these videos! These topics aren't really talked about at my university, and the way you present them really makes me consider things from a different perspective. Keep it up! :)
luckly my uni has one non mandatory class that talk about refactoring, pattern and code smells
Many university teachers have never worked in the real world, so they simply dont know
Same
@@Lucas-hh4oh do you have a recommended public resource for that topics? appreciate it if you have something.
@@christopherw1248 refactoring guru (website, I left the dot out just to be safe), SOLID-principles, object calisthenics are things worth looking into
I was already doing this but not aware of all the benefits outlined here. I've always used extraction and inversion (without knowing their names) simply because it's cleaner and easier to read. Great video.
+1 :)
I used to be a never nester myself. Now I’m more of a it-depends-on-the-case nester. Does extracting code into a separate function make it easier to understand? Well, sometimes. Do early returns make it easier to focus on the actual core of the procedure? Again, not always. It’s good to know these techniques of extraction and inversion, but as everything else in programming, you shouldn’t use them just because you can.
That said, the video is really well done! The animations make it easy to follow all the moving parts, and the explanation is clear and concise. Great work!
Could you elaborate? I would argue that if you can you should. Reading heavily nested code sucks balls. Reading code can take quite awhile for you to finally absorb what's going on, and minimizing nesting helps keep the high level logic in the foreground for the readers. Things like guard clauses just seem like good coding practices, up there with making sure you comment your code and try to use descriptive/accurate names for variables and functions.
I said in my own comment, and that kinda answers Ade's question, so I'll repeat it here. It does depend, a lot. Sometimes, but not always, extracting code to a function can help, provided everything still remains in one file or one compact section of the file. I've had the unfortunate experience of trying to dig around programs and, in order to understand the full procedure, I end up clicking around to find the function declarations. This is the worst for triple or quadruple nested functions, where you basically have to scroll up and down constantly just to follow the flow of code. Nested code might be bad, but similar to reading a book, I'd rather stick to one or two continuous pages instead of bouncing around dozens of pages just to read a couple sentences here and there. My general rule is that it needs to be a big chunk of code to be it's own function, or so self descriptive and simple that you don't need to keep the implementation in mind. A good example of the big and small functions would be the big "handle http error" and "is even" in the video. However, sometimes I see functions that are two or three lines, and aren't so descriptive that you actually need to keep the implementation in mind everywhere you see it used. An example might be an error logging function, which depending on a boolean variable, may call another function that can affect the code going forward, like terminating the program or changing a variable being used. In that case, it would definitely be better to either split that function in two and just call what you need, or take the nested function call out and call it when needed. Or, best idea, don't use the function if it's only called in one place and just leave the code where it came from originally.
@@ade8890 I definitely think guard clauses are the best way to go, and should be used whenever possible. I can't really think of a reason to not invert code like that. Worst cases, where there's a lot of clauses interspersed between big sections of code, adding a comment just before some part reminding you "Hey, the number cannot be 5 here anymore, that was covered above" is great. I've written code with over five clauses like this, with enough code between to push it out of view, so having those comments to remind me and others is very helpful. This does get a little hairy when you have multiple different variables being filtered, so instead of "number isn't 5 here" it'd be more like "the number isn't 5, the object is null, and this enum color is either RED or BLUE now." But still, that's where I write the comment stating that. Good IDEs also understand this in static analysis, so I sometimes use that to check myself by testing a variable, and pray the IDE tells me "that is always true/false here."
@@ade8890 For example, you know which parts of the code get executed when everything goes well simply by looking at the indentation. Having a single exit point is tidy and organized.
Also, sometimes it’s difficult to give a name to a specific part of the code, so it’s better to leave it inlined without a name. A name that is unclear and/or inaccurate can make things worse than just plain inlined code.
At least, this is what works for me. It’s how my mind thinks and processes information. But that might not be everyone’s case.
@@almicc " Sometimes, but not always, extracting code to a function can help, provided everything still remains in one file or one compact section of the file."
Yeah I agree with this. You shouldn't extract code to a separate file if where you are extracting it is only ever used in one spot.
" I've had the unfortunate experience of trying to dig around programs and, in order to understand the full procedure, I end up clicking around to find the function declarations."
I'm not sure if I understand the issue here. Most IDEs allow you click into a function call to auto-navigate to the declaring section. And aslong as that function is only used once, you can click the function signature to be brought straight back into the calling body. This gives you the best of both worlds, where you can click into more specific logic that you need to understand, or you can jump back out and just focus on the high level details.
The blur is really cool, but I think also including the more traditional background highlight rectangle is still helpful because it pops out more.
(in other words, maybe it still looks cool using both techniques at the same time!)
I totally agree!
or just blur more
Agree on this one, the blur is slightly harder to follow for me
I've love the way that you animate code code changes. It makes it really easy to follow.
I like the disgust-o-meter, it is often how I decide whether or not to request a change on a PR and now I've got a name for it.
This was a nice way to advocate for the Single Responsibility Principle, better than the 7-line methods Bob preaches about.
I very much wish Visual Studio had a smooth scroll; especially when navigating to definitions in the same file.
I've never heard of the "never nester" concept -- but take "single responsibility principle" very seriously. Just checked my code and it looks like never nester.
I mean... not really. Single Responsibility is for classes, not methods. And while you COULD apply it to methods, it doesn't always work out as well as you think.
However, it's much easier to enforce it with classes because classes should be performing 1 type of operation and be handing off other operations to other classes.
@@Dyanosis You're right. I misused Single Responsibility here. It is supposed to be "this code is fulfilling one person requirements, and no one else's."
I was looking at "this code should only do one thing and do it well." I suppose both principles have the number one in them, but they are very different.
Good catch.
On a PR?
At 2:24 in your inversion, "if (top > bottom)" doesn't invert to "if (top < bottom)", that potentially introduces a bug. It probably should be "if (top
I'm an old school programmer that cut my teeth on COBOL and C. We were taught to only nest to 3 levels and use functions to simplify code. This is how I still program today nearly 40 years later and works well for modern scripting languages such as PowerShell which I use extensively.
This is interesting. 40 years ago that was the time when compilers did not yet produce good code and every avoidable If condition and every function call was expensive and function inlining was also avoided because memory was also limited.
In order to save computing time, it made sense not to outsource performance-hungry code to functions and to nest If conditions so that the code was as efficient as possible. A multiple nested if else construct was often more performant or meant fewer steps for the CPU than executing each IF query multiple times just so that you don't have to nest.
How did you manage to nest on only 3 levels? Was a fast runtime speed not a requirement for the code? Did you have plenty of memory available?
Today that may not be such a big deal, the compiler optimizes it all away anyway and inlines the functions again, but it just wasn't like that in the past.
@@OpenGL4everingenuity
I go 2 levels. 3 only if absolutely necessary.
@@OpenGL4ever 40 years ago was 1983, so not quite at the punch-cards age. 😉 Compilers didn't have quite the level of optimization they do now, of course -- four decades of improvement are a beautiful thing -- but they could handle function calls and conditionals to a reasonable degree in most languages I'm aware of. Of course, if you were writing highly-responsive or real-time code all bets were off, just like today, but for most purposes, human readable code was a goal then as now.
@@autochton The Microsoft C compiler was improved a lot between the release of Windows NT 3.1 in 1993 and Windows NT 3.5 in 1994. That resulted in a big performance improvement of the newer Windows NT 3.5 solely because of the much better compiler.
This was 10 years later than 1983. The C compilers in the 80s where still in its infancy.
I totally agree. This was a nice example of refactoring code. When I start a new piece of code I always force myself to not go into the details right away but create a fundament that outlines what I'm trying to build. Extracting routines and give the names of what I need to do in them and handle any results. This way I can start to think about the bigger picture before diving into the details. It helps me tackle the whole problem more easy. When I get to the nitty gritty of the details I may need to refactor some of the outline I made but most of the time it is not needed. Chopping the problem up like this lets me focus on each little detail independent of the whole problem. When I find that an extraction is becoming too big I create a little more outline before diving into the details again. Works very well for me. Sometimes I find that the way I thought about the fundament is totally wrong. No problem. I didn't write much code so far, so starting over is not very costly and I've learned a lot setting it up the wrong way.
Dude I have been teaching CS for 10 years and honestly this is the best piece of communication on general best practices that I have ever seen
What? Did you just say? What the what what? What was that? Did you say what? 10 years and this garbage is the best? And you... you teach? 10 years and you teach for 10 years and this garbage is the best you have ever seen and you teach.
That is scary. I guess so your students must be hyper dense.
Job security for me, I guess. No competition. Lol.
10 years huh. And this is it.
@@xbzqCongratulations, you win the flexing competition, here's some internet validation
@@xbzq brother is stuttering in text. There's no need to be scared
@@xbzq are you okay
@@soupman8165 Why you ask
In general I agree. I especially like early returns and splitting conditions/validation from operations. But sometimes nesting is better. For example when you have algorithm that is inherently nested (like you have 4 tight for loops indexing some arrays etc.). Better to have the whole looping/indexing in one function and extract the "doing" into another function than to extract part of looping to another function. Because it makes the algorithm less clear and if you do 4 loops with complex indexing you better be aware what you're doing.
I think the problem is that you wrote an algorithm that requires 4 loops.
@@pizzapunt3960 there are algorithms that inherently require 4 or more loops. Some faster versions of matrix multiplication for example. Or pathfinding.
@@ajuc005 they don't need nested loop. If you are using nested loops you're being slow. You need parallel processes (might be called differently, every programming language implements it differently). If you look at a* for example, doesn't require a single nested loop. It's not something inherent to the algorithm but to your lack of skill.
@pizzapunt3960 you are aware that some environments disallow multi threading, yes? Have you ever even heard of an MCU?
@@cantthinkofaname1029 Who hasn't heard of the "Marvel Cinematic Universe", at this point? :P
I've generally found people call the block of returns at the top of a function after inversion to be "Guard Clauses" I've always found them to make everything amazingly clear just like the content in your video shows and suggests.
Keep up the good work, can't wait to see more.
This style of coding really should be what is taught, but a long time ago the idea that you should only return at the very end of a method was very popular. I'm sure there may have been a good technical reason back then, but it is an objectively worse way to code. Today we don't have a valid reason to stick with the only return once at the end dogma.
@@evancombs5159 I think it's a misunderstanding of the "single entry, single exit" principle, which is actually something that object-oriented languages automatically enforce: that you should only enter a function or routine at one point (the top) and only exit _to_ one point (right after where you came from). Older languages like BASIC allowed you to jump to any line by using a command such as GOTO or GOSUB, or by changing the pointer stored by GOSUB to tell RETURN where to jump to -- so in the case of BASIC, the principle meant "don't use GOTO; use GOSUB only to jump to lines immediately after a GOSUB, RETURN or END; don't change the RETURN pointer; and RETURN from every GOSUB".
The one thing it's still useful for is freeing allocated resources (which you have to do before any exit) -- but Java lets you use "try" and "finally" instead of duplicating that code, or allocate the resources in the opening statement of the "try" if they can be freed automatically when it's exited.
I've heard it referred to as a "gauntlet pattern." I swear by it.
@@evancombs5159 In my company, I teach beginners the guard clause method because it makes a massive difference in readability. I tell them that else should never be used.
"Guard Clauses" that's a great name for it. Super great pattern, although I notice I implement a second tier of them within a nested scope if the right situation calls for it
One other positive to this approach is that because you extract pieces of logic into functions, you can give them a function name that describes the logic. This alone goes a long way to making your code more immediately comprehensible.
Sometimes this makes sense. And sometimes it doesn't.
Also, every function call has significant overhead: Saving the execution frame of the calling function onto the stack, and then throwing all those values onto the stack for the function being called... then when returning, restoring the execution frame from the stack to resume the calling function. Do that inside a loop, and the execution overhead can be ENORMOUS.
That's this being a hard and fast rule that should never be violated is retarded.
And let's not forget how much nesting of decisions structures there can be in an interrupt handler. Putting a function call INSIDE an interrupt handler is equally retarded, because interrupt handlers should be as fast as possible. If you can't understand the code, then you're not commenting enough.
@@akulkis "that should never be violated" → As they say, never say never. In art and engineering I think rules are more like best practices that you have to learn first before you understand when to break them.
In a lot of cases, I think readability (i.e. maintainability) is more valuable than cutting edge performance. If performance is an issue there are likely algorithmic changes you can make before changing whether something is extracted out into a function or not.
About comments, I'm personally persuaded by the idea that the code should be as readable as possible without comments. I.e., if the code requires comments, that's a sign that maybe the code is getting quite complex. Sometimes that's unavoidable, but a lot of times it isn't.
It would be one thing if comments were free but they're not: they require diligent upkeep or else they're liable to confuse rather than help. Learning a good balance between code readability and commenting is just another skill acquired on the journey of becoming a somewhat competent developer.
@@akulkis most compilers inline functions and even inline loops anyway.
@@shugyosha7924
"that should never be violated" → As they say, never say never. In art and engineering I think rules are more like best practices that you have to learn first before you understand when to break them.
-- that's why I wrote that saying it's a hard and fast rule is retarded.
@@shugyosha7924
"About comments, I'm personally persuaded by the idea that the code should be as readable as possible without comments. I.e., if the code requires comments, that's a sign that maybe the code is getting quite complex. Sometimes that's unavoidable, but a lot of times it isn't."
The GREATEST thing that learning to program in various assembly languages is this:
1. Write the comments for the function BEFORE writing the function itself.
A. You are creating a "mission statement" for that function
B. You are going to describe the strategy used to go about solving whatever that function is supposed to use.
C. If you can't describe the algorithm(s) in plain old everyday language, there's literally no way that you're going to write it in code correctly.
Too many programmers treat commenting as an afterthought. In assembly language, you literally can't get away with that. You're going to comment before you code, and then do more commenting while you code. ... every single line.
I haven't written anything in assembly in decades, but those habits described above have always garnered heartfelt appreciation from others who have had to read and/or modify what I wrote.
Remember this: "Self documenting code" is a myth. Yes, use meaningful variable names, but believe me, what one person thinks is clearly readable without comments will leave someone else scratching their heads. Suppose you have a for loop in C/C++, and for whatever reason, you do this
loop_variable += skip;
If you're not commenting SOMEWHERE (I would suggest both before the loop AND next to that line) why you're screwing with the loop control variable, SOMEONE is going to be confused, and chances are, you aren't going to be around to explain it. Or its' 20 years later, and your thinking and problem solving style has changed so much that you literally cannot understand why you yourself wrote something like that.
I've never seen source-code with too many comments. I've seen source-code with lots of comments that are unnecessary because they tell you what the code itself tells you:
int counter; /* counter is an integer */
But that's a different problem -- it's just being redundant. Comments should generally focus on answering the question: "WHY? " and occasionally on "How?" (comment blocks for functions, and generally longer for loops and long if statements and long assignment statements ( x = LOTS of factors and terms)) and occasionally WHAT (data structure declarations)
And remember, the more CLEVER your code, the more comments you need so that someone can understand what you're thinking without you being there to answer their questions.
Compiler writers added commenting ability for a reason. If it were simply a matter of teaching "write self-documenting code" then we wouldn't need comments. But the fact of the matter is, a good portion of code is NOT self-documenting, and for most programmers, it's a FAR larger proportion of their own code than what they think.
I'm all for extracting code into functions as long as these functions are given names which clearly convey what they do. If they are not named properly, I would rather have the code written out explicitly than having to jump though the functions in order to find out what it is actually supposed to do. If one finds the process of naming the functions difficult, it might be a strong indication that the block of code should be extracted in a different way - perhaps it is better to extract the code into two different functions rather than one, or perhaps the rest of the code needs to be sorted out first. Too many times, I see developers hastly jump to the conclusion that "extraction == good" while forgetting the larger goal at hand, which is to improve readability.
Exacly. The problem with multi-nesting is that you have to understand a lot of code at once while also making big chunks obfuscating flow of code. If we have one very short (thus not obfuscating) nest then replacing it with badly named function actually makes problem WORSE because it forces reader to jump over multiple places to process one procedure.
Obvious solution is to name stuff descriptively, but naming is hard sometimes. There is no golden bullet.
Also comments and documentation helps, I know not everyone likes commenting and likes self descriptive code and not everyone thinks documentation even exists, but sometimes a short sentence of intent helps, even if it's in the reference code and not in the compiled code or the documentation.
Sometimes I need to extract first in order to figure out the name. I'll extract a function and give it a temporary nonsense name like "Banana." Then see how the function is used, what goes in, and what comes out. From that I discover a name and I rename Banana to the name it ought to have had all along.
If the name is long-winded and complicated, that's a hint that I need to extract it further, or perhaps I extracted the wrong thing.
@@NotExplosive 3 weeks and 10 projects later "WTF is 'banana?' " lol
And that is a problem you do run into when applying this too much. You'll get a lot of small functions for which it's sometimes damn near impossible to think of a sensible name. Sometimes just keeping the nesting is a fine solution as well; and add a comment if it's somewhat unclear.
I rewatch this video every few months to remind myself to do this. Thank you for making it so clear and simple
I didn't realize I was subconsciously a never nester but I thank my professors for subtly teaching me good practices
Same! When writing graphics code I noticed that if there was some check I needed to do, like if the renderer initialized or not, I would return out of the function rather than nesting all the "happy code" inside passing checks. I didn't realize this was an actual concept lol, I just thought it made it way easier to read.
I taught myself programming from scratch, interestingly this is one one the things I figured out myself.
same!!
I think this happy path / sad path disctinction is also called a "guard" clause - in which you guard the important part of a function with the guard(s) that are checking for invalid conditions and then bounce if the conditions are not ok before ever reaching the important parts.
Yep. I recently started programming in Swift and there "guard" is an actual keyword. I have been doing much more "happy path" programming before but will refactor some of the code I done in Kotlin.
Guard clauses aren’t “never nest”.
Guards simply mean you simply have a flat set of “if” validation checks on your inputs (and error handling) before you start using them in the function’s business logic. In effect you’re writing all your functions like they were main(), weeding out as many error conditions as possible as early as possible.
This is a good design pattern that has nothing to do with obsessively avoiding nested logic. It simply eliminates many wasteful and unnecessary layers of nesting by doing error checks in sequence (with error returns). There are times where 5-6 or even more layers of nesting is perfectly good, efficient code for the business logic. Especially if you’re doing something functional, like creating several layers of nested objects in one statement, where indenting the nested calls on new lines makes the code way MORE readable.
@@zackyezek3760 100% agreed, the thing these videos often ignore is that you have to apply common sense, logic, and your own experience to decide whether to nest or not, whether to split something out into a function, whether to chain calls on the same line, or split it up. Etc etc.
Obsessively applying some arbitrarily rule to all situations results in code that is consistent, but often consistently wrong. You have to both consider what you gain by making a certain style choice, and the disadvantages as well. Normally readability, performance, and maintainability (ease of making changes in the future) will fall on one side or the other, and you have to compromise. But always prioritising readability at the expense of all other factors is often the wrong choice.
I like to think of them as getting rid of the boring stuff beforehand so I can focus on the stuff that actually matters.
I would consider myself a "never nester" too, but I can also recognize that there are drawbacks to it that might be good to point out, with some tips to avoid them.
1) The inverted conditions can sometimes be less obvious, especially if there are some logical operators (and, or, not) that can be hard to invert correctly or understand afterwards. I think a good mitigation for that is to avoid compound conditions by either splitting up individual conditions, i.e., instead of "if any of these conditions are not met, return early", itemize each condition with its own early return, which also encourages more helpful error codes or exceptions, or putting the compound condition in a function with a good name.
2) Although putting all the error conditions up front cleans up the "good path" below, it can also put distance between checking for a problem and the code that would fail. So, either you can't understand the check before you see the code that needs it or you can't remember that the check was already done by the time you read the code below. The way to remedy this is typically to decompose into smaller functions (e.g., instead of 20 checks up front, and 5 chunks of "good path" code below, you split into 5 functions, each having around 4 checks up front).
3) In a similar vein of scoping issues, decomposing into many functions can encourage broadening the scope of code and variables. This is especially common with object oriented code when decomposing member functions (aka methods). It's easy to create new "decomposed" functions that are not self-contained, i.e., they take in and/or return variables or (self-)objects in a state where they are in the middle of some broader processing (the parent function), and invariants are temporarily invalid. That can be error-prone in future changes or additions to the code. It's also easy to make what should just be local variables of the parent function into member variables of the class or (God help us) global variables to avoid having to pass them in and out of the child function. And finally, it's easy to overly expose functions that are really just implementation details. Avoiding this just takes discipline to minimize the scope of variables and functions as much as possible.
4) Another obvious issue is that splitting up code into many single-use functions, and often, for other practical considerations, having to scatter them in different places in the code, does create readability problems due to having to jump around, and also because it can be hard to name functions that don't have a well-defined purpose outside of that one call-site. This can sometimes be mitigated by using "local" functions (or closures, or lambdas, or whatever, they go by many different names). A relatively common pattern you see is a bunch of checks up front, then some helper functions defined (typically closures or lambdas), and finally the main "good path".
This is exactly what I was thinking, also referencing "A philosophy of software design" about deep and shallow classes/modules
@@raducuvlad2187 Best book in the universe for any programmer EVER, it is a must read. In my opinion a lot of these nesting issues aren't so bad when you add a lot more comments to the code, which all of these examples is lacking
Also if you deconstruct into a bunch of small methods, that makes it harder to understand in a different manner because it obscures the real functionality. "clearDownloads" doesn't mean anything at all, it's the code inside you need. Don't break up the code into many small functions especially if it's only gonna be used in that one place
In addition to your 2nd point. I noticed putting checks upfront goes against the paradigm of try now fail later. Instead of checking whether a file exists, just try to read it and throw your exception (try-catch blocks). The nesting needed is just one level if the language allows multiple catch blocks per try block.
@@nuckm RUclips recommended another video from the same channel, titled "don't write comments". Now I feel like I gotta watch it
Great video. Just one remark related to the inversion example. The inversion of "top > bottom" is not "top < bottom", but "top
The bit about mentally discarding the unhappy path is what I try to drive into my junior devs brains! Less stuff in working space of my brain = less stuff to think about = less stuff to go wrong. The mental model is so important! Very glad you mentioned this and I'll be sharing it :)
Not really the case, if you dividing your code into a lot of tiny functions it can make it difficult to read since you spend a lot of time scrolling around for all the functions called, plus if you modify several function & it breaks something, but scrolling around all those functions trying to determine which function is the culprit, and you still have to track it all in your head. Worse is when you use nested functions:
function A() {
if (x > y) {
result = Function B(x,y);
} //endof if (x> y);
} //endof functionA
Function B (x,y) {
if (x == (y +1) ) {
z = y +1;
result = Function C(x,z);
} //endof if (x == (y +1) )
else
result = Function D(x,y);
} //endof Function B (x,y)
All your doing to making it harder to read and debug. I would be much easier if all this was in Function A, and not have to jump arround all those functions trying to figure out the bug, especially if the functions are either in different source code files, or your source code file has thousands of lines of code and the functions are located far away from the main function.
You can use comments to divide your code blocks into groups to make them more readable. Also add comments to all closing brackets to make it more readible\trackable. ie "} //end of if (x == y)" . Code your levels like a FIFO stack, so that for each level you add the closing bracket before inserting the first line of code in the new level.
@@guytech7310 this is why we have tools that make it easier for us. Your comments on the ends of code blocks are handled automatically by modern IDEs showing you where braces are related, and you don't have to worry about changing functions and breaking stuff if you have unit tests. "But who has unit tests?" I hear you ask. You, because you didn't write your code to be testable in the first place. Or you inherited a codebase where someone else made that mistake for you. I don't know, I'm not your boss. The point is it's better to leave things better than you found them and this is one way of doing it.
The gist of this all is, if I know that a certain function or piece of code works, I can *entirely* disregard it from my debugging process. Skip it entirely. Not even a thought is spared for it (unless it has side effects, in which case, you have bigger problems). I don't care about code jumping around because my debugger is handling it for me. If I have to have an entire god function's state in my brain's working memory, that's very taxing and slows me down. Too many possibilities and variables to keep track of. I'll let me brain do what it is good at: think of things logically one thing at a time. Break down the task into smaller chunks. Check my assumptions. "The result of this function should be x, and I got x, so my problem is not here" and I never need to remember that that code even exists. So I move on to the next thing. Keep doing that until a) I find something that doesn't match my assumptions or b) run out of assumptions, in which case, more often than not, the princess (bug) is in another castle.
@@guytech7310 what makes things hard to read is the inadequate naming. And unnecessary comments are a classic evil trying to compensate the lack of effort on finding expressive name for variables, functions, etc...
@@biltongza I program in C# and find so much relief that Visual Studio 2022 keeps track of darn near everything (how many references to any function anywhere, no matter if it's in another file, inside a dll, or what-have-you). Even deep nesting display is color coded and you can collapse code at any level (the best feature ever - though most IDEs have this) and keep it collapsed (not to mention, it's all customizable!). This plus intellisense, and keeping track of all external stuff, git, etc, makes programming a lot less stressful. This is the what ALL IDEs should be doing: keeping track of everything. Coming from a background of writing code on a text editor, devs today have no idea how good they have it!
@@davestorm6718 precisely!!! Use the tools you have!!! They are there for a reason!
I used to do a ton of nesting when I was a beginner, but as I invested more time, I automatically learnt better methods of writing code by watching tutorials and reading documentations, and also got used to them, its great to know that I have improved over the years and already use good practices without actively trying to.
Whilst I'll probably never be a strict never-neater this video has certainly helped me consider some ways to make my code more readable!
Truth should be shared 👉 The Connections (2021) [short documentary] 👀
unreadable code is job security, mate 😄
Is this a new religion?
Ancient one
Thanks for the high quality video as always!
A little suggestion though, instead of blurring other lines, how about highlighting the lines viewers should focus on? It will be easier for eyes on mobile.
Thanks for the feedback. I'll play around with some options for the next one and make sure it comes through well on mobile
@@CodeAesthetic Just blur + grayscale that way you will leave the code you need focus on colored while the rest would be discoloured
@@CodeAesthetic you can use this style. ruclips.net/video/6-mk6OpcUdM/видео.html
This video is a really helpful one. I have recently started trying out rust as my main programming language and although it's basically all I've ever wanted, its syntax and how it's written can quickly turn any app into a big mess, and these techniques really helped in making my rust code more readable. Stuff like the extraction method help with fixing some of the messy syntax and hiding it away somewhere else so that your main function that does all the work looks cleaner and more concise. I will say though, writing code while making sure that it doesn't nest more than 4 layers deep is quite hard, and I usually go with a "make a messy function that does a bunch of stuff first, then denest it" type of approach. Great video!
I’m mostly a self-taught coder so videos like this are invaluable. Thank you so much! 😊
You'd be surprised how little attention stuff like nesting, single responsibility functions, etc. are taught at university / college. You will be very lucky if you have a programmer that actually appreciates that as well as good logic and on top of that is your/a teacher.
I've found that your best option is to find several sources of people that have a lot of experience and tailor to expert/intermediate audiences (even if you are not). That's where you start learning about design principles, abstraction, nesting, composition etc. I'm a python developer myself, and i can tell you that what i got prepared with from uni vs what i met when i started working is vastly different. Was lucky enough to find a channel called ArjanCodes which is exactly the type of channel i'm referring to when it comes to enhancing your coding levels. They help you pick up good habits early on and keep you on the right path of good practices
@@GOTHICforLIFE1 I was about to point out the same things. Uni just gives you the basics of programming. Learning about coding best practices enhances your programmer level to another level.
@@GOTHICforLIFE1 mCoding is another good channel focused on intermediate/advanced programmers (particularly Python and C++ devs), but I particularly want to mention Corey Schafer, especially as he just posted a new video after like a couple years off; I've found that Corey's channel does a great job of at least mentioning more advanced concepts while keeping the videos very accessible and beginner-oriented. I believe his videos are almost all about Python though, but for anyone learning Python, I highly recommend Corey Schafer's channel, and for anyone new to programming who's starting off with a language other than Python, I highly recommend switching to Python (at least while you're learning the basics of programming) and also Corey Schafer's channel.
I was self taught, but I also kept reading to make up for it. This was eons ago, so books are less popular now, but The Pragmatic Programmer and Code Complete have timeless wisdom. Then there's the "Gang of Four" Design Patterns book. After that I would suggest learning SOLID design principles.
@@T1Oracle ty for that
0:11 as soon as I found out Linus Torvalds is a never nester, it made me want to nest more
I hope you will grow up.
Bill Gates wrote this comment
I remember working with an ‘ever nester’ once.
Rapidly scrolling down his code, there were large gaps between function definitions.
Weird, I thought.
Scrolling to one of the gaps, then scrolling to the right, I found the missing code. It was nested so far it had disappeared off the right hand side of the screen!
This should be illegal.
Disgusting. Just reading that sent a shiver of revulsion down my spine.
Nestscape.
XD
Truth should be shared 👉 The Connections (2021) [short documentary] 👀
YES! when multiple if statements are nested I feel like I need to keep it in mind it's a nightmare. I've always been a never nester without knowing. Great video!
This also gets into the idea of testability. If you have a lot of small functions like you do in your last example, it's easier to write a test case to verify each public API that you split out in isolation.
Wouldn't the extracted functions be private and therefore untestable?
And no, you shouldn't then make them public instead just so you can test. That would be very bad.
@Jan Krynicky you misunderstood. I meant that extracting out functions doesn't mean you can test them separately because they are private.
I learned all my coding ways from the company I work for who hired me with zero experience. This video changed my life, and we are currently working towards implementing this on projects going forward.
I'm a new C# programmer doing hobby game design in Unity. I am so glad I got introduced to some of these ideas early in my programming journey, mainly inversion using the term "guard clauses". It's so nice now that I have a little more experience to see this elegant dive a little deeper into the topic. Thanks for the great material!
Thank you for this explanation of never nesting. It help this beginner see some very bad habits I was creating. The exciting part for me was seeing you refactor via inversion, moving all the conditions and pre-qualifiers to the beginning of the code block and grouping the actual functional code after that… begin able to examine a block of code after putting down all the initial tests and rules will really help me a lot. I am always trying to do something way beyond my capabilities, so this should bring huge benefits to me. Thanks again.
As always, follow coding paradigms with caution. Just because someone on the internet said don't do X thing, doesn't mean you should follow it all the time. There will always be edge cases to everything. While it is generally a good idea to keep nesting at a minimum, there maybe sometimes that you might want to nest a piece of code for conciseness.
I was taught this by my mentor in my first internship and honestly one of the most important foundations of my programming approach to date.
I'm extremely fascinated by the never nesting technique. I actually started using inversions and premature returns and I sure feel satisfied of my codes. There are always ways to improve since I'm a self taught, but damn, the elegance and readability sure feel great.
They really do. Especially in cases where you're using rust or something. Although rust is quite awesome, the syntax can get quite messy at times and using these techniques really do clean up code and makes all of my rust code quite concise and elegant and easy to read, especially the extraction one.
Now in my work I'm reviewing some code and excessive extraction is my number #1 obstacle. To verify how some simpliest things are calculated I need to constantly jump across different sections of the code, often having to look for them in various files. It results in having much more to remember in the code, because not only you still need to keep those additional conditions in mind, now there's another matter on whether there are no type conflicts, whether the functions work together, because it's pain in the ass to debug if every function works on it's own, but they don't work together when you run the whole thing.
Personally for me, I find the extraction to be significantly easier after everything is written. Sure, when starting out, you may end up 4 or 5 deep, but once you have it working, I think refactoring to avoid excessive nesting makes the most sense at least for me
I've seen some code with excessive extraction and poor naming, enough said i had to rewrite it all... i didnt even surpass nesting 3...
I'm not a fan of single use extraction, but also not a fan of deep nesting, so its a constant balancing act.
probably the main single use extraction I like is to represent proessing that only happens in specific states... ie. a finite state machine.
@@LizEllwood that's good thinking, it's the same as premature optimization, doing it will usually do more harm than good
I agree that indentation should be avoided when possible, and also that guard clauses are one good strategy to do that, but I think breaking your code into a ton a little functions makes it harder to follow what’s happening and makes it easier for bugs to slip in. John Carmack wrote a memo about this a while ago, look up “John Carmack on Inlined Code” to see a more thorough argument.
Regardless, excellent video! Can’t wait to see more!
I totally agree, I found my self going up and down, up and down around some files because I had all these function calls... Not everything has to be a function, guard statements are often enough, and well designed code will rarely if ever go deeper than 3 levels w/o extracting into functions.
If functions are well named and tested there shouldn't be problems with understanding code that uses them, in fact they can make it quite easier
@@coffeedude You do have a point! Nonetheless at least in my line of work that's rarely the case (I seem to eat spaggetti every day) and well I sometimes have a "nag" to see how a function operates if that makes sense?
@@coffeedudeI really love when I can tell what the code is doing in 10 seconds
I think it's a bit important to note that the kind of nesting you're talking about is mostly visual, and you're completely fine nesting to infinity as long as each function is only visually 3 deep.
YandereDev disliked
LMAO
Just here to say that the “inverted” (negated) condition of “top > bottom” at 2:21 is “top
Do not, I repeat, never negate (!) numerical comparisons. This straight up harms readability.
I generally agree, after 3 or 5 indentation levels it gets harder to understand how deep you really are in the indentations, but I'm not really against nesting past 3 levels. I am against spreading the code around however, eg. "else" statements that are far apart so you have no idea what they're for at a glance.
In fact I'm more likely to add an indentation or an immediately invoked lambda to contain relevant code in its own block. Mostly in situations where the block is relatively small, semi complicated but doesn't quite make sense to put it in its own function.
Back when I knew less I wrote some code with the else so nested and far from the if that I had to add comments just for knowing to which if the else belongs.
With this video I could probably easily fix it but I'm afraid something will break so... my old code will just stay the way it is, at least until I get determined enough to do another larger update XD
Cohesion is important. Extraction only works when the thing you're extracting is a *cohesive* chunk of code.
@@cook_it That is how you know it's either time for guard clauses or a new function, or both
@@absolutewisp Inversion/guard functions would perfectly solve it and make it way easier to read.
The problem is that it's part of my updater subroutine which checks if there's an update and starts the update sub if there is, which means if I mess _anything_ up I can't send out anymore automatic updates, which would require all users to manually upgrade to a hotfix version, which is just... no.
So I will probably do it if I do a major refactoring of all modules, which won't be anytime soon as I don't get to work on that all that often and there are more important things to update first.
@@cook_it don't be afraid, just do it. It's a bad idea to let code rot. Just need to create some good tests to make sure any chance doesn't break anything.
Wow, you just opened my mind to the realization that I too have always been a never nester. I never thought of it this way, and I never had a disgust-o-meter for function depth, but I do extractions and inversions ALL the time to minimize the length of functions and to keep everything easy to read.
This style of coding is easily gonna save me hundreds of hours of confusion... thank u :)
You’ve managed to articulate a code style I’ve stuck with for years far better than I can!
Regarding your last example with the download queue, you could also point out that it’s much easier to unit test the de-nested code compared to the big block
Actually, on the video sample, it doesn't change much the testing strategy.
When testing, you want to target an unit which has a functional meaning, with scenario that makes sense. Generally, it is testing public actuators, controlled by public state, and asserted on public observable.
For instance, with the download sample, one would really want that given some recoverable http error, then the execution is retried many times until a failure state.
The handleHttpError method, or all other internal extracted methods are not testable as they don't have any particular functionality.
A fellow never nester here. The gatekeeping method is so much more elegant, and your points about how it shifts all the real code to the end and keeps it all together is exactly the reason I do it. I find it much easier to make sure I've validated everything necessary when all the "unhappy" conditions are all lined up before the "happy" code.
Even the nestiest of "nesters" agree that input validation belongs at the top of a procedure. I'm sure examples like 2:40 came from real (presumably novice) code, but to cast this as "never nest" advice is a bit of a red herring. The more interesting use case is algorithms and data structures which, as Niklaus Wirth pointed out, equals programs.
Try "never nesting" code that perform B+-tree deletion or something like that.
@@DeGuerre No you're right, and I was using the term "never nester" a bit jovially. I agree that there is a place for most programming practices, even heavy nesting, and we must be careful following any advice that says "never" or "always" do something. While I think nesting should be limited as much as possible to improve code readability, in certain examples like the situation you mention it would actually improve readability compared to the alternative.
I'm glad I found this channel. It's so pleasing to see the code being organized. Keep up the great work!
asmr for the developers lol
@@galitan5881 gonna put this video while I sleep
Opposite condition to (top > bottom) is not < but
One of the things I learned from the Pragmatic Programmer is to write a function that does one thing well. And if it does that one thing well, you don't have to worry about it and can call it when needed. If your function is trying to do two things, that's pushing it, three things and you better start thinking about what you're doing and why. Sometimes you can't help it. But many times you can.
I'm an ex-content writer, now learning code. In the web writing world, we have headlines from H1 to H6 (and beyond), but it's generally considered sloppy and bad practice to go deeper than H3-subsections and H4-lists. If you find yourself building nested H4-subsections within your content, you need to revisit your structure.
I see many parallels here!
Niiiice!
My brain works the same!
As a web programmer, I have to say: I think not allowing multi-line links in HTML is a crime. I know having to change a one-line link is easier, but I just want shorter code goddammit!! It feels bad to have well indented and well nested HTML look awful once you put a link that's like 170 character wide ;(
Because in both case it's about readability, less complexity means more straight-forward access to the information. Ultimately a deeply nested piece of code will work the same, it's just gonna be easier to read, maintain and extend.
I’m in the same boat. Ex content writer and I see the parallels
Thanks for posting, these are really great! I was a technical writer before I became a software engineer, and think about the aesthetic of code and cognitive complexity quite a lot.
Other possible topics:
• the declarative flow pattern you illustrate here (put every individual "thing your code needs to do" in it's own function, then compose in a main method at the bottom with a top-level try-catch),
• phrase every boolean in positive terms (i.e. isLoaded vs. !isNotLoaded; every negative boolean in code as well as written/spoken word adds cognitive complexity because we have to mentally flip to understand),
• factor out the individual parts of a compound condition into statements aliased to easy-to-read variables (if isUserAuthenticated && recentlyJoined)
• writing code with the primary goal of minimizing the time-to-understanding of the next dev who has to read it
It's very interesting you mentioned this!! I am a software engineer and I find myself so frustrated with the way people code and organise their code, that I am now the defacto documentation person in my team!! I am constantly writing pieces explaining the flow, thought process, context, memory concerns and so much more. All of this for the - COGNITIVE LOAD. All I wish for is the next person doing this after me to not have to spend years understanding the same thing. I find that Code organisation, comments, variable names, fonts, colors etc make a very big difference in how I subconsciously read code and affect speed
Recently I was refactoring and cleaning up a single file of 13k lines and modularizing it. I work in C btw. I found myself telling the junior dev how to name functions.
Subject-verb-object
I.e. module_name-action-what_to_act_upon
For example
Custom_utils_generate_hash.
This helped me create header files with almost tree like branching in names where I could easily read through it and find out what a module has to offer.
Denesting and functionisation only works if the same code will be used in multiple functions. If you HAVE to limit nesting, that's where the "inline" keyword comes in for the simpler functions.
I always compare coding to literature.
As you grow more experienced and laureate, you will write less but deliver more.
Given this, I always start writing code verbosely to meet all the requirements.
Then, I trim down the fat by sticking to the core principle, being DRY. It's not ideal, but it works for me!
Completely agree. If I could write all my apps in one line of code I would. I’ve come to find that simple code tends to be the best code. Obviously not everything can be simple, but if it can be, it should be
Nice comparison.
Our programming gurus like to create long lambda expressions which looks sick unless you try to understand it. Also it is much harder to debug.
I like this approach in moderation. When extraction goes overboard, it can get real annoying. Not a lot of functions are simple return statements. Trying to remember how 20 smaller functions work can be a lot harder than understanding the flow of a complicated function (if you document it well).
I could possibly be traumatized by legacy code I'm working on right now...
this is one of the things that has me on the fence about this.
Sometimes having everything in one function is very helpful.
The metric I use to determine if I should refactor into another function is if we use it more than once.
Or if the section of code can be replaced by a very well named function to make things clearer to read.
Separate intention from implementation.
Truth should be shared 👉 The Connections (2021) [short documentary] 👀
100% this. I might even say it's *worse* to spread the code across tons of other functions, methods, files, etc. At least if it's nested, it's all in one place. Then just use your code editor's code folding feature to make it easy to read.
That's what commenting is good for. The extraction is for readability and organization. You could argue that commenting nested code is the same, but it doesn't read the same.
Sad clause before happy clause, that makes so much sense now that you say it out loud. I just finished a uni assignment (using java to program a really shit interpreter) and did this without realising because I was trying to avoid messy nests, but I had no idea what I was actually doing. I’m glad there’s a name for it and I can do it knowing it’s a good idea, unlike many of my “good ideas”.
“Oh I should program my ENTIRE interpreter around arrays, right now it doesn’t matter that their size can’t be changed and it definitely won’t bite me in the arse later”
2 days later: “oh shit I didn’t think about that specific case. Well I’m not rewriting every single method to take something other than an array so I guess it’s time to spend 2 hours “making the puzzle piece fit with a hammer”, as my mum described it.”
I wholeheartedly agree that keeping track of deeply nested code logic is overwhelming. I've been using these techniques you've shown in your video for a long time and I started doing it because I found it annoying to read through my own code, so I figured out how to make it more readable by moving things around and breaking up parts of code into separate functions.
As a beginning programmer, I always hear my professors tell me to separate complex problems into several methods. this video is a visual representation of what they mean. thank you very much!
I think inversion can work to make a function more readable, but extraction almost always makes a function less readable. Scrolling between different functions is way more annoying and confusing than looking at another nested layer which is right there next to the rest of the code you're trying to understand.
Most of the IDEs nowadays allow you to split the screen, so you can literally have the same file twice in front of you, and just go to whatever second function you need. Works well for me
If you name the functions properly you usually don't need to check the inner workings of every function call
As a chronic nester this advice was very helpful! Hopefully I’ll be able to make my code clean as can be!
Towards the end of the video, he mentioned Single Responsibility. The S in SOLID principles. This one practice helps so much to make code easier to read, understand and troubleshoot. I teach this very early on in my high school programming classes and show examples of how it helps to make things easier. I also teach the use of guard clauses to put the conditions first and the work last. all these techniques promote less nesting. I'm also a never nester. :D
Nesting isn't always bad or evil. Depending on your desired output, workflow & requirement, you may need to nest your code abit for the sake of clarity.
the same goes with using multiple if...else and switch statements...😶
Yes, you do have to create functions if it goes deep, to take care of some parts of the processing and prevent having all the visual spaghettis in front of our confused face
can you give example?
@@hetuman nested, although not if/else: a definition of a recursive data structure that represents many possible "pathes" (for example in a game) or shows relationship between sub-nodes (just like xml)
@@nutelhere Oh yeah! Good example!
Glad to know there is people like me out there. Also, it's wonderful that you didn't stick with the first trivial example and instead dug into a more complex and realistic problem to show the true potential of this kind of aproach to coding. Cheers!
Too many functions is spaghetti code. Properly nested code is beautiful. Your team won't wait for you to refactor your code just to fit your own idea of "pretty code". If you can't handle and organize nested code properly, you are gonna be a NeverCoder pretty soon.
@@czernnobog5350 if you can't create an optimized code then you're gonna be a nevercoder
Moving the unhappy code to the beginning is not always a good idea because you're throwing off the branch predictor. By default, your CPU is more optimized to take a reverse jump before the condition is fully evaluated (useful for for-loops) and *not* take forward jumps before the condition is evaluated (useful to prioritize the first if-block). You can "solve" this problem by using __builtin_expect(your_condition, 0) , which will allow the compiler to reorder the generated assembly code such that the block is moved further down in the function. I use this technique sometimes when I find myself nesting too much but sometimes I find it cleaner to keep the "more likely" branch high above in the function.
This is clever but almost no one needs to worry about this. Premature optimization 99% of the time!
@@ChrisSchepman It's not really premature optimization until you've measured it and verified that the optimization isn't helpful. Modern CPUs rely heavily on pipelining and a missed branch prediction can be more than an order of magnitude slower.
@@ImranHaider All code optimisation is premature if you don't have a particular reason to optimise that code yet.
You might have slowest piece of crap code ever, but if it only runs occasionally optimisation is irrelevant. Or you might have an already very fast beauty which you've already mostly optimised with barely any room to squeeze more performance out it, but if it runs hundreds of thousands of times per second it's probably still a better candidate for optimisation than the really slow shit.
@@tomcutts9200 You shouldn't optimize your code based on how frequently it runs, you should optimize it based on whether it runs within the time you expect it to execute. For example, in a video game, you would optimize your rendering code so that it can finish by the time the next frame is about to be drawn on screen.
Many developers do what you mentioned and they give very little priority to code paths that they think won't run as frequently. The application start up procedure usually falls in this category and this is why so many "professional" applications (like Microsoft word or Photoshop) take so long to boot up. Users of these applications actually care a lot about load times but developers don't prioritize it enough.
All I was pointing out in my original comment is that you should be aware of how your platform (aka your hardware) is executing your code. This enables you to write code that works with your hardware instead of against it.
@@ImranHaider I am a video game developer, and I have tools which tell me pretty much exactly how much of a frame's budget is spent in which function calls, so I don't need to guess what code is taking up all the time.
If a significant chunk of time is spent in one place, that's the place to start looking for potential optimizations.
I could try and find the bit of code which is doing it's specific thing the slowest, but if it's called once per frame it probably doesn't matter. Maybe it should take 1 ns but it takes 10ms, but who cares?
There'll be some other code which should take 1ns, but it takes 1.1, but it executes 10000 times per frame. Even if I only shave off 0.1ns that's a much better choice for optimisation.
You look at the thing that's taking up the largest % of your frame and you start there. And you prioritise the spikiest frames that take longer than average frames and see what can be done to optimise whatever is happening to cause the spike (and possibly see if you can flatten that spike by spreading it's work over multiple nearby frames).
And yes, while application startup, and load times, are both important things that you don't want to get out of hand, they don't kill game sales nearly as quickly as framerates tanking and the game stuttering. Most customers would prioritise that you give them a stable fast game, over slightly annoying load times. People are prepared to wait a bit if the game is solid, but no matter how fast your game loads no-one will want to even start loading it up if the framerate is garbage.
For the most part I agree. If you’re going 3 or 4 deep you should be asking yourself if some of this should be deferred to another function.
Too many conditions in a function can make code difficult to test because the test has to account for many conditions and that makes the tests themselves sort of brittle and too cumbersome to deal with over time.
Before this video, I never thought about it. But seeing code de-nested, I agree. It's far more readable. I will most likely incorporate these strategies into my coding technique. Thanks!
I do this all the time , glad to see someone put it into words and make it so visual. Awesome work!
Another approach that can help with this is defining some local variables that act as flags and get the bulk of your computation before the final logic. That's one strategy we often take in Lisp. Defining predicate functions will also help greatly, Accessory functions can help too, even if they are only called in one function it'll help with readibility. Any conditional more than 3 levels deep is due for refactoring (with exceptions of course). EDIT, Whoops, I didn't wait until the very end of the video. :P
I’m beginning to appreciate this. I once held the belief that each function should have just one return/exit. I don’t even remember why; I think I read it somewhere/somewhen? It makes for some complex nesting and code paths!
The important thing is to split code into logical blocks rather than to mindlessly follow a style guide or a linter.
The % 2 example is an example of doing this wrong. Extracting there adds no value and makes the code harder to follow.
If the function was named filterEvenNumber() or filterEven() instead of filterNumber() it'd be slightly more useful
Unhappy code first makes so much sense. It cleans up code and lets other developers understand easily! Love it
So crazy, some people (like myself) are natural “never nesters” without even realizing it, just the way our brains choose to tidy things up or preplan ahead to write them in that order. I find it so fascinating that you were able to explain this “behavior” or “way of coding” so eloquently and in a fun way, especially for those of us who do it unconsciously or without really thinking about it! Thanks :)
i found myself thinking ' i thought everyone does that'
Yeah. I was suspicious going in, but quickly realized "wait that's just what I do"
Add me to this list. I have never heard the term "never nester" before this video, but this pretty much how I write my code. Quick returns first (unhappy cases) followed by readable work. I also try to avoid single-use functions/methods, though. It probably comes down to which annoys me the least, case by case
you guise are all so cool!!!
@@philrod1 I don't agree about single-use functions/methods, when you first do it, if it's a good one, it's likely possible to use it for other cases in the future, even if there's only a single case when you write it
A former colleague of mine used to nest entire programs in an if (true) {} at the top of all his code. Someone spent a day while this colleague was on vacation trying to understand why one of these programs was indented one additional, unnecessary level in all places. When the problematic colleague returned from vacation, he explained that this pathological behavior was simply for debugging and as a “safety switch” he could set while programming. Our boss gently but firmly ordered him to discontinue this practice.
honestly an indentation size of 8 is great even if you aren't doing it for the purpose of reducing nesting. it's incredible how much the boundaries between different sections of the code clear up. functions and methods become sort of like section headers in a document, and you can find everything easily.
I used to be all about spaces, but switched to preferring tabs just for this reason: the user can define how wide a tab is displayed. If they feel more comfortable with compact indentation, they can set the tab width to 2. If they are like you and prefer nice wide margins they can set the tab width to 8. But the code structure doesn't change, just how its displayed based on user preference. And I think that's one of the keys to happier programmers: choice without toe-stepping.
Truth should be shared 👉 The Connections (2021) [short documentary] 👀
@@error.418 I would add that most correctly configured IDE should allow for changing the visual appearance of indents, even if they are spaces.
I still think think that semantically tabs make more sense anyway (space separate words, tabs were thought up for aligned indentation) though, but both work practically.
@@raphaeld9270 I have never seen someone configure spaced indents to display at different widths.
@@error.418 Might be a text editor thing then maybe? I'll try to come back with an example later this week, though I think some of my old coworkers did have that.
In a year or two (even less maybe) when this guy blows up, these videos are going to be recommended as part of introductory CS courses in university the same way that 3Blue1Brown videos are for math courses at my university. Glad to be an early adopter!
Definitely a fan of inversion, and largely for the reasons you described. Early returns are powerful and I've seen programmers who forget they exist!
But I think extraction has a problem: it forces additional abstraction that can decrease comprehension. This particularly happens when the extracted code is just one line and is used just once. The problems of making clear names has been mentioned, but I also usually see people doing extraction to keep function line counts down, which feels like the wrong motivation.
Any code which does a separate step/piece of the functionality is an abstraction, even if it is inlined. It doesn't simplify anything if we keep it inlined and unnamed. The tricky part is to correctly identify those pieces and layers and separate them in a coherent and reusable fashion. And naming them well, of course.
Inversion is a no-brainer. Just do it!
Extraction requires a bit more experience. Do it with care or you risk creating The Wrong Abstraction.
I used to make functions a lot when I was a solo dev mostly. Didn't need to read others code often. However, now I work on a large project with many people and I DESPISE unnecessary functions. So many functions that only do one thing and many are nested making it very hard to keep track of what you are reading.
I much more prefer very long functions with comments now. Also less classes in general. I shouldn't need to go on a journey just because someone felt they need to name a piece of code three times.
Agreed, but one thing that helps is have the main orchestrating method either well commented, or use method names and var names that define what's going on. The main method would have a good comment as to what is going on, the inner methods would not be as important with regard to method name (but I agree all the sub-methods can hide meaining). Coming up wtih good names can be 50% of the battle.
My personal test is if I want to copy paste some lines of code. A single function call, copy away, but with multiple lines, you're probably doing something, and you might as well have a single location to debug said thing.
I was expecting something far worse when I heard "never nester" but this is incredibly informative.
This is already an incredibly high quality channel, please keep it up!
This is how I've been coding for idk, 6+ years. I've preached earlier returns to dozens of programmers. Good stuff. I hope many watch this and it sticks like a thorn in their brain.
2:15 That part really tickles my nerves. The inversion of top > bottom is bottom
I actually didn't expect to get a whole different perspective watching those videos, and even worse, I actually expected them to be a bit boring. Man, how I'm I wrong about my suspicion and false judgement. Your videos are well explained, intuitive, fun, professionally made and very useful to the point that I have never got any similar valuable info in my college so far. So, I would like to thank you for your hard work, and please do keep it up! :)
I think the only thing that someone needs to "keep up" is their studies in college. Where did you learn to use commas and what monstrosity didn't teach you about run-on sentences? Commas aren't periods, my fellow internet user.
@@Dyanosis its youtube comments noone cares 🤓
@@Dyanosis I'll give it to you, you are kind of right. I haven't really put any effort into my punctuation, as I am completely aware that I'm not writing an executive letter but a RUclips comment. Otherwise, you would've not seen me writing like that, my fellow internet user. However, I do thank you for your comment and valuable advice.
@@Dyanosis You could have a comma before and.
@@Dyanosis Also, the word ‘that’ is not needed before ’someone’.
Huh? Turn's out I'm a Never Nester also. I didn't even know that was a thing. Learn something new every day. 😀
Also, it really helps you follow the Single Responsibility Principle on a per-function level, which inherently helps make your classes follow it also. 😁
I definitely find it easier to understand if there's a chunk of validation at the top. I also remember reading that some branch predictors assume the last chunk of code in a function is the hot path if the branch predictor has never seen the code before. So this might also help with performance.
I am also a never nester! I hate nesting stuff, and always have, and employ both these techniques constantly. Nice to get names for them, and very pleasing seeing all that code organized
Great advice! I am definitely in this camp. I also like to minimize “if/then/else” constructs finding other mechanisms like call tables and switch statements to be cleaner. Thinking about code in terms of a finite state machine helps with both of these problems.