The 3 Laws of Writing Readable Code

Поделиться
HTML-код
  • Опубликовано: 22 ноя 2024

Комментарии • 1 тыс.

  • @kantancoding
    @kantancoding  5 месяцев назад +781

    For those of you that are confused about me not using a return statement in the first example, it's because `log.Fatalf()` actually exits the program e.g.:
    ```
    // Fatalf is equivalent to [Printf] followed by a call to [os.Exit](1).
    func Fatalf(format string, v ...any) {
    std.Output(2, fmt.Sprintf(format, v...))
    os.Exit(1)
    }
    ```
    Sorry if it made the example a bit confusing!
    ref: cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/log/log.go;l=417

    • @guruware8612
      @guruware8612 5 месяцев назад +99

      If you have to explain what "Fatalf" does you have exactly that naming problem you talked about.
      "Fatalf" - tells nothing what it does - and why the ending 'f' ?
      A name like "LogErrorAndExit" would do the trick.
      Using exit() is very questionable, but that depends on the language used.

    • @kantancoding
      @kantancoding  5 месяцев назад +85

      @guruware8612 😆 I didn't write the Fatalf function. This is part of the log package which is part of Go's standard library. It's a variation of Printf the only difference being that it will exit the program.
      Printf is somewhat historically understood from the C Programming language. The "f" is because it prints "formatted" output
      I left a link in my original comment. If you don't like the naming you need to talk to google about that one. And also some of the pioneers that created C

    • @yondaime500
      @yondaime500 5 месяцев назад +42

      @@guruware8612 I prefer Rust's "panic!" :)
      I guess "fatal" implies that the program cannot recover, so it makes sense. But the problem is that in many logging libraries, "fatal" is just a log level like any other, and won't exit the app automatically. As for calling exit() on error, I think it depends more on the type of application than on the language. If it is a command line app that only does one thing (which this example kind of is), then it makes sense. But if it is something like a GUI, you probably want to display a message to the user and let them try again or something.

    • @kantancoding
      @kantancoding  5 месяцев назад +16

      I agree with your point about calling exit() but actually this isn’t a real app, it is just artificial code I wrote to illustrate examples. The reason I used the panic approach is because adding error handling made the code too long to fit on the screen. Not to mention laziness.

    • @haze86
      @haze86 5 месяцев назад +36

      @@guruware8612 it's a standard library function dude. Combination of being pedantic and clear unfamiliarity with the language.

  • @louis-philip
    @louis-philip 4 месяца назад +1177

    And remember: "Potential readers of your code" is more than likely going to be YOU in the future.

    • @kantancoding
      @kantancoding  3 месяца назад +60

      Very important point indeed

    • @everyhandletaken
      @everyhandletaken 3 месяца назад +50

      Future you, is always the one you should feel the sorriest for.. every time.

    • @Gann4Life
      @Gann4Life 2 месяца назад +9

      Haha I wouldn't like to be that guy, poor man

    • @Sercil00
      @Sercil00 Месяц назад +9

      Yes, your future self who will have completely forgotten having ever written that code. You may even switch on Git Blame just to figure out who wrote this garbage, praying that it wasn't you. The easier it is to understand, the less likely it is that your colleagues will start calling you for an explanation. And if they do it anyway, on a morning after you had a horrible night and can barely focus, you'll spare yourself the humiliation and headache of having to decypher your own code quickly and not understanding it either.
      Source: Guess.

    • @HarryPujols
      @HarryPujols Месяц назад +5

      I am thanking myself of 5 years ago for doing well self-documented code I completely forgot about.

  • @Cadet1249
    @Cadet1249 5 месяцев назад +1870

    In my opinion avoiding duplicate code is often taken too far. You avoid duplicate code if the code has the same reason to change, not just to make it look prettier. If you try to extract every inch of shared logic you’ll end up with high coupling and your code will be even harder to maintain

    • @NemisCassander
      @NemisCassander 5 месяцев назад +50

      I approach it in the same way as normalizing a database (as it's effectively the same issue in the data domain as opposed to the logic domain). Normalization is best for the integrity of your code or data, but there are reasons to have denormalization as well.

    • @DevinLauderdale
      @DevinLauderdale 5 месяцев назад +42

      DRY only applies to code where the "actor" is the same, as you explained. Code that happens to do the same thing, but has two different responsibilities according to SRP, is ok to do. :) If you end up using it in MANY different components, you likely need to abstract it into a util function, or you may need another business logic interface, depending on what this repeated interface does.

    • @Scroapy
      @Scroapy 5 месяцев назад +16

      big difference between "accidental" and "real" duplication. I have seen countless time people abstracting "accidental" duplication, which in fact wasn't the same code given its context.

    • @TehKarmalizer
      @TehKarmalizer 5 месяцев назад +14

      @@Scroapy that’s how you end up with a bunch of parameters that dictate nested branches in a function.

    • @Scroapy
      @Scroapy 5 месяцев назад +3

      @@TehKarmalizer exactly

  • @MechMK1
    @MechMK1 5 месяцев назад +790

    I've once seen a variable called qr_t_us_135_uss_t. The variabke name meant "Query result, Table, Users, Using Columns 1, 3 and 5, which are of type unsigned int, string, and string, and the whole variable is temporary".
    The codebase was a mess

    • @Jonas-Seiler
      @Jonas-Seiler 4 месяца назад +99

      Hungarian notation taken to the extreme

    • @ooker777
      @ooker777 4 месяца назад +9

      How would you improve that?

    • @Jonas-Seiler
      @Jonas-Seiler 4 месяца назад +50

      @@ooker777 just don’t, it’s not worth it

    • @ooker777
      @ooker777 4 месяца назад +4

      @@Jonas-Seiler you mean trying to have that variable at the start is a crime? But then how do you process the data that is that specific?

    • @icodestuff6241
      @icodestuff6241 4 месяца назад +21

      @@ooker777 write a comment

  • @rhn8696
    @rhn8696 5 месяцев назад +513

    1. don't deeply nested code and not using inversion;
    avoid deep nesting by inverting conditionals, merging related if statements, and extracting complex logic into separate methods or functions
    2. organize code;
    prevent code duplication by extracting shared logic into its own functions.
    3. don't use naming that only you understand;
    use clear and descriptive naming conventions to make the code self-explanatory and easier for others to understand.

    • @perrymaskell3508
      @perrymaskell3508 5 месяцев назад +5

      For me some of the worst issues with #3 is if you try to change a variable name at a later stage and you called it x or y or such. If you "find and replace" it will replace every instance of x and y, and not just the variable names.

    • @DevinLauderdale
      @DevinLauderdale 5 месяцев назад +1

      based comment

    • @seanthesheep
      @seanthesheep 5 месяцев назад +9

      @@perrymaskell3508 IDEs have a language-aware rename feature that only replaces the name when it refers to the variable, so renaming single-letter variables shouldn't be an issue if you use an IDE

    • @RegisMichelLeclerc
      @RegisMichelLeclerc 5 месяцев назад

      @@seanthesheep my IDE is made of bash and vim...

    • @seanthesheep
      @seanthesheep 5 месяцев назад

      @@RegisMichelLeclerc Then the package you're using for IDE features should have a command to rename variables, like :YcmCompleter RefactorRename for YouCompleteMe

  • @jorge_cazares
    @jorge_cazares 5 месяцев назад +210

    In my Software class, we learned the following principles:
    Don’t repeat yourself (DRY)
    Comment where needed
    Fail fast (code should reveal its bugs as early as possible)
    Avoid magic numbers
    One purpose for each variable
    Use good names
    Use whitespace and punctuation to help the reader
    Don’t use global variables
    Functions should return results, not print them
    Avoid special-case code
    I think this video summarizes many of them!

    • @hungryhikaru
      @hungryhikaru 4 месяца назад +5

      what is magic number

    • @jorge_cazares
      @jorge_cazares 4 месяца назад +15

      @hungryhikaru Well, if let's say somewhere in my code, I have
      let costOfBag: number = 67.25 * costOfApple
      There, the "67.25" is a magic number because we have no information about it on our code (though it could be inferred). This a silly example, though. I remember working on a project that involved geometry and had to write various angles across the code, such as 72. The reason why magic numbers should be avoided is to make the code more readable and to show the relationship between the variables, so that the code is ready for change and easy to understand.

    • @bozdowleder2303
      @bozdowleder2303 3 месяца назад +3

      Astonishingly it leaves out the most important one! - documentation(or comments) There is no substitute for it. Even if you have taken an unusual approach to an unusual project, explaining it in plainspeak in the comments will make the code very easy to read(if only to your own self in the future) Documentation, modularity(also great for debugging!) and avoiding nested as opposed to linear thinking are probably the three golden rules

    • @bozdowleder2303
      @bozdowleder2303 3 месяца назад +1

      Astonishingly it leaves out the most important one! - documentation(or comments) There is no substitute for it. Even if you have taken an unusual approach to an unusual project, explaining it in plainspeak in the comments will make the code very easy to read(if only to your own self in the future) Documentation, modularity(also great for debugging!) and avoiding nested as opposed to linear thinking are probably the three golden rules

    • @Marcus_613
      @Marcus_613 3 месяца назад +14

      ​ @hungryhikaru "what is magic number"
      If 21

  • @nicky5185
    @nicky5185 5 месяцев назад +207

    I'm leading a project which consist of migrating old PHP code. I found up to six levels of nesting, functions with bodies of more than 3000 lines (yes, three thousand), copy-paste engineering galore and many, many other issues. They blame PHP, but I blame the programmers, who even today still write very sub-standard code

    • @static-san
      @static-san 5 месяцев назад +17

      It is possible to write good code in PHP, but 15 years ago, there were a lot of mediocre programmers producing bad PHP because it was so easy to get going in it and there were far fewer ways to learn what was good and what was not.

    • @delanym
      @delanym 5 месяцев назад +6

      I've had to refactor 12 levels of ifs (that's not counting loops/try/class)

    • @kristianlavigne8270
      @kristianlavigne8270 5 месяцев назад +5

      Typical for PHP and Python code bases in my experience. Typically the languages novices start with and many never leave…

    • @nicky5185
      @nicky5185 5 месяцев назад +4

      @@kristianlavigne8270 Do you mean that other languages like Rust or JAVA are inmune to high cyclomatic complexity, copy paste engineering, poor class architecture and poor typing?

    • @BaldurNorddahl
      @BaldurNorddahl 5 месяцев назад +7

      At a previous job, we had a guy nicknamed Mr. If. He didn't care and never improved.

  • @overloader7900
    @overloader7900 5 месяцев назад +2653

    The reason math is poorly understandable is not its inherent complexity, but the fact that mathematicians for some reason are all using single letter meaningless variable names

    • @ooker777
      @ooker777 5 месяцев назад +416

      Coming from a math background I actually find having to using explicit variable names makes it hard to calculate more than using Greek letters. There's a topic about this on Math Stack Exchange.

    • @user-sl6gn1ss8p
      @user-sl6gn1ss8p 5 месяцев назад +298

      hard disagree on that one, simple variables are way better for the kinds of things math uses, especially in the domains mathematicians would actually be working on. The variable names are short, sure, but the notation is very formal as way and, in general, the formalism plus conventions capture what matters fairly well.

    • @ooker777
      @ooker777 5 месяцев назад +12

      Well, as you can see I don't have any actual argument. I can only provide information and my experience. (Or you can say secondary evidence, supportive argument, or whatever). From your arguments, then a consequence is that computer scientist must understand that meaningful variable names are better than abstract notations. Is that correct? But if you look at how they write math formulae, only notations are used.

    • @DF-wl8nj
      @DF-wl8nj 5 месяцев назад +79

      This depends a lot on what you’re doing and how complex it is. If I had to work with differential equations using camelCased variable names I think I would end myself. However, it’s usually super helpful with physics or advanced engineering topics to stop, and take a moment to walk through the equation, keeping in mind what each variable actually corresponds to, or even saying it out loud.
      Except transport phenomena. Fuck that shit.

    • @vborovikov
      @vborovikov 5 месяцев назад +11

      they use them because digits are single letters too. So the data is represented by the letters and the operations are represented by other symbols different from letters

  • @VeitLehmann
    @VeitLehmann 5 месяцев назад +24

    I agree 100% with rule 1 and 3. For rule 2, you have to be careful to not split things too early. This could lead to premature abstractions that don't work well in the future. Instead, don't be afraid to rewrite/refactor code early and often. So first keep it as simple as possible, and if some logic is user 3 times or more often, extract it. By then it should be clear what a good abstraction should look like. And because you're in the habit of refactoring early and often, you'd be confident to adjust the logic if the abstraction doesn't work nicely anymore later.
    So my top priorities for good code are:
    1. Write code that's easy to understand
    2. Write code that's easy to change

    • @oliver_twistor
      @oliver_twistor 4 месяца назад +4

      I think what people who abstract prematurely do wrong is they don't understand why they should make abstractions. The real goal of abstractions isn't to avoid code duplication, it's to increase separation of concerns. Instead of focusing on abstracting the HOW, one should focus on abstracting the WHAT. I think a reason for that misunderstanding is that many beginner programmers will only encounter simplified examples of abstractions like those in the video. They learn only that they should avoid duplicated code, but not the reason why.
      It reminds me of when schoolchildren first encounter equations in maths class. They could get an example like "2 + x = 3" and they wonder why the heck they should use equations since the answer what x is, is obvious. They don't get that the true power of equations isn't to find the value of x, it's to express a generalisation of an equality.

  • @AdidasDoge
    @AdidasDoge 5 месяцев назад +252

    2:46
    I feel like the if statement that checks if it is a valid user does not need to be in its own method, as it is literally two lines of code. I get that making it into its own method would allow whoever is reading the code to instantly understand what the if statements are for, but another part of clean readable code is conciseness and being succint. If we are adding another two lines of code that doesn't actually much affect the readability of the code, then it isn't very necessary.

    • @velvetsycap6351
      @velvetsycap6351 5 месяцев назад +13

      True, but let’s say you’re working with React, Vue of something. It’s better to turn your longer conditionals into computer state for better readability. In terms of executable code, I agree. It’s not necessary to create a small isValidUser func if you’re only using it once.

    • @semplar2007
      @semplar2007 5 месяцев назад +26

      the point of extraction in this case is to define a new logic property for the entity user: whether it's valid or not. currently user is valid when they're authenticated and authorized, but who knows, maybe there will be some additional protection in future, like user will be required to have some special security ID, or restrictions like no banwords in username, not being deactivated or banned by administrator

    • @zyriab5797
      @zyriab5797 5 месяцев назад +3

      At least, it should have a more descriptive name like "isUserAuthenticatedAndAuthorized()" or simply "isUserAuthed()".
      You get the idea.

    • @semplar2007
      @semplar2007 5 месяцев назад +35

      @@zyriab5797 isn't calling a function like that defies the whole purpose? why write isUserAuthenticatedAndAuthorized() when you can write isAuthenticated && isAuthorized

    • @RichardLee-qt2mp
      @RichardLee-qt2mp 5 месяцев назад +2

      @@semplar2007 It's more so that when new validation method is added such as TFA, modifying the function will automatically apply to the entire codebase. Edit: I misread you comment and wholeheartedly agree with you

  • @brandyballoon
    @brandyballoon 5 месяцев назад +84

    Overuse of these principles (except the naming one) can also be a sign of an inexperienced developer, because they treat them like rules instead of guidelines and only applying when it really does make sense to do so. Overuse of extracting methods would be the most common one - creating a function for the sake of it when it's just a few lines of code that's only used in one place. If the name of the function helps to make the code more readable, this is where a comment would be more helpful than a function call. Bear in mind that calls have performance implications too.

    • @danielcreatd872
      @danielcreatd872 5 месяцев назад +9

      You can always put inline for the function, and in many cases the compiler will do it anyway if it is deemed more efficient to do so.

    • @brandyballoon
      @brandyballoon 5 месяцев назад +3

      @@danielcreatd872 Yes you are right. I should have said "may have performance implications"

    • @Navhkrin
      @Navhkrin 4 месяца назад +1

      Tbh context matters a lot. Even if it is used only once if it is kind of thing that would make sense to be used again as code develops then it still makes sense to make a function out of it. Otherwise you can get code duplication from seperate Devs rewriting same logic on seperate files.
      I will take over functionalization over huge monolithic files but I get your point, striking the perfect balance takes expertise

    • @sm5574
      @sm5574 4 месяца назад +6

      Avoiding extraction for "a few lines of code" is exactly the wrong thing to do. An ideal function will only have a few lines of code. If you see a few lines of code that perform an encapsulated task, it absolutely should be extracted into a function, even if it is only called once. Readability and maintainability are at least as important as reuse.

    • @NoSpeechForTheDumb
      @NoSpeechForTheDumb 4 месяца назад +2

      In fact only virtual functions do have performance implications. Non-polymorphic function calls are optimized away by every decent compiler.

  • @bartoszporzezinski4842
    @bartoszporzezinski4842 5 месяцев назад +113

    Watching this video I remembered collaborating with some friends on a school coding project years ago. One of my friends had a proclivity for writing very messy code, using variable names like "habbababa". I asked her on that occasion why on earth she would name a variable like that - and reportedly, "habbababa" sounded "kind of cute". I still find it hilarious up to this day.

    • @ego-lay_atman-bay
      @ego-lay_atman-bay 4 месяца назад +7

      Yeah, I've seen many programmers learning to code use nonsensical names, just because they don't care much about programming. If they got into programming more, they would use much much better variable names.

    • @Erhannis
      @Erhannis 4 месяца назад +3

      I used variables like that for a couple years. Well, not usually COMPLETE nonsense, just unrelated to their function. "makeToast", "batman", "spoooon", etc. I eventually got over it.

    • @dandymcgee
      @dandymcgee 3 месяца назад +2

      I did this on a school project once because I was the only programmer and the 3 other team members were "artists" (only 1 of them made all the art, the other two did lord knows what for the whole semester). I was super bored so I started naming things random stuff like "laughingLlama" and "pb_and_j" etc. It was actually a pretty cool platformer game in the end, but tons of copy pasta for the different levels.

    • @haizk
      @haizk 3 месяца назад +2

      someone I know, he uses join at top of the query, and select at the most bottom..

  • @whatever7338
    @whatever7338 5 месяцев назад +92

    Extracing is a double edged sword. You now need to find the function, understand it, remember it and keep it in your head while youre trying to understand what follows after it. Also if its something simple then the function is pointless. The only reason to extract something is if youre using it multiple times. Also that first example for extraction it would be much better just having bool isValidUser = ...; if(!isValidUser){} right next to each other.

    • @gregoryfenn1462
      @gregoryfenn1462 5 месяцев назад +20

      As long as the interface (func name and param names) are clear and meaningful, then this is not a problem because each lower level function should have been verified and tested, so you treat it as a trusted black box when debugging the higher level function.

    • @elbozo5723
      @elbozo5723 5 месяцев назад +12

      Well yeah, if you do it wrong. A well written function with good documentation that serves one purpose is extremely easy to understand and debug.

    • @piff57paff
      @piff57paff 5 месяцев назад +13

      You don't need to have intricate knowledge _how_ a function works as long as it's clear _what_ it does. (E.g. no one thinks how the regex method works in detail or how file read eventually makes a sys call, or what happens so that you get electricity out of the wall socket)
      Creating good abstractions is hard, and all are leaky, but they can still help to reduce cognitive load.

    • @Jonas-Seiler
      @Jonas-Seiler 4 месяца назад +4

      Usually only when you’re debugging something, if it’s working fine you can just ignore what the function does in the background

    • @volbla
      @volbla 4 месяца назад +5

      ​​@@piff57paff _"Creating good abstractions is hard"_
      I think that's exactly the point. It's very hard to consider all use cases and edge cases in advance. That's why abstraction shouldn't be a kneejerk reaction to make code prettier. It should be born out of necessity. When you know _why_ you need it it can be better tailored for its purpose.

  • @padcom
    @padcom 5 месяцев назад +17

    There is a lot more to it than just 3 simple laws but I can definitely agree that this is a very good start. Well done!

  • @demonicious_
    @demonicious_ 2 месяца назад +5

    Separation of Concerns is good but I feel like people sometimes take it too far. So you'll find a function which calls 3 other functions and each of those functions perform only 1 line operations. A rule of thumb I like is that I only extract functionality to another function if it's used in multiple places.

  • @atlas_19
    @atlas_19 3 месяца назад +27

    4:33 Bro gave us time to discuss the topic in pairs.💀

  • @rilauats
    @rilauats 6 дней назад

    Great examples! You earned yourself a new subscriber!
    Back some 30 years ago, I noticed one programmer on the dev team consistently broke our coding conventions.
    Drop downs in the IDE listed methods in alphabetical order. He prefixed all methods with letter = A to ensure his methods for all devs always showed up first in the IDE. Ego driven!
    Next, any local variables he created were named Anna, Bertha, Charlotte, Denise, Ellen, . . .
    I asked why? His answer: To ensure only he would ever want to maintain the could he wrote - to ensure his continued employment.
    Sometimes developer decisions can have effects way beyond the next code check-in.

    • @kantancoding
      @kantancoding  5 дней назад

      lol I don’t understand the argument about making your code obscure so other devs can’t maintain it. There’s definitely always somebody smarter than you who can figure out what the code is doing and refactor it.
      Also, this guy seems insane 😂

  • @perfectionbox
    @perfectionbox 5 месяцев назад +792

    Imagine someone writing a program that converted well-written code to badly-written, by nesting, duplicating, and renaming.

    • @thevalarauka101
      @thevalarauka101 5 месяцев назад +41

      comment for the algorithm

    • @ooker777
      @ooker777 5 месяцев назад

      As your requirement doesn't include runnable, I suggest ChatGPT

    • @AntonioZL
      @AntonioZL 5 месяцев назад +35

      so a badly written compiler.

    • @mikhailturov7066
      @mikhailturov7066 5 месяцев назад +112

      yeah, its called obfuscator, or minimizer for other purpose

    • @ClariNerd
      @ClariNerd 5 месяцев назад +33

      So basically ChatGPT

  • @bartekkaczmarek2865
    @bartekkaczmarek2865 4 месяца назад +13

    the overly nesting problem often comes (at least where i live) from the way coding is taught at university. i was taught that a function should have a single return, and early returns where a bad thing, so that resulted in a deeply nested code

    • @acabreragnz
      @acabreragnz 2 месяца назад +1

      Same here in Uruguay, having múltiple returns was a bad practice at university.

    • @matheusjahnke8643
      @matheusjahnke8643 2 месяца назад

      There was a reason for having one return: cleanup code;
      Imagine the following
      function() {
      setupStuff1(); //Remember to cleanupStuff1();
      setupStuff2(); //Remember to cleanupStuff2();
      // now bury this into logic
      if (badPleaseStopNow) {
      return shitHittingFan; // Forgot to cleanStuff1(); and cleanStuff2();
      }
      setupStuff3(); // Remember to cleanupStuff3();
      cleanupStuff3();
      cleanupStuff2();
      cleanupStuff1();
      return result
      }
      The single return makes it so you only have to check one "cleanup path";
      I've actually found a bug in a friends' assignment "it just hangs"...
      It was a mutex locking... and unlocking if the function went to the end... but it had an early return inside a loop... which didn't unlock the mutex.
      If you are using a modern language with a modern library that usually isn't a problem because there are tools to deal with that:
      C++ has destructor methods
      Java has "finally" block
      Python has "with" block
      Go has the "defer"

    • @sayori3939
      @sayori3939 2 месяца назад +1

      such bs

    • @idwtgymn
      @idwtgymn Месяц назад +1

      The hate for goto also can bloat nesting

    • @Ice-Cold-Fury
      @Ice-Cold-Fury 29 дней назад

      At my first job we were doing the next version of a project for a customer. The old code had been written in ADA i believe, and ADA has some sort of facility where you jump to some end code, kind of like a finally or ensure section, and inside that section there was one return statement (I have never programed in ADA, this i what i was told). In the new version of the project it was begin coded in C and C did not have that feature, but the requirements to have one and only one return from a function remained, it led heavy nesting.

  • @00sjsl
    @00sjsl 5 месяцев назад +9

    I’m luke warm about the DRY principal. It only makes sence if the spun out code can be understood in isolation. If you need to see where a function is called to understand what it does then the code is actually harder to understand.

    • @oliver_twistor
      @oliver_twistor 4 месяца назад +4

      I agree, but that's a misunderstanding or misapplication of the DRY principle. The DRY principle doesn't say that you can't duplicate anything and that you have to chop up everything into small fragmented pieces. One function should do one complete thing. If the duplicated code looks the same, but do different things for different reasons, they shouldn't be in the same function. A function shouldn't rely on another function or some external context to make sense. That is a poorly defined function.

  • @AD-wg8ik
    @AD-wg8ik 5 месяцев назад +55

    I’m not inexperienced but I stayed for your animations

  • @Milotiiic
    @Milotiiic 2 месяца назад +5

    Its personal preference, but i probably wouldnt create those tinty functions isValidUser and calculateTaxes. The first one you can clearly guess what its doing without having to create the isValidUser func. The second one, the file is probably named tax calculator service or the function instead of "main" is already probably named "calculateTaxes" or something like that.
    The base function is already short enough and easy to understand, i prefer reading it sequentially than having different layers of abstraction by creating tiny functions like those. You already know what the base function will probably do because u previously read the filename or the base function name. If the base function was larger or more complex, then yes, i would probably extract some functions tho.
    Great advices btw!

    • @kantancoding
      @kantancoding  2 месяца назад +1

      All great points! Thank you! 😊

  • @etni_dev
    @etni_dev 5 месяцев назад +10

    Very good tips, but I like to keep the conditionals more granular. It's easier to write better log messages or throw more specific error codes

    • @hoi-polloi1863
      @hoi-polloi1863 4 месяца назад +4

      I'd agree in general, but in the specific case used (unauthenticated vs unauthorized user), it's often best -not- to split them out. I don't want people "fishing" for valid usernames! That's why so many login screens give errors like "username & password do not match" or the like.

    • @v0id_d3m0n
      @v0id_d3m0n Месяц назад

      ​@@hoi-polloi1863 ohh i never even considered this!

  • @thomai5287
    @thomai5287 4 месяца назад

    Great summary of the most important concepts that everybody should follow in a team.
    Nested, unreadable code like your example is a perfect breeding ground for bugs, often missing edge cases or special cases. It's always worth taking a closer look at such code.

  • @abulkalamasif1270
    @abulkalamasif1270 5 месяцев назад +6

    Please correct me if I am wrong.
    The inverting looks good but I don't think it is that simple. In the given example, multiple conditions are needed to be checked to execute the nested code. If you are going to make it linear, you will have to use logical operators like &&, || to make sure that the condition remains same.
    For example: at 0:39, "user not authorized" will be logged if ( ! isMaintainancePeriod && isAuthenticatedUser && ! isAuthorizedUser ).
    On the other hand, at 1:21, "user not authorized" will be logged just by ( ! isAuthorizedUser ). Here, we have not made sure that isMaintainancePeriod is false and isAuthenticatedUser is true.

    • @ghostradiodelete
      @ghostradiodelete 4 месяца назад +2

      You're not at all wrong, the logic is entirely changed. Personally I've come across a lot of these "laws" by folks like Robert Martin and I'm just not a fan. I don't think most of them are that critically helpful.

    • @v0id_d3m0n
      @v0id_d3m0n Месяц назад

      No... we have made sure that isMaintenancePeriod is false by exiting the function if it's true.
      Rhe authorized user logic did in fact change but that's not because of the inversion.

  • @sayori3939
    @sayori3939 2 месяца назад +1

    the logic inversion is so valuable specially for beginners, usually you create a lot of weird situations and temporal dependencies by not structuring conditons properly

    • @kantancoding
      @kantancoding  2 месяца назад +1

      I agree, can get out of hand pretty quickly

  • @HerbertLandei
    @HerbertLandei 5 месяцев назад +33

    I see some people complaining about the code extraction. In the example, I would do a similar extraction, but for another reason. Avoiding code duplication is a nice side effect, but the core idea should be: *Keep the abstraction level of a function or class or whatever constant.* An example: You don't want a function dealing with structuring a report page doing regex stuff, this is clearly too low-level. Think about how you would explain your function with a few words to your colleage: "I look if there is a report header, else I use the default, then I add the body, and if there is a footer, I include it as well". And that's what the function should do. If it does some finicky stuff with the body, extract it. If it does some regex, definitely extract it, maybe even a level deeper, below the functions dealing with the header, body or footer. Having one abstraction level is much more important for readability than to avoid code duplication.

    • @kantancoding
      @kantancoding  5 месяцев назад +10

      People who complain about code extraction don’t write unit tests.

    • @jmbrjmbr2397
      @jmbrjmbr2397 5 месяцев назад +3

      @@kantancoding and they write unreadable spaghetti code :/

    • @moussaadem7933
      @moussaadem7933 5 месяцев назад +4

      That makes it hard to do anything beyond simple changes.
      I would usually agree that it's important to avoid mixing levels of abstraction, but I never thought someone would ever think that using a regular expression at high level code is a violation of that, regular expressions are not part of the hierarchy of concepts for a page

    • @thomai5287
      @thomai5287 4 месяца назад

      @@kantancoding This is is. There's a type of "professional" developers out there who complain about TDD (TDD and clean code go hand in hand imo) being so much overhead but wasting so much time debugging. I've seen people in enterprise context preferring to wait for minutes for the app to startup and testing every single iteration of their changes by hand instead of adding a little bit of code and refactoring and automating this process. LiveUnit-Tests give you feedback in seconds and in the long run you're so much faster with TDD.
      Seriously, I'm doing TDD now for a long time and I hardly need the debugger anymore. Tools like NCruch even show you the code paths or hot spots in your code these days.
      Using the debugger for me is mostly a sign that I'm working with ugly or untestable code.

    • @LPFan33
      @LPFan33 4 месяца назад +1

      best comment by far.

  • @SaddyMogikw
    @SaddyMogikw 5 месяцев назад

    Great brief explanation. As someone once said, bad coders know what to write, whereas good coders know what not to write.

  • @jordanmancini
    @jordanmancini 5 месяцев назад +3

    TL;DW
    Use guard clauses to check state for on/off condition code, switch statements are great and can be used for simplifying the if, ifelse, else chains, enums are also useful for conditions that are always going to be of a set type
    Reduce duplication by moving common code with similar purpose into their own methods. Single lines don't need to be methods, multiple line duplications probably should be moved unless they're inherently dynamic like DB calls.
    Make clear variable names for everyone to figure out what each variable is, single and double character names aren't helpful for describing what your variable is in most cases.

  • @vasiliynkudryavtsev
    @vasiliynkudryavtsev 3 месяца назад +1

    I really like how Verilog has freedom of the switch-like case statement to the point of "reverse case statement".
    E.g.:
    case(1'b1)
    isMaintenacePeriod: log.Fatalf("feature unavailable");
    !isAuthenticatedUser: log.Fatalf("invalid user");
    !isAuthorizedUser: log.Fatalf("permission denied");
    default: doWork();
    endcase
    SystemVerilog even adds features like unique and priority.

  • @Zaro2008
    @Zaro2008 5 месяцев назад +14

    1:46 All fine and good until you run into an auth error and can't figure out if it's an authentication or authorization problem. Don't do this!

    • @fusion.studioX
      @fusion.studioX Месяц назад

      you *should* do that, he just gave a wrong example.

    • @SirIlliterate
      @SirIlliterate 6 дней назад +1

      Yeah I was also thinking that he committed a much worse coding crime than having some deeply nested code. As someone who troubleshoots other people's code in Prod a lot, never ever do this and log incorrect errors.

  • @salvatore.masuzzo
    @salvatore.masuzzo 5 месяцев назад +1

    Writing readable code is an art and a great skill. I also think there is a lot more that could be covered here.

  • @RegrinderAlert
    @RegrinderAlert 5 месяцев назад +153

    It’s worth noting that the first example refactor changes the program behavior- they are not equivalent. It needs additional return statements in the guards .

    • @kantancoding
      @kantancoding  5 месяцев назад +82

      Actually that’s incorrect. It doesn’t need a return because log.Fatalf() will exit the program. It’s the same behavior as before the refactor.
      If you're curious: cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/log/log.go;l=417

    • @RegrinderAlert
      @RegrinderAlert 5 месяцев назад +55

      @@kantancoding Good to know! I’m clearly not familiar with Go 😅
      So for a more general refactor recipe one would basically make the (implicit) returns in the "leaves" of the control flow tree explicit before applying inversion and remove redundant ones afterwards.

    • @kantancoding
      @kantancoding  5 месяцев назад +39

      @@RegrinderAlert Yeah, that's my bad. I shouldn't have used such implicit code in the example. I didn't really think about it at the time 🙏

    • @eadwacer524
      @eadwacer524 5 месяцев назад +2

      On one hand it could be expected logging a "Fatal" message should exit(1) but in some systems allowing a logger to end the program could be seen as bad design or dangerous.

    • @FreggeLock
      @FreggeLock 5 месяцев назад +61

      It's a weird behaviour when a logging function exits program

  • @ChasRMartin
    @ChasRMartin 3 месяца назад

    This is quite good, and as someone who’s been teaching programming for 40 years, you’d be amazed at how many college instructors will teach exactly the things you’re saying not to do.

    • @kantancoding
      @kantancoding  3 месяца назад

      Thanks for watching! Great to see so much experience in the comments section 🙂

  • @lleytonmorris6305
    @lleytonmorris6305 4 месяца назад +5

    I really need to say this. The advice to never nest your code has been repeated for years now, it's the same thing that I was told 8 years ago when I was reading about how to write VB code in highschool and it is a good concept, but It's important to note something.
    In the provided example, yes, I think the code is easier to read, but it does not change the conditions you need to keep in your head. If you are doing more complex logic that then relies on the state of those conditions, you still need to know that those conditions are not true for example.
    Take authentication. If you have middleware the runs in an endpoint, or your function verifies that the user is authenticated before running, you still need to know that condition has been met, otherwise you will end up writing redundant code in your actual logic to ensure that the user is authenticated. Whether your nest or don't nest the condition, you need to be aware of the state your app can be in at that point in the code, and both ways require you to have this awareness.
    I agree that in most cases, early returns are better, but just wait until you have a function that has a structure like this and tell me it isn't confusing AF
    if (A) return
    // logic
    if (B) return
    if (C) return
    // logic
    if (D) {
    //logic
    } else {
    //logic
    }
    if (E) return
    //logic
    The code looks readable, but damn, it can get confusing, especially if the code I put as "//logic" can potentially return, therefore adding conditions that aren't even ovbious. Sometimes the real solution in my opinion is to double up your conditions just for understandings sake, or in a complex case, abstract where necessary. The idea of code inversion is very primitive and does not always apply

    • @kantancoding
      @kantancoding  3 месяца назад +2

      These are very good points. Thanks for the insight!

    • @Jacksparrow4986
      @Jacksparrow4986 3 месяца назад

      When you check all your preconditions at the start you're fine. If the logic is complex, by all means, use nesting.

  • @tonytortuga51
    @tonytortuga51 Месяц назад

    30 seconds into this video and I liked and subscribed. The animations and simplicity are great

    • @kantancoding
      @kantancoding  Месяц назад

      Thank you for the support and the kind words!

  • @anantmishra6783
    @anantmishra6783 5 месяцев назад +7

    Also, User not authenticated and use not authorized are very different things, its just wrong to have a common response to both!

    • @ellonmusketwannabe2201
      @ellonmusketwannabe2201 Месяц назад

      yea, he did point it out tho. Soo like maybe we should do his approach just to make it "readable" in exchange for information

  • @alexandrosweeb8059
    @alexandrosweeb8059 5 месяцев назад +2

    There are a lot of Videos, on general readable code, but I would like to see one, which gives you tips, for structuring a whole Project, at what point to use Classes or what valid to put in Arguments or write a whole new Function for etc.
    Good Video tho :)

  • @Speed-TV
    @Speed-TV 3 месяца назад +6

    I'm surprised this video didn't mention comments. At 2:05, couldn't you instead put comments on what the code segment does instead of making it a small, one-use function? I think it would be harder to go and search for function definitions.

    • @olivetho
      @olivetho 2 месяца назад +4

      could have also put the condition into an aptly named variable if it was so badly needed. whenever i find something like this in a code review i tend to immediately reject it, because stuff like this make the the code as a whole harder to navigate. imagine if every single condition longer than a single word were to be its own function - the whole thing would fill up with one liners that each take up 5 lines instead. i'm not braindead enough to be stumped by a single AND statement, i'd much rather keep the code concise.

  • @LokiDaFerret
    @LokiDaFerret 5 месяцев назад +1

    OMG. I love how your refactoring of code actually changes the logic and you somehow breeze right over that as if that's not kind of important??!!
    I think I need to make a video about rookie developers doing RUclips videos. 🤣😂🤣😂

  • @nijolas.wilson
    @nijolas.wilson 5 месяцев назад +6

    Point 1 is good. Point 3 is a bit of a dramatic example IMO but it does happen 😅 and there's no such thing as perfect names, so be sure to write good tests and documentation anyways.
    Point 2 though - "Avoid duplication" is I think the wrong advice to be giving, it's missing the actual problem.
    The problem is poor abstraction, or in other words a lack of / unclear / poor separation of concerns. Lots of duplication is simply a symptom of poor abstraction. Your problem is in your design choices, not your duplicated code.
    Software design boils down to asking the question "should this code be shared or not" over and over again. By saying "avoid duplication" you imply the answer to that question is always "yes" when in fact that's not going to give you good results. Sometimes duplication is the right answer.
    The key question, where you want to focus your intent, is on where and why code is shared, and whether it should be or not. Don't simply avoid duplication, that's a shortcut that will lead you into poor design choices and will prevent you from improving as an engineer. This is the exact reason why the calculateTaxes extraction from the first example is a bit iffy - I understand it's a bit contrived on purpose, but it clearly illustrates the problem of preferencing "cleaner" code instead of making a thoughtful abstraction, because the latter is what really matters. The developer's intent is in the wrong place, i.e. "perceived cleanliness" instead of thoughtful, intentional, useful abstraction.
    The most effective way to produce good abstractions is through strong TDD practice. As you improve your practice the best abstractions will start to become obvious, and you'll stop thinking about code duplication and "cleanliness" altogether, because it turns out to be somewhat irrelevant in the quest for "good" software.
    ---
    I'll throw in a Point 4 for free, don't use double negatives in conditional logic. This feeds into data design, too, naming a field disableTooltips just means later someone has to write if (!disableTooltips) { enableTooltips() }... It makes logic just enough harder to read to be not worth it. This is a simple example but add a clause or two or a few indentation levels and the cognitive load adds up quickly.

    • @doc8527
      @doc8527 5 месяцев назад +4

      I probably agree everything you said.
      The point 1 rewrite is good enough and I think the rest of the video is kinda pointless to the original problem.
      After the first one, like merging conditions into one, everything becomes a readability preference, it doesn't affect the readability and you lose the log difference and the fine details. The point 2 (convert multiple conditions into isValidUser) is purely wrong in my opinion, it's pure premature abstraction without good reasons. Unless you told me that isValidUser is fairly common and used in many spots.
      Last point is just pure assumption of what your code will be in the future, the reality is that 90% of the time your predication will be wrong. Had seen countless cases where people/colleagues rewrote their pre-imagination refactor/abstraction over and over, which can be avoided at the really beginning if they kinda listen my suggests. Also, reduce my PR review time.
      So instead of trying to do the abstraction early, leave it like that is totally fine. If you need to use many "what if" for an abstraction, likely you shouldn't do it. I felt like the whole video falls into the trap of premature abstraction after point 1 unless the author didn't include those context in the video.

    • @nijolas.wilson
      @nijolas.wilson 5 месяцев назад +2

      ​@@doc8527 Yeah I totally agree with you. I also don't think deep nesting is a sign of an "inexperienced developer" necessarily, but premature abstraction all over the place is definitely a strong sign of inexperience 😅

    • @kantancoding
      @kantancoding  5 месяцев назад +1

      @@nijolas.wilson Hmmm I feel like you both are applying the wrong argument to what I'm trying to explain. I'm not adding abstractions to the code based on some prediction in the future. I'm simply writing each function so that it does one thing and only one thing. There's a difference between:
      ```
      package main
      func calculateTaxes(product product) {
      switch product.Type() {
      case "alcohol":
      total += alcoholTax
      case "electronics":
      total += electronicsTax
      default:
      total += generalTax
      }
      }
      func main() {
      for _, product := range cart {
      calculateTaxes(product)
      }
      }
      ```
      AND
      ```
      package main
      type calculator interface {
      calculateTaxes(product product)
      }
      // implements calculator
      type taxCalculator struct{}
      func (tc taxCalculator) calculateTaxes(product product) {
      switch product.Type {
      case "alcohol":
      total += alcoholTax
      case "electronics":
      total += electronicsTax
      default:
      total += generalTax
      }
      }
      // "constructor"
      func newTaxCalculator() calculator {
      return taxCalculator{}
      }
      func main() {
      tc := newTaxCalculator()
      for _, product := range cart {
      tc.calculateTaxes(product)
      }
      }
      ```

    • @nijolas.wilson
      @nijolas.wilson 5 месяцев назад +2

      ​@@kantancoding Sorry, I didn't mean to imply by my response that I think your example was premature abstraction, or that I think you are an inexperienced developer 🙏 that was meant as a separate commentary. I agree, and to me it's very clear the point you were making.
      I don't think that deeply nested code is a sure sign of an inexperienced developer, but I also don't think what you were showing was premature abstraction. 🙏

    • @kantancoding
      @kantancoding  5 месяцев назад +1

      😂 the part about deeply nested code being a sign of an “inexperienced” dev was likely a bit of an oversimplification/overgeneralization on my part.

  • @nil.4
    @nil.4 4 месяца назад +1

    Changing my habit of writing code is better for the next developer to continue my work. But what will be more valuable for me is to be able to understand the messy code that someone else has written with bad patterns.

  • @ajcgpol
    @ajcgpol 5 месяцев назад +37

    The "merge related if statements" example you gave is in fact a bad advice. Authentication and authorization are different things. You are not just "losing granularity", you are shooting your foot with a wrong log message. You'll waste time trying to solve an authentication issue thinking it's an authorization issue.

    • @ajcgpol
      @ajcgpol 5 месяцев назад +3

      Nevertheless I agree with all rest, its very well presented animated and edited.

    • @kantancoding
      @kantancoding  5 месяцев назад +4

      Hey, I see your point. I mentioned you’d lose that granularity in the video. But yeah, next time I’ll spend more time on the example code 🙂

    • @oliver_twistor
      @oliver_twistor 4 месяца назад

      Yeah! Additionally, it makes it difficult for the programmer because if they have to change the authorization code, they would also have to touch the authentication code (or at least, it would look like it for someone doing a diff on the code). A function should ideally just be responsible for doing a single thing.

    • @TazG2000
      @TazG2000 2 месяца назад

      ​@@kantancoding The user validation could actually be extracted functions like "requireAuthenticated" and "requireAuthorized" that both throw their own errors. The authorized function calls the authenticated function, and the main function function only needs to call the authorized function.

  • @DraconiusDragora
    @DraconiusDragora 3 месяца назад +1

    Law nr.4: Always comment on what something is doing. Not only will this allow you to remember why it was made in the first place, but it can help anyone else who needs to look over your code. This can also help them find a function, a code block, or array they might need to change.

  • @JaredBuckley93
    @JaredBuckley93 5 месяцев назад +2

    The first example does not make the conditions any less relevant, or further out of mind. It’s just easier on the eyes. I hear this incorrectly described too often.

  • @paulsander5433
    @paulsander5433 4 месяца назад +1

    There is also a "five finger" rule. The origin of this rule was back in the days when code reviews were done with printouts. Reviewers would identify the entry point of the code under review, then use fingers as bookmarks to walk through the program logic to evaluate its correctness. This agrees with the 5+/-2 rule of human short-term memory, which effectively means that if there are more than about 5 nested function calls, the reviewer would lose track of the logic, and the time and accuracy of the review quickly deteriorates.
    Although there are code inspection tools that can search for symbols and keep a stack of examination points, the 5+/-2 rule is a limitation of the human reader and remains an issue.
    Executing these three laws also serves to reduce a metric called Cyclomatic Complexity. This is a measure related to the number of branches and loops in a piece of code (including short-circuit operators), and it's interpreted as a measure of the likelihood that a section of code contains a bug. It's often used to guide code reviews and testing. It reinforces the notion that code that lacks complexity is both more correct and easier to inspect.

  • @DoggoDebugger
    @DoggoDebugger 5 месяцев назад +4

    Im not sure why but somehow lots of Go code using single / double character names which annoys the hell out of me.
    Good stuff, keep it up!

    • @kantancoding
      @kantancoding  5 месяцев назад +2

      Thank you! Yeah that has always annoyed me too! It seems some Go people would rather be terse but I always get irritated reading that type of code.

  • @fallenarchangel8709
    @fallenarchangel8709 3 месяца назад

    gotta admit, did some of the nesting 'mistakes' you mentioned, noted it under great advice, for naming i find it the best to write the convention used in a comment at the start of the file. not sure how helpful it is to some people but since there are a few i wanted to make it easier for potential other programmers

  • @diegoaespitia
    @diegoaespitia 4 месяца назад +3

    i was gonna say make sure you use comments. much easier imo than searching through code again

    • @ShotzInTheLight
      @ShotzInTheLight 4 месяца назад

      As someone who only got into learning C quite literally last week, I'm shocked and awed at how few comments (pun unintended) there are about commenting, and that it wasn't even mentioned in the video. The amount of times I've searched on stack overflow to try and find a solution to a problem I was having and only understood what it was I was looking at when the poster explained what the code did afterward...comments are an ACTUAL godsend for anyone trying to read your code!

  • @ilikegeorgiabutiveonlybeen6705
    @ilikegeorgiabutiveonlybeen6705 5 месяцев назад +1

    yeah and on the extraction
    rule of thumb: 3 to 5 actions, which produce 1 action when combined.
    e.g. function is "cook potatoes"
    actions would be "peel potatoes", "chop them", "choose a way to cook them", "cook them the way you chosen to".
    if you can split an action into more or equal
    amount of actions you should do so.
    e.g. ",cook potatoes the way you chosen to" can split into "get the chosen cooking method", "prepare for cooking" and "do the cooking".
    you can split actions into more actions into more actions. they only need to result in one concrete thing and be semantically similar.
    like e.g. when we cook we will do things related to cooking. we will not focus on arbitrary actions like "take the knife in your hand" if it isnt obvious how we will relate that action in the context of splitting another one ("take the knife in your hand" and "make a chicken stock" arent obviously related, imagine if we dont have to chop anything why did we take the knife then?)

    • @ilikegeorgiabutiveonlybeen6705
      @ilikegeorgiabutiveonlybeen6705 5 месяцев назад

      why 3 to 5
      becsuse human brain ram is approx 5-9 statements depending on the person
      so youre always within your brain's 1 stack frame (maybe even 3 if youre lucky). thats easier to reason about what

  • @akatoxa
    @akatoxa Месяц назад +3

    Saw the video until 2:42 instead of one single function that I can read and understand I’ve got multiple functions, multiple contexts, you forced me to remember what’s going on in all functions, and a simple code becomes unreadable. Bravo

    • @kantancoding
      @kantancoding  Месяц назад

      Skill issue brother

    • @cjendantix
      @cjendantix Месяц назад +1

      ​​@@kantancodingNo, what you presented is terrible advice. One should only extract logic into its own function if it is used multiple times. There is no benefit to extracting logic, especially if it pertains to the original function.

    • @kantancoding
      @kantancoding  Месяц назад

      Sure, we’ll just put everything in main() if it’s only used once. No need to logically structure our applications.
      And the comment you are responding to is completely unrelated to what you’re saying man.
      I’m all for opposing arguments but your argument is useless since you don’t give any type of explanation of why or how you came to that conclusion.

  • @MrC0MPUT3R
    @MrC0MPUT3R 5 месяцев назад +2

    One thing I tend to prefer in my code is avoiding the ! operator in if statements. Sometimes it can be missed by someone who is going through the code quickly and therefore I tend to try and make all my boolean variables describe what the condition is. Of course don't go out of your way. Sometimes a quick ! is better than an extra line of code to flip/rename a boolean.

    • @rhone733
      @rhone733 5 месяцев назад +1

      Agreed. I don't like that tools like clang-tidy advocate for less readability rather than more. I always spell out my boolean comparisons.

  • @kevintanudjaja7597
    @kevintanudjaja7597 5 месяцев назад +9

    problem in second law is, when trying to avoid duplication, I also need to do transaction in SQL, and every transaction function always differ from other

    • @w花b
      @w花b 5 месяцев назад +8

      Every law has its edge case. The point where it divides by zero or reaches infinity or whatever. Just adapt. A law is just a guideline, not something to follow religiously like a robot.

    • @kevintanudjaja7597
      @kevintanudjaja7597 5 месяцев назад

      This is exactly why Chris McCord make significant change in elixir 1.12 to 1.13. He change 'Model' in MVC to 'Context', partly because people just keep using the generated Model to talk to db without using proper transaction. Every transaction SQL will different from each other. Race condition is fine if you just want to make small blog, but not in banking app

  • @LokiDaFerret
    @LokiDaFerret 5 месяцев назад +2

    Law #4: never right negative properties. The name of your property should be something such that knowing what the default is is very obvious and straightforward. If you find you are putting the word not in your property names then you have it backwards. Example: DontProcessX. In this example in order to process x you would need to pass false. You're much better off with a property called ProcessX

  • @RegisMichelLeclerc
    @RegisMichelLeclerc 5 месяцев назад +3

    Law zero of readable code is to comment abundantly and always keep in mind that the reader might be... future yourself. I don't know about Python, but Perl, Java and C can be deceptive... Blocks of code? Tell me about looking for a bug in a regular expression just 6 months later in a function called from pretty much everywhere in hundreds of lines of code (i.e. a small program).
    Also, do abuse functions/procedures and merge them to the main code (with lots of comments!) only at the very end when they're called only once (just get the xref to get the list). The thing about code being unique but called from multiple locations is a well known paradigm of "proper" databases: each piece of information is semantically unique, otherwise you'll pay that in code and data size, and each chunk of data is identified uniquely. These are base rules, but everything else derives from that, you'll think of me when you need recursivity and callbacks: pointers on lambdas are a thing...

    • @oliver_twistor
      @oliver_twistor 4 месяца назад +1

      Yeah, documenting is a must. When working on multiple projects at once, as one often does when programming as a career, makes it increasingly difficult to remember what a particular piece of code does. I think I was lucky in this regard when I first learned programming. I was born with a disability that among other things has affected my memory. As such, I have always commented my code a lot, out of sheer necessity, because if I don't, I forget what the code does the next day (heck, some days I wouldn't be able to remember over the lunch break...).

    • @RegisMichelLeclerc
      @RegisMichelLeclerc 4 месяца назад +1

      @@oliver_twistor have you ever tried coming back a month later to a Perl script full of uncommented RegExes? ;-)

  • @anantmishra6783
    @anantmishra6783 5 месяцев назад +2

    the switch case does not need to be in a separate function, while you can modularize anything, just having a separate function for the validations and the logic is a good amount of modularization with reasonable "locality of cose" so the un-necessary navigations are not needed!!!

  • @Jakub1989YTb
    @Jakub1989YTb 5 месяцев назад +7

    I call it "The indian arrow." Once our externist (guess from where) gave us a 10x (I kid you not) nested code. Truly amazing.

  • @ninja47rei77
    @ninja47rei77 16 дней назад +1

    Quite late, but this video is amazing, bug thanks on the tips. Also, what programming language is that?

  • @nxone9903
    @nxone9903 4 месяца назад +6

    0:32 flutter devs in shambles rn

  • @dvorszkydavid9140
    @dvorszkydavid9140 5 месяцев назад +1

    As someone who taught many programming students, I'm glad to say I quickly understood the bad code in the last example :D

    • @kantancoding
      @kantancoding  5 месяцев назад +2

      😂🤣I too have had lots of exposure to that one

  • @MrHaggyy
    @MrHaggyy 5 месяцев назад +1

    The first one is mighty if you use it with de morgan's law. It can also be an optimization if "isAuthenticated" and "isAuthorized" are packed in the same register or data in cache. In that example you might want to keep the granularity for log messages. But in machine control you might have a bunch of conditions that would force your machine to stop if one is false.
    If i had to add one rule it would be keep the same flow control statement for the same type of task. If you keep the number of language features low and the syntax similar it's much easier to understand the problem the code is solving.

  • @itstato
    @itstato 5 месяцев назад +3

    Thanks for the suggestions 😊

  • @needMoreInput
    @needMoreInput 2 месяца назад

    In teaching, i'd recommend mentioning the titled names of the 3 'laws' in the end of the video to help brains retain the information. Second, I recommend after making final adjustments to code explanations that you give a side by side of original and final. This helps brains understand structure, just like the first 'law' is alluding. This would elevate your vids from good to fantastic.

    • @kantancoding
      @kantancoding  2 месяца назад

      Awesome tips! Thank you, I will try to incorporate them :)

  • @zxuiji
    @zxuiji 5 месяцев назад +16

    2:06 While extraction is good in some cases, in this case that is a step too far. Code is easier to reason about if you keep basic operations inside the main function. You should only extract large chunks of code into their own inline functions. They should be inline to start with because you have no reason to take external code doing the same thing into account until it becomes common enough.
    Somewhere about 3-5 usages is the minimum amount for this, depends on the complexity of what is being done. In the case of one liner expressions extraction is in fact incredibly poor developer behaviour. If the code spans at most just a few lines (3-5) then an inline function is all you need for it. If it spans 5+ lines then it's a candidate for a shared function to avoid mistakes in logic due to complexity.

    • @kantancoding
      @kantancoding  5 месяцев назад +4

      The argument that extraction should only be used if the code spans a certain number of lines doesn't make any sense to me. Why some arbitrary number of lines?
      Better to have a real reason regardless of the number of lines. The reasons are as follows:
      - separation of concerns
      - modularity for testing
      - reduced surface area when debugging
      - readability
      - enables us to test individual behaviors in isolation
      - the above mentioned tests have the added benefit of serving as documentation for how each individual functionality should work

    • @zxuiji
      @zxuiji 5 месяцев назад +2

      @@kantancoding All those reasons are auto-covered by the line count. The more lines the more context you're trying to keep in mind when looking at the whole function. 1 to 3 are easy enough but it get's a bit unreasonable from there on. However jumping straight 2 a shared function when extracting is not always a good idea which is why I say use a local inline function. Also for many people, both newbs and devs, a line count is easier to understand or even remember than a bunch of more complex rules. You can simply reference those rules as the reason for the line count. At bare minimum the line count helps identify code that should at least be considered for extraction. It's like using if ( line_count >= 3 ) extract = should_extract( lines ); You're simply skippimg the complex stuff until the line quota is met

  • @ubuntu1960
    @ubuntu1960 4 месяца назад +1

    In fact, the first rule is a design pattern named Early return. Which can be refactored to Chain of Responsibility in more complexe situations.
    Both second and third rules made me think about respectively the third and second rules of the 4 rules of simple design from Kent Beck.

    • @kantancoding
      @kantancoding  3 месяца назад +1

      This is actually very helpful. After looking into these I’m thinking of expanding the series. Thank you!

    • @Jebusankel
      @Jebusankel 3 месяца назад

      It's also called a guard clause

  • @electricz3045
    @electricz3045 2 месяца назад +19

    I was on your side until you talked about the functions. Don't get me wrong, functions are useful, however, functions which only have 1 return line or less than 5 lines altogether, are unnecessary and using those would require the reader to potentially jump around in different files to find what the main code is doing.

  • @burnsy96
    @burnsy96 5 месяцев назад +2

    Great, thanks to you I no longer get to know if it's my email or password I typed in incorrectly.

  • @user-qr4jf4tv2x
    @user-qr4jf4tv2x 5 месяцев назад +3

    to me one thing i've learned is
    1) make sure functions can run without relying on global variables this to improve testability
    2) all false or invalid states should be on top most
    3) nest as few as possible.
    4) don't apply abstraction too early until you actually know you will be repeating it this so you don't end up with 1 time use functions
    5) avoid iterating a database rather turn it into a function from the database

  • @dodouniquelife9725
    @dodouniquelife9725 11 дней назад

    ❤ Thank You Very Much ❤... Probably & Maybe This is One of the Best & Useful Video of My Coding Life Journey

  • @raoulkent
    @raoulkent 5 месяцев назад +3

    Good stuff! This is the type of stuff that doesn't go out of style :)

    • @kantancoding
      @kantancoding  5 месяцев назад

      Thank you! I’m glad you liked it 🙂

  • @FreddyRangel85
    @FreddyRangel85 5 месяцев назад +2

    Usually it's better to sacrifice performance for readibility. This is where CS programs set up their students for failure when they get to the real world. The thing you really need to optimize for is Mental RAM. Engineering time is expensive. Writting code that's easy to understand and refactor and is tested will save engineering time in the long run.

    • @oliver_twistor
      @oliver_twistor 4 месяца назад +1

      So true. And increasing computer processing power is generally much cheaper than increasing human processing power. :) It's better to let the machine read one more line or one more byte than having the engineer waste a day trying to understand their colleague's messy code.

  • @etilworg
    @etilworg 5 месяцев назад +5

    variable naming has the problem that it dependes of the program model inside the programer's brain , you must choose names that make sense in context of what are you writing.
    " Future readers of your code " will never have the same brain model until they read enougt code .
    Java fully qualify names is the solution , and i hate it.

    • @kantancoding
      @kantancoding  5 месяцев назад

      Hmm, this is a good point actually 🤔

    • @Fanmade1b
      @Fanmade1b 5 месяцев назад +3

      "The two hardest problems in software engineering:
      1. Naming things
      2. Invalidating cache
      3. Off-by-one errors"

    • @oliver_twistor
      @oliver_twistor 4 месяца назад

      That's why, in my opinion, a variable should be short-lived; preferably only be used once and also be defined as close as possible to the code that uses it. I never reuse variables, I treat variables as write once, read (preferably) once. I hate it when reading someone else's code and they have defined a variable with some dubious name on line 10, and then first use is on line 46, it's redefined with a new value on line 51 and finally used in a totally different context on line 123.

  • @xaviconde
    @xaviconde Месяц назад

    If your function has validations of the style "if (something is true) then (exit function) ", you should use something like Assert() in java, which throws an exception and stops the execution of the function. It also makes code more readable, as it is self-documenting too.

  • @johnjoeson3115
    @johnjoeson3115 5 месяцев назад +46

    Kind of ironic that a video about readability flashes words onto the screen instead of displaying the full text and animating it... ;P

    • @kantancoding
      @kantancoding  5 месяцев назад +6

      Absolutely brother!

    • @chersymale
      @chersymale 5 месяцев назад +10

      i had zero issues following along with the video, maybe just, use your eyes?

    • @johnjoeson3115
      @johnjoeson3115 5 месяцев назад +6

      @@chersymale "use your eyes?" lol
      It seems you missed the ";P". My comment was meant to be tongue in cheek.

    • @lelsewherelelsewhere9435
      @lelsewherelelsewhere9435 5 месяцев назад

      It's supposed to be, so you realize it.
      (Can't tell how much of this is sarcastic, I know I know, always assume someone online is sarcastically wrong...)

    • @seanchen9771
      @seanchen9771 5 месяцев назад

      the results are so readable you only need a split second.

  • @JawwadHafeez
    @JawwadHafeez 3 месяца назад

    Thanks ... i coded both ways .. depending upon size of ifs .
    But there is one thing tht needs consideration and tht is sudden return on a condition unmet leads to a break and processor cache clearing.. tht is taxing. Will appreciate ur input.

  • @JJ-nh9vm
    @JJ-nh9vm 5 месяцев назад +3

    The only thing I have a problem with is the if statement in the first code. Ie; ! isAuthenticatedUser || ! isAuthorizedUser
    First of all I would be really wary of putting the both conditions into one if statement. The reason being clarity. We can only use one error message the way you recode it, which could work but it limits how descriptive the code is.
    Second thing is, that by having the if statement in its own method, you don't make the code better. It doesn't make it shorter or more readable. I would even argue the opposite for both. The code is longer and you have to search the code for the method which could be just there. The conditions are descriptive so there is no problem there. Of course in such a short code it doesn't change much, but when you are working with longer, more complex code, you would only make it harder for others to understand the code.

  • @StarContract
    @StarContract 5 месяцев назад +2

    I often wonder if modern developers work on solving actual problems, or just moving text in their IDEs.

  • @naranyala_dev
    @naranyala_dev 5 месяцев назад +5

    more like this please

  • @BeTeK11
    @BeTeK11 4 месяца назад +2

    I'm a software engineer of 15 years and my main thing is that code is not for the computer. It's for the person reading it. Just think "is this code understandable for person that has no context" you write so much more clean code.

  • @Akrob55555
    @Akrob55555 5 месяцев назад +20

    You seem to have completely missed the big backlash against DRY. The biggest tell that someone is a junior programmer is when they start creating premature abstractions to avoid code duplication.

    • @oliver_twistor
      @oliver_twistor 4 месяца назад +2

      I think this was just an easy example of the concepts. If this was the total extent of the program, I would agree with you that extracting the code into separate functions is a bit excessive. DRY is good; the problem isn't that junior programmers abstract prematurely or too much, the problem is that they try to abstract the same literal code, but from multiple contexts, which will cause problems later on when they want to make changes to the extracted code. But if one make sure that the abstraction also is semantically the same, not just lexically the same, the DRY principle should be good.
      I feel the same way with the inheritance backlash. In my experience, most of the criticism comes from misuse of the technique, not from the technique itself. Same with OOP, a lot of criticism comes from either lack of understanding what it is or plain old misuse.

  • @Maxime2bleau
    @Maxime2bleau 5 месяцев назад

    Good advice!! I would summarize the video at the end just like the thumbnail, learning is based on repetition after all :D

  • @larsp5109
    @larsp5109 5 месяцев назад +6

    1:12 Well, the code kinda needs a “return;” after each log.Fatalf()…

    • @yahyafati
      @yahyafati 2 месяца назад +1

      I think Fatalf throws error

  • @SahilP2648
    @SahilP2648 5 месяцев назад

    One other thing you can do is maintain objects so you can use the 'if' statements wherever you want, to check for the current status of the block of code you are running. Sometimes it won't work but most of the times it will work.

  • @NStripleseven
    @NStripleseven 5 месяцев назад +3

    I’m personally not a fan of some of those function extractions. If I were reading the code, I might not know exactly how calculateTaxes works, but if instead you added a comment saying “// Calculate taxes”, I’d know exactly what you intended the code to do and also exactly how it does it.

    • @aiacfrosti1772
      @aiacfrosti1772 5 месяцев назад +6

      With extraction, the general idea is "Either you are working on the program's general functionality and you don't care about the implementation; or you are worrying about the implementation and don't care about the overall flow" so you separate those two things by tucking certain details into its own part of the codebase. With many IDEs you can simply control+click the function you want to know more about and it will jump you right to it!

    • @kantancoding
      @kantancoding  5 месяцев назад +6

      Wow finally somebody who knows how to go to function definition.
      Another point, with calculate taxes extracted, you can now write unit tests to test that functionality in isolation which will have the added benefit of providing documentation about what calculate taxes does if you really need to know. Boom, no need for excessive commenting

    • @zachmanifold
      @zachmanifold 5 месяцев назад +1

      I would argue you probably don’t *need* to always know what every piece of code does at all times. And if you do, you can easily go to it. If you are debugging, editing or expanding this function you can easily follow what’s going on and where to add/edit the logic without reading the entire implementation line by line

    • @oliver_twistor
      @oliver_twistor 4 месяца назад

      @@zachmanifold Yep. It would be a pain if all code for all standard libraries and such would be inline. A simple Hello world program would take several sheets of paper.

  • @Zaborovnik
    @Zaborovnik 4 месяца назад

    My advice is to put lots of boolean statements into one integer, and compare the resulting integer with the combination you need for code execution. Example is calling an integer "status", defining 3 as admin, 2 as trusted, 1 as worker and 0 as unauthorised. Then you can make switch status, case admin, trusted etc

  • @raytry69
    @raytry69 5 месяцев назад +12

    My first law of readable code: opening and closing curly brackets has to be in the same column.

    • @damianjblack
      @damianjblack 4 месяца назад

      100%

    • @notyourfox
      @notyourfox 4 месяца назад +1

      const char* this_is_also_acceptable() {
      if (closing_bracket_matches_statement_start) {
      return "ok";
      }
      return NULL;
      }

    • @damianjblack
      @damianjblack 4 месяца назад +1

      @@notyourfox I have a hypothesis that a lot of people who use this style learned Pascal first and wrote "IF x >= 10 THEN BEGIN" ;-)

    • @notyourfox
      @notyourfox 4 месяца назад +1

      @@damianjblack I have learned some Pascal (and now remember almost none of it, didn't ever really use), but my first language was Python
      Also I remember writing begin on a new line smh :)
      I was generally a terrible coder back then

    • @damianjblack
      @damianjblack 4 месяца назад

      @@notyourfox I have to admit I love Pascal and my main hobby project is in FreePascal. And yes I still put BEGIN on a new line for some of my code. IFs and loops not so much but usually for procedures and functions.

  • @Marcus_613
    @Marcus_613 3 месяца назад

    3 if checking predicates and handling error log isn't bad nested code at all !
    What make a version better than the other is the coding convention of your team/project.
    Coding, is most of the time a team effort. It is more relevant to write code following the same conventions your team use than following the rules you learnt at school.
    Nested if can be very structured and you can really get used to it as long as all the code of your project is consistent.

    • @kantancoding
      @kantancoding  3 месяца назад

      I agree that team conventions and consistency trumps many things. Well said

  • @dynamicplayz1897
    @dynamicplayz1897 5 месяцев назад +6

    Just add a comment instead of converting it into a function, adding too many functions can also cause confusion when they are only being used once.

  • @chwynn
    @chwynn Месяц назад

    When you invert an if statement like that to eliminate the else block you need to also return inside the if, or you'll execute all else blocks which changes the logical flow.

  • @iCrimzon
    @iCrimzon 5 месяцев назад +8

    First law: Use Go, Second law: ???, Third law: Profit

  • @_MB_93
    @_MB_93 Месяц назад

    Recently I have encountered code like this:
    if a >= 1 {
    do something with variable C
    }
    //...50 lines later
    if a >= 2 {
    do something with variable C
    }
    Yeah it's not nested, but then readers might just read the 2nd block and skip the first, nested version force readers to read 1st block too:
    if a >= 1 {
    ...
    if a >= 2 {
    ...
    }
    }
    Sure you can blame the readers for not being careful, but why not take a step back and less mistakes will happen?

    • @kantancoding
      @kantancoding  Месяц назад

      This is a really good example.
      The solution is to extract those 50 lines to well named functions/methods segmented based on their responsibility but people will undoubtedly complain about needing to jump to definition 😂

  • @Zmey5656
    @Zmey5656 5 месяцев назад +5

    First rule for me - you need to remember about Clean Code

    • @kantancoding
      @kantancoding  5 месяцев назад

      Yes!

    • @plaintext7288
      @plaintext7288 5 месяцев назад +3

      Thirst rule - don't forget to hydrate!

    • @CartoonyPirate
      @CartoonyPirate 5 месяцев назад

      Silly programmer. That's not what 'thirst' means!

    • @Zmey5656
      @Zmey5656 5 месяцев назад

      @@CartoonyPirate thanks))))

    • @steveandamyalso
      @steveandamyalso 5 месяцев назад

      Regarding clean code: placing the opening containers at the end of the line, which is the case for every if() and for() statement of these examples, is NOT clean code and is, therefore, NOT readable code. The nested if() statements are made much harder to track because the formatting is unnecessarily compact. Frankly, the nested if() statements with the open container at the end of the line is less code structure and more ASCII art. Please stop showing young programmers your ASCII art!

  • @LocherYT
    @LocherYT 5 месяцев назад +1

    Me and my buddy like to make fun of naming conventions and terminology while we work together. The first rule would have been termed to 'hire a bouncer/hire a guard' - in order to write the subroutine exits at the top instead of dangling elses. Interface Connectors are called 'Handshakers'. We are writing code that are full of insiders.
    Also i've learned to better name functions as getOneUser/getManyUsers instead of getUser/getUsers due to quick readability

    • @tjdewolff5104
      @tjdewolff5104 5 месяцев назад

      Not being overly enthused by silly 'guard clauses', I like your functionnaming scheme of and (please note the application of capital(s)). I myself use a simmilar scheme of _ or even _, like 'Get_Object()' or 'Read_Line()' or 'Show_GrandTotal()': all for the benefit of readability...

  • @presi3005
    @presi3005 5 месяцев назад +32

    You forgot one rule: COMMENT YOUR CODE... No seriously, it takes like 5 seconds to do and makes any and every piece of code much easier to read and understand. So please, for the love of god, comment your code.

    • @henrymoutarde605
      @henrymoutarde605 5 месяцев назад +29

      Good code doesn't need comments tho

    • @zyriab5797
      @zyriab5797 5 месяцев назад +17

      I try to use comments to explain "why".
      If I feel like I should explain "how", it means the code is not readable/explicit enough.
      Besides, comments tend to "expire" during refactoring and updates, especially when working in teams.

    • @kantancoding
      @kantancoding  5 месяцев назад +20

      Excessive commenting is useless and actually not good practice in my opinion.
      Not to mention, if you write unit tests you are actually documenting how the code should work so comments should really only be needed on rare occasions.

    • @rdubb77
      @rdubb77 5 месяцев назад +4

      No. excessive comments is a smell. Code should be self documenting by being readable, and only in rare cases comment if there is some necessry tricky logic.

    • @mrk131324
      @mrk131324 5 месяцев назад +1

      Well written code with well chosen names should be self explanatory.

  • @rafaeldeleon3386
    @rafaeldeleon3386 5 месяцев назад +1

    I checked out this video to see if there was anything off but this was well explained. Nice work. In JS, eslint can be used to check deeply nested conditionals. Maybe go over something like that

  • @alexaneals8194
    @alexaneals8194 5 месяцев назад +1

    One word of caution, you should never default to valid with authenticating a user. So, that's one area where allowing the code to drop through when by checking !valid instead of valid will get you into trouble.

  • @jelarar
    @jelarar 7 дней назад

    I would add a small caution to the first law: don't overuse it. I've had to see code where every little thing has been extracted to external functions: suddenly to understand a process you have to track it across dozens of functions in multiple different files, and it actually makes code readability worse. As with everything, the key is finding a good balance. Other than that, great three laws.

  • @danielalbuquerque6049
    @danielalbuquerque6049 4 месяца назад

    this save me in job, i took hours to find a error in a script that i made. after refactory, using functions was very much easier to find

  • @Theopheus
    @Theopheus 2 месяца назад

    Keeping in mind, I'm a novice who learn everything I know about programming like 20-30 years ago....
    I'm not a fan of the bracket formatting in these examples. I always thought it was clearer to put the { on it's own line, vertically in line with the } so you could easily see where the condition starts and ends because they're lined up vertically.

    • @kantancoding
      @kantancoding  2 месяца назад

      Yeah, I can see that. Depending on the language/team, there may be different ways of handling this. In Go (this code) there is a default formatter.

  • @dwried
    @dwried 3 месяца назад

    This may be a bit over the top, but I like to write comments before every function describing what the function is expected to do and any assumptions being made. Just the act of writing out any assumptions will cause you to rethink the way a function is written for the better.

    • @kantancoding
      @kantancoding  3 месяца назад

      Yeah this is a good point. Many languages actually encourage this for exported functions and this type of commenting has helped me on many occasions as well. Problem is when those comments don’t get updated with the functions

    • @dwried
      @dwried 3 месяца назад

      When I first started doing it, I thought it was crazy pointless, until I realized how much stuff I assumed to be true.