Give Expressions a Name | Clean Code

แชร์
ฝัง
  • เผยแพร่เมื่อ 6 ต.ค. 2024
  • Become a patron and download the source code: / zoranhorvat
    A prevalent issue faced by developers is the use of complex expressions embedded directly within various structures, making it challenging to discern their purpose or intent at a glance.
    This video delves into a systematic approach to address this concern: extracting these convoluted expressions into dedicated methods. By transitioning to this design, the primary code structure remains clean and comprehensible, with method calls succinctly conveying their intent. Furthermore, this segregation allows for better unit testing, modularization, and potential reuse.
    Watch this coding demonstration to grasp a deeper understanding of this pivotal refinement in coding practices, which advocates for a clearer, more maintainable codebase.
    Thank you so much for watching! Please like, comment & share this video as it helps me a ton!! Don't forget to subscribe to my channel for more amazing videos and make sure to hit the bell icon to never miss any updates.🔥❤️
    ✅🔔 Become a patron ► / zoranhorvat
    ✅🔔 Subscribe ► / @zoran-horvat
    ⭐ Learn more from video courses:
    Beginning Object-oriented Programming with C# ► codinghelmet.c...
    ⭐ Collections and Generics in C# ► codinghelmet.c...
    ⭐ Making Your C# Code More Object-oriented ► codinghelmet.c...
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⭐ CONNECT WITH ME 📱👨
    🌐Become a patron ► / zoranhorvat
    🌐Buy me a Coffee ► ko-fi.com/zora...
    🗳 Pluralsight Courses ► codinghelmet.c...
    📸 Udemy Courses ► codinghelmet.c...
    📸 Join me on Twitter ► / zoranh75
    🌐 Read my Articles ► codinghelmet.c...
    📸 Join me on LinkedIn ► / zoran-horvat
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    👨 About Me 👨
    Hi, I’m Zoran, I have more than 20 years of experience as a software developer, architect, team lead, and more. I have been programming in C# since its inception in the early 2000s. Since 2017 I have started publishing professional video courses at Pluralsight and Udemy and by this point, there are over 100 hours of the highest-quality videos you can watch on those platforms. On my TH-cam channel, you can find shorter video forms focused on clarifying practical issues in coding, design, and architecture of .NET applications.❤️
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⚡️RIGHT NOTICE:
    The Copyright Laws of the United States recognize a “fair use” of copyrighted content. Section 107 of the U.S. Copyright Act states: “Notwithstanding the provisions of sections 106 and 106A, the fair use of a copyrighted work, including such use by reproduction in copies or phono records or by any other means specified by that section, for purposes such as criticism, comment, news reporting, teaching (including multiple copies for classroom use), scholarship, or research, is not an infringement of copyright." This video and our youtube channel, in general, may contain certain copyrighted works that were not specifically authorized to be used by the copyright holder(s), but which we believe in good faith are protected by federal law and the Fair use doctrine for one or more of the reasons noted above.
    ⭐For copyright or any inquiries, please contact us at zh@codinghelmet.com
    #csharp #dotnet #cleancode
  • วิทยาศาสตร์และเทคโนโลยี

ความคิดเห็น • 79

  • @zoran-horvat
    @zoran-horvat  ปีที่แล้ว +1

    Enroll video course *Beginning Object-Oriented Programming* with C# ► codinghelmet.com/go/beginning-oop-with-csharp
    Download the source code: www.patreon.com/ZoranHorvat
    Learn from related videos:
    Avoid Returning Null From Methods - There Is a Better Way To Write Them! ► th-cam.com/video/HRLdcMil7Ec/w-d-xo.html
    Clean Code Tip: Remove Messy Constructor Calls ► th-cam.com/video/6g4ggpOxxCc/w-d-xo.html
    Use the Decorator Pattern To Reduce Code Duplication in Complex Models ► th-cam.com/video/7N0LpGAI5T4/w-d-xo.html

  • @Pookzob
    @Pookzob ปีที่แล้ว +20

    Im not fond at all of nested/chained ternary operators, but this format actually makes them a-lot more readable and revealing of intention, especially combined with util functions. It's (sadly) the first time I've seen them presented in this way.
    This format, in my eyes, borrows from functional programming (piping) and is a great display of what can happen if you join the two paradigms together instead of seeing them as a dichotomy or two separate dogmas to be enforced.
    Thank you! Great tip as usual!

    • @dracuul78
      @dracuul78 ปีที่แล้ว +2

      I thought exactly the same, nested ternary expressions can be very confusing (eg. see Sonar RSPEC-3358).... But if you format them like this, it reads just like a switch expression, with all the cases listed in order... I even like how the colons align so nicely...

    • @adrianvargo9816
      @adrianvargo9816 ปีที่แล้ว +2

      You should look at the c# 9 pattern matching. I believe it is much more readable then nesting like this

    • @chrisnuk
      @chrisnuk หลายเดือนก่อน +1

      His code was the first time I saw it, too. I think the reason you like it so much is that it also has the nested logic on the else and a result less cognitive load.

  • @chrisnuk
    @chrisnuk หลายเดือนก่อน +1

    I agree it's better. There are two lousy reasons I sometimes don't do it and one understandable one:
    1. Refactorings like this can only really be done when you finish writing the code and often under time pressures if the code works you want to move on.
    2. The simpler a bit of code, the more others are compelled to change it.
    3. Naming things can be hard.
    On the last point, I find AI a real help with its suggestions. On the other two, I recommend having children they give you perspective...

  • @mikec64
    @mikec64 ปีที่แล้ว +1

    How have I gotten this far without anyone teaching this technique? It's very helpful and your examples drive home the point. Thank you, I'm a subscriber starting… now!

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      Thanks! Now the next step is to see the shortcomings of all this!

  • @tommysmith5479
    @tommysmith5479 ปีที่แล้ว +3

    Along wth chaining (which you did a video on recently), this truly transforms ugly code into beutiful and readable code. Bravo!

  • @codingbloke
    @codingbloke ปีที่แล้ว +3

    Nice one! I would agree with other commenters regarding the choice of some names, I would use `ThisNoLessThan` and `ThisCurrencySameAs`, that would make, IMO, the main expressions even more fluent.

    • @necrokora7688
      @necrokora7688 ปีที่แล้ว +1

      A file scoped extension class would be my goto since method prefixed with this are the bane of my existence.
      Something along the lines of this.IsSameCurrencyAs(other).
      Otherwise, I would use a static method like "static bool IsSameCurrency(Money first, Money second)"

  • @rustamhajiyev
    @rustamhajiyev ปีที่แล้ว +8

    Thanks for sharing :) I quite often use this technique, as you pointed it makes code much more readable. But I have a question, why not to use a Result class instead of throwing exceptions? In my opinion, Exceptions are expensive and produce side effects. I typically use exceptions to indicate truly unexpected situations. Could you please explain your choice? Maybe it's even a topic for a new video :)) Thanks again 👍😊

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +9

      The reason is that the majority of viewers wouldn't understand it, simply put.

  • @TazG2000
    @TazG2000 ปีที่แล้ว +3

    Type doesn't matter for the throw expression itself, so you could just write the expressions like `throw CurrencyMismatch("add")`, where CurrencyMismatch return type is Exception - no generics needed.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      It's not the throw expression that fails to compile, but the calling site, where the method is called as part of a pattern matching expression. All hands of the expression must be assignable to the same type, and so the utility method must have that return type.

    • @TazG2000
      @TazG2000 ปีที่แล้ว +6

      @@zoran-horvat What I mean is the caller can use a throw expression as one of the hands. An expression such as this will compile:
      condition ? x
      : condition2 ? y
      : throw CurrencyMismatch("add")
      where the exception method is defined like:
      Exception CurrencyMismatch(string operation) => new InvalidOperationException(...)

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      @@TazG2000 Oh, that is an interesting twist. I will give it some thinking.

  • @davideaton6876
    @davideaton6876 ปีที่แล้ว +1

    Beautiful work, many thanks for it!

  • @rodrigoramirez3870
    @rodrigoramirez3870 ปีที่แล้ว +1

    Great content Zoran, keep it going!

  • @MuhammadSaleem-df6ub
    @MuhammadSaleem-df6ub ปีที่แล้ว +1

    Thank you sir.

  • @markuszeller_official
    @markuszeller_official 2 หลายเดือนก่อน

    In PHP I would use a custom CurrencyMismatchException type and accept a operation as Enum. But I see the point, you want exression named methods.

  • @georgehelyar
    @georgehelyar ปีที่แล้ว +2

    Giving things names is great advice, but those nested ternary operations are the real problem.
    It's not hard to read because any individual operation is hard to read, it's hard to read because there's a lot of branching on a single logical line.
    Spliting it out into named variables and if statements is a much better fix in this particular case.
    You can still have named methods where it is useful on top.
    You could also put each ternary in it's own method.
    Another thing you can do to greatly improve the readability of ternary operators is to put the ? and : at the start of new lines, with some indentation. This exposes the branching, and when you have a lot of branching it makes the code smell more obvious.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      Ternary operators in the demo are not nested - they are chained. This particular format is following the common formatting of pattern matching expressions, where each pattern and its resulting expression are entirely written on one line. If switch expression could be used in this example it would look exactly the same.

  • @zeldaire7484
    @zeldaire7484 ปีที่แล้ว +1

    Another great video as usual! I teach web development and tell my students all the time to make their code readable like this. It's so much more legible, especially for me who has to grade it ☠️ I've slowly been passing along some of the concepts I've gleaned from your channel, so thank you for all the work you do

  • @denysgranevych8279
    @denysgranevych8279 ปีที่แล้ว +3

    Really good idea, but `CurrencyAs` is a terrible name.
    In C#, the prefix 'As' is usually used to convert from one type to another.
    Why don't you call it `IsTheSameCurrency` for example?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +4

      Because of fluency. CurrencyAs is not the same as AsCurrency, the latter being a known naming scheme in C#. The word As alone is not prohibited by any naming scheme, I think.
      I would suggest CurrencyAsIn(other) instead.

    • @EricKing
      @EricKing ปีที่แล้ว +1

      I agree with the commenter here... I would have no idea what "CurrencyAs" or "CurrencyAsIn" means. The "As" would make me think that some kind of conversion is happening, but upon inspection I would find that it's actually a sameness check. I would give the expression some name that includes "Same" or "SameAs".

  • @johnhershberg5915
    @johnhershberg5915 ปีที่แล้ว +4

    I feel like this whole pattern is inferior to Discriminated Unions. There are some smells here, like how you're making a function that returns Money even though it really just throws. Also the problem with throws is the consumer has to know ahead of time which throws can potentially happen down the chain. This makes it impossible to exhaustively deal with the problems.

    • @nickbarton3191
      @nickbarton3191 ปีที่แล้ว

      To support chaining, it must return Money object, but perhaps TryAdd, TrySubtract which returns NoMoney would short calling chains.
      Ought to check for overflow strictly.

    • @evancombs5159
      @evancombs5159 8 หลายเดือนก่อน

      Yes, union types are my top want from future C# additions, unfortunately I feel like they may have fallen out of favor by the team who seems more focused on improvements for AOT.

  • @DongoBongKong
    @DongoBongKong ปีที่แล้ว +1

    Good naming renders code comments obsolete!

  • @etshbadr
    @etshbadr ปีที่แล้ว +3

    yea bury those implementation details as deep as you can

  • @bearforce0ne
    @bearforce0ne 7 หลายเดือนก่อน

    Another solution for the FailWithCurrencyMismatch return type problem would be to have the method return the exception instead of throwing it, and to write the throw before FailWithCurrencyMismatch is called

    • @zoran-horvat
      @zoran-horvat  7 หลายเดือนก่อน +1

      Yes, that is another way to approach the problem, though we are both doing nothing but fight against the limitations impressed by the language syntax.

    • @bearforce0ne
      @bearforce0ne 7 หลายเดือนก่อน

      @@zoran-horvat Very true :)

  • @andreibicu5592
    @andreibicu5592 ปีที่แล้ว +1

    Is there a reason you didn't separate each ternary value on a new indented line ?
    Like:
    Condition
    ? Value-For-True
    : Value-For-False;
    If find this easier to read than the way you indented the ternary conditions on your example.
    Thanks!

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      This formatting is more suitable for pattern-matching expressions, where each pattern-expression pair is entirely located on one line.
      You would have the same organization if switch expression was used.

  • @kyriacoslouca2799
    @kyriacoslouca2799 ปีที่แล้ว +1

    How would you approach async expressions which return a Task instead of a value?

    • @TazG2000
      @TazG2000 ปีที่แล้ว +3

      If all of your return expressions are tasks, then you can still use the same approach, like:
      Task OperateAsync() => condition ? GetValueAsync() : GetOtherValueAsync();
      If you have a mix of async and non-async expressions, then there are two approaches. You can write an async method and use await in inline expressions:
      async Task OperateAsync() => condition ? (await GetValueAsync()) : GetOtherValue();
      Or, return tasks directly and create tasks for the non-async expressions with Task.FromResult:
      Task OperateAsync() => condition ? GetValueAsync() : Task.FromResult(GetOtherValue());

    • @kyriacoslouca2799
      @kyriacoslouca2799 ปีที่แล้ว

      @@TazG2000 thanks for the answer. Do you think is good practice to use task.FromResult?

    • @TazG2000
      @TazG2000 ปีที่แล้ว +1

      @@kyriacoslouca2799In this case I would probably decide based on the most common type of expression. If I have several tasks and one value, Task.FromResult would make it more consistent. If there are mostly values and one async expression, it would be more consistent to return values and await the async expression.
      In general, I think Task.FromResult is mostly used out of necessity, when you aren't doing async work but still require a Task. For example, if you are overriding a method from a base class or interface that is normally async, but your class isn't async, the simplest way to implement the method would be to return Task.FromResult with your value.

  • @nickbarton3191
    @nickbarton3191 ปีที่แล้ว +1

    As you said, those functions throwing exceptions smells a bit. I didn't find it so awful before refactoring. Maybe in a more complex case but that's hard to demonstrate in a short video. I do like the chaining though, that you should keep somehow.
    BTW one of my pet hates is unnamed regular expressions, they're hideous anyway. Is there a more readable alternate?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      You can now use the source generator for regular expressions. It will both name the expression and generate its fast variant for the run time:
      learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-source-generators

    • @nickbarton3191
      @nickbarton3191 ปีที่แล้ว

      @@zoran-horvat That's better, thanks. Just grepped and have dozens I can convert.

  • @pavel3596
    @pavel3596 ปีที่แล้ว +1

    Even though I like your videos, I had an easier time understanding the expressions than your names. CurrencyAs or CompareNoLessThan rings no bells for me. I would have to check the source underneath.

  • @superpcstation
    @superpcstation ปีที่แล้ว +1

    Hi Zoran, i was recently going through you "Making your C# code more object oriented" course on Pluralsight. The premise of the course is very interesting to me i.e. people are using object oriented languages to write procedural code. But when i see all the these nested ternary operators, it seems like the same problem (writing procedural code using object oriented language) with just a more concise syntax. Am i missing something here? do you have any thoughts on this?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      These are actually chained ternary operators, not nested. They are used in a pattern matching expression the same way as we would format a switch expression.
      The need for pattern matching expressions is coming from functional programming. It is a natural technique of implementing immutable types where any behavior is naturally an expression.

    • @evancombs5159
      @evancombs5159 8 หลายเดือนก่อน

      Most of Zoran's videos have a good point and lesson to learn, but he does have a vice for short code. My advice is to try and figure out how to apply what he is teaching without going to such an extreme for shortness.

  • @andreasgkizis2135
    @andreasgkizis2135 ปีที่แล้ว

    Hello!
    Very nice video and ideas to help us make a more readable code
    I am fairly new to this and i would like to ask you, is there any advantages to using => instead of the {} to define the body of a method?
    Thank again, cheers

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      Expression-bodied methods are shorter but, much more importantly, they are forcing the expression-centric mindset. The entire software design becomes different once you start thinking about operations as mappings of an input object and varying parameters into an output object. The greatest, tectonic change caused by the use of expression-bodied methods is not visible on the surface.

    • @andreasgkizis2135
      @andreasgkizis2135 ปีที่แล้ว

      @@zoran-horvat I do understand the concept of absracting to private methods and how that helps . my question was directed spesifically to the "=>" used after the methods signature instead of the classic(?) {}
      your version
      public MyMethod() => // the code of the method
      the one i have seen
      public MyMEthod() {
      // the code of the method
      }
      So i was wondering if this is just preference or if there is anything to be gained when using your approach.
      Thanks for answering =]

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      @@andreasgkizis2135 There is no special gain, i.e. the expression-bodied method will compile the same as a single-line block method which only consists of a return instruction. The difference is philosophical. It changes the programmer's perspective, moving focus away from statements and onto mappings.

  • @Cooper076
    @Cooper076 ปีที่แล้ว

    The Book Store application which you are building to explain design patterns is part of any course? It will be interesting if this is complete course with design patterns and OOP.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      It is not part of any course right now, but I plan to include it in future courses.

    • @Cooper076
      @Cooper076 ปีที่แล้ว +1

      @@zoran-horvat great! Waiting for a design pattern course with application code.

  • @justgame5508
    @justgame5508 ปีที่แล้ว

    How would you approach expressions and lambdas that are long and unreadable being used with LINQ of EF Core. I have some pretty complex queries and I’m not sure whether to extract them out into extension methods or some other approach

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +4

      Custom extension methods are a valid approach to substituting LINQ operators with business-oriented equivalents.
      The best and the worst thing in LINQ is that it is general. The best because it is then applied easily with any problem domain. The worst because the resulting code is mixing domain logic with low-level language details.

    • @justgame5508
      @justgame5508 ปีที่แล้ว +1

      @@zoran-horvat Okay thanks for the great content!

  • @adambickford8720
    @adambickford8720 ปีที่แล้ว

    I'm sure i'm in the minority but i don't love over abstraction. Its obnoxious when every line is some level of indirection and i'm left wading through what's effectively a poorly thought-out DSL. At some point you're approaching stuff like comments that are //increment `i` by one. For me a big win of lambdas is it reduces the boilerplate noise enough it doesn't need to be abstracted to be comprehended.
    Real talk: i'll avoid it just so i don't have to sit through the bike shedding in the PR; even the custodian walking by has suggestions on "better" names. Don't believe me? Read the comments on this video!

  • @baranacikgoz
    @baranacikgoz ปีที่แล้ว +1

    I love your videos, but this video is a design mess. Thr Result monad is faaar faar much better than this approach

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      The Result monad drives 90% of the viewers away from the video, for the mere fact that they don't understand it. But it would probably amuse you to learn that I plan a separate video on the Result monad precisely on this portion of code.

    • @bighat6501
      @bighat6501 ปีที่แล้ว +1

      @@zoran-horvatAdd me to the wait list for this one 👍 thanks

  • @maurosampietro9900
    @maurosampietro9900 ปีที่แล้ว

    He really thinks his code is readable

  • @Naton
    @Naton ปีที่แล้ว

    horrible advice. Clean code is harder to read. I trust comments more than function names. Please use monad instead.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      Do you trust comments that are out of sync with code more than the code or less than the code?

    • @Naton
      @Naton ปีที่แล้ว

      @@zoran-horvatit's mostly because devs are bad at naming functions and i often find an Update function within a Read function. There's a saying "not telling is lying".

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      @@Naton What makes bad method-naming devs good at writing comments?

    • @Naton
      @Naton ปีที่แล้ว

      @@zoran-horvatyour point? all i say is i prefer comments over nested functions. I often i find myself at a fork deep within the rabbit hole. my point is this "clean code" nesting is mentally draining. dont be so butthurt.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      @@Naton Back to square one. Do you also prefer comments that are out of sync with code over nested function calls?
      Don't get me wrong, it is you who tried to supplement one common technique with another that was removed decades ago simply because it was causing misinterpretation and bugs. Why dig it from its grave now?
      BTW, what makes you read into the methods called from inside the current method? It is precisely the observation that programmers *don't* do that the primary reason that gave popularity to pulling out utility methods.

  • @ticTHEhero
    @ticTHEhero ปีที่แล้ว

    You should consider acting career with that deep charismatic voice

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      One day, they will ask me to take the clothes off. You know how it goes nowadays. I'd rather stick to coding.

  • @viktorgesiarz
    @viktorgesiarz ปีที่แล้ว

    I really like this approach, but is there a reason why you keep the condition and the "Fail..." so far apart? Like the reason "FailWithCurrencyMismatch" is called, is because the conditional sub-expression "CurrencyAs" failed, which is on a separate line. Also the actual subtraction expression is buried right in the middle of the whole expression.
    IMHO this makes it harder to read than if you applied a "Fail-Fast" approach.
    Like the substract method I would write as:
    other.IsZero ? this
    : !CurrencyAs(other) ? FailWithCurrencyMismatch("subtract")
    : !ComparesNoLessThan(other) ? FailWithInsufficientFunds()
    : this with {Amount = this.Amount - other.Amount};
    or to increase readability event further, negate the conditional sub-expressions, so "IsCurrencyNotTheSameAs" instead of "!CurrencyAs" and "ComparesLessThan" instead of "!ComparesNoLessThan".
    This way the reason for the Fail, and the call to the fail are on the same line.
    So, basically I would write it as:
    other.IsZero ? this
    : IsCurrencyNotTheSameAs(other) ? FailWithCurrencyMismatch("subtract")
    : ComparesLessThan(other) ? FailWithInsufficientFunds()
    : this with {Amount = this.Amount - other.Amount};