10 Tips for Cleaner C++ 20 Code - David Sackstein - CppCon 2022

Sdílet
Vložit
  • čas přidán 5. 05. 2024
  • cppcon.org/
    ---
    10 Tips for Cleaner C++ 20 Code - David Sackstein - CppCon 2022
    github.com/CppCon/CppCon2022
    The objective of this session is to provide guidelines and tips that will help the audience write readable, testable and extensible code with modern C++.
    One of the greatest challenges we face, as programmers, is to make sure that our code can be understood and used by our colleagues - and not only by the compiler.
    As the complexity of C++ increases, the compiler is able to deduce and optimize more of our code, but often at the cost of making it more obscure for our colleagues.
    Code that is difficult to read is difficult to test, to maintain and to extend.
    In the session, I propose to help the audience understand the core problems that we introduce as our programming becomes more advanced, and how we can avoid them.
    The main part of the session will discuss some fairly complex and advanced C++ code which is difficult to understand. I will then demonstrate 10 easily applicable tips to make it easier to understand and to test. The benefits will be clearly evident.
    There will also be a short discussion with the audience to increase the confidence that the guidelines and the tips are readily applicable in their own diverse projects.
    ---
    David Sackstein
    David is an experienced software programmer, consultant and instructor. His passion is to write readable, extensible and testable code and to help others do the same.
    David leverages his broad background with a number of programming languages to achieve this goal in C++ too.
    He has spoken at ACCU and at CppCon on coroutines and fibers and demonstrated how these tools can help simplify complex programs in C++.
    __
    Videos Filmed & Edited by Bash Films: www.BashFilms.com
    CZcams Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
    #cppcon #programming #cppprogramming
  • Věda a technologie

Komentáře • 58

  • @lefteriseleftheriades7381

    dependency inversion is not dependency injection. dependency inversion is about having low level components (UIs, inputs, outputs, databases) depend on high level components (logic, use-cases) in terms of who is deciding on the interface (it should be the high level component that defines the interface)

  • @assonancex
    @assonancex Před 6 měsíci +1

    This was a great talk. I really liked the IoC bit as I was looking for that in the C++ world as a former Java developer.

  • @TNothingFree
    @TNothingFree Před rokem +4

    std::expected is expected in 2023, I want to know how much he giggled writing this title :)
    It's nice, we use lots of "Expected" in our code, the Try___ pattern.
    Many good aspects but for a 40 minute presentation I think it's a bit too much, could emphasize couple of them instead of trying to tell how to build an application,
    I know these aspects so it wasn't so hard for me to follow, others may have issues with it.

  • @andrez76
    @andrez76 Před rokem +2

    Lots of useful insights here. Thank you.

  • @piotrarturklos
    @piotrarturklos Před rokem +16

    I'm going to push back a little on the "primitive obsession" smell. Every clean C++ talk seems to push for introducing more and more types, but this increases mental overhead of actually implementing something. I find this kind of advice to have a very strong bias towards maintenance, but in order to maintain something, first one has to actually reach the point when someone can pay for the maintanance, and trust me, there is going to be a lot of code thrown away before that, much more than finds its way to the final product. And if I wrote a struct or whatever is recommended for every number, string etc, I would never get to the point of releasing the product in the first place. Having powerful vocabulary types like int is what enabes rapid prototyping and experimentation, even if their ranges are too wide sometimes. The extra types have their uses, but if all advice a beginner hears is focused on maintenance only, at the expense of experimentation, no new C++ solution will ever be released.

    • @rutabega306
      @rutabega306 Před rokem +1

      I feel like this talk is about what you do after the prototype has been validated.

    • @ABaumstumpf
      @ABaumstumpf Před rokem +3

      There is nothing wrong with having an "int" represent the duration in seconds - as long as that is easily made clear.
      going fro chrono::seconds is also not that bad, but a static construction-method and private constructor? Now creating an instance of movie goes through a templated function, throws and exception, catches that and converts it into an "expected".
      And you then STILL have to validate that the movie is valid.

    • @curlyfryactual
      @curlyfryactual Před rokem

      I have found that, in order to strike some sort of balance in this area, I tend to simply try my best to not force future engineers using my code into interacting directly with STL containers. Often times, I may model some of the objects that go into the containers as well.
      I have frequently made the mistake, particularly during very early development, of over-abstracting the primitives.
      With a good name, a primitive may be plenty good.
      I draw the line at STL containers somewhat arbitrarily, but mostly because the interface for primitives is very simple. Though the primitives themselves may be less confined than the data type they're representing, STL containers compound this problem by presenting an interface that is much too diverse, so you get a bundle of problems (unclear representation limitations, unclear method of interaction).

    • @jonohiggs
      @jonohiggs Před rokem

      Primative obsession tends to reduce congnative overhead since the ambiguity is reduced. For example, we deal with a number of string-typed ids that have different formats. Representing these as strings means that one id type can be passed to another and the semantic meaning is lost, id check can fail, the wrong id type can be passed in as a parameter where a different id type is required. Sure you can try to make sure you name them all well, but that is easy to miss. We have a zero-runtime typedef wrapper, that creates a strongly typed wrapper around a string, and each Id type is then a unique symbol that can't be pass where it shouldn't so we get compile time correctness. Another example would be width and height ints, with a typedef wrapper for each then you won't be able to pass them to a method the wrong way round

    • @NombreNoBailable
      @NombreNoBailable Před rokem +1

      In a prototyping situation, you may be right. But, as the code grows, fixing primitive obsession is invaluable. Having different types for different concepts make it easier to reason about the code, to have fewer errors, and to increase the abstraction of the code. Also, it's easier to place invariants where they belong. Instead of checking if a double is between 0 and 360 all over the place, or wrapping it, it is better to do it in one place, in the constructor for the Angle class, for example...

  • @llothar68
    @llothar68 Před 11 měsíci

    I'm now more following the John Ousterhouse (TCL/TK creator) way of Software Engineeing. Then from SOLID nothing is left as a guideline except some little I and D. Large closed subsystems that encapsulate a lot of code.

  • @piotrarturklos
    @piotrarturklos Před rokem +5

    Funny fact: the actual number of tips provided is 11, as shown on the last slide 53:15.

    • @sv_gravity
      @sv_gravity Před rokem +3

      LOL. C++ is so bloated that it leaks to slides as an extra tip.

    • @jan-lukas
      @jan-lukas Před rokem +2

      The 2 most common problems in programming are naming things, cache coherency, and of by one errors

  • @colinkennedy
    @colinkennedy Před rokem +7

    A good presentation up to the 37 minute mark. After that, it takes a weird turn with few gems. Up to 37 is a good presentation overall, IMO.

    • @ChrisCarlos64
      @ChrisCarlos64 Před rokem

      I stopped at 36:50 to take a break and was going to resume. Now I'm curious to see what you mean. 😆

    • @TNothingFree
      @TNothingFree Před rokem +2

      I know why it bothers C++ devs so much, it isn't the static compile way of doing things, CRTP, compositions and generics are used much more and I think for the sake of performance generics could be used there instead of virtual inheritance.
      However I also see the failure of C++ devs to understand such crucial aspects such as Interfaces, Dependency inversion and IOC containers.
      Many boost libraries are hard to test, hard to maintain and hard to compose because the overuse of generics, I've been there.
      C++ Devs are failing to see the success of other high level languages, for example in C# these aspects are built into the language and the framework,
      Such as IOC Containers are pretty basic in composing multi middleware servers, while in C++ they are very complex.

    • @sv_gravity
      @sv_gravity Před rokem +1

      No. From min 1 all examples are inedible contrived, and if you throw all >=C++17 features out of this presentation, code will be more readable, debuggable and supported.

    • @piotrarturklos
      @piotrarturklos Před rokem

      @@TNothingFree Forgive my generalization, but in niches where C++ is used I don't think you get so much situations where you need very many objects of complex interfaces and classes with state, that have so many different methods, properties and so on, where the inversion of control (IOC) container would really help you avoid some boilerplate. C++ is often used in cases where you have straightforward procedural code, in addition to a lot of memory buffers and other resources, and 1 or 2 controlling classes with some complex state, without a need to create more than 1 object of those. Complexity is limited. C++ is not used as a glue-code language as often as C# or Java. So if one has just 1 or 2 virtual methods (rather than 100) in the whole codebase, then why not use std::function or a callable template argument instead. Of course sometimes the code grows, but one needs commercial success with C++ glue code before that happens. :)

    • @ChristianBrugger
      @ChristianBrugger Před rokem

      I also found that the presentation introduced too many 'modern' concepts, without really motivating them or discussing their pros and cons. I find them actually quite hard to understand, not easy to read, presented in that way. Although the ideas itself definitely sound interesting.

  • @ChristianBrugger
    @ChristianBrugger Před rokem +4

    Would love to use modules, but it's not supported yet by any mayor compiler or build system, apart from some trivial examples.

    • @llothar68
      @llothar68 Před 11 měsíci

      Give it 5 more years. The problem is more in the mobile world where now NDK and iOS really dropped speed in adopting C++. The language wars not just brought the tower of babel down but also the world of software development.

  • @tricky778
    @tricky778 Před rokem

    That first bit of library code was so regular I wonder if it was generated by a solver from requirements. Anybody know?

  • @lefteriseleftheriades7381

    32:05 what is the point of throwing if you are then going to wrap the constructor to catch the throw and return it as exceptional? just have the factory method be the one that does the validation.

    • @rickr530
      @rickr530 Před rokem +1

      It is the responsibility of the constructor to validate. The factory method constructs and if validation fails then construction fails and the factory returns an "expected" which maintains state about the failure and is used to control execution through the chain of "and_then" calls. I think what you're proposing is to move the validation logic outside the Movie class which may break encapsulation, increases coupling, and is just a messy design.

    • @ABaumstumpf
      @ABaumstumpf Před rokem

      @@rickr530 " I think what you're proposing is to move the validation logic outside the Movie class which may break encapsulation"
      No, he clearly stated what he meant.
      As shown on the slides the Movie-class has a public static factory-method to create an Object of type movie, and a private constructor that is only called from within that factory. The private constructor-function is doing the parameter-checking and throws and error.... but why not do that error-handling right inside the factory-method? That is the only justification for that goddamn awful patter to begin with: To have one specific function that deals with validation and creation of objects. Doing the parameter-change there would prevent the useless allocation and creation of a stack-trace to begin with while otherwise behaving exactly the same.

  • @Swampdragon102
    @Swampdragon102 Před rokem +2

    This is pretty decent, but some opinions aren't explained:
    - why are comments bad in general? why prefer the additional indirection of declaring small use-one functions?
    - why is declaring too much in one file bad? why not group closely related definitions in a single file? what's the advantage pf having the extra indirection of putting related things into separate files?

    • @zhulikkulik
      @zhulikkulik Před 7 měsíci

      In general comments are just visual noise. I've seen some educational repos on GitHub where >50% of the file are comments and spaces.
      Also in stb headers comments occupy lots of space, but they usually are useful.
      Comments are totally fine, IMHO, for things like Quake's fast inverse sqrt. When there is no way to write a code that explains what it does.
      Writing small headers would be fine if we didn't need to include them. If somehow every header in the project would be automatically scanned and you could just namespace::thing and the thing knows what you want to do.
      Until then I'd rather have loooooooong headers where every related thing is declared and a corresponding looooooooong cpp file with implementation.

  • @edgeeffect
    @edgeeffect Před rokem +1

    I love the way he presents SOLID as good guidelines but we the caveat of "don't turn this into some kind of fanatical religion" (looking at you development managers)!

  • @markp8418
    @markp8418 Před rokem +1

    Great talk, one comment though is to be very careful with using the "pimpl" idiom. I am currently working on a large codebase for an embedded system which is far from clean code (most is C++03 written as if it were C. It is basically every product developed over 20 years by 200+ engineers baked into a single monolithic application. Someone in the past decided to apply "pimpl" to every class their team wrote which makes navigating the code impossible. Pimpl has its uses but IMHO it should be used sparsely, and only after carefully considering the consequences.

    • @llothar68
      @llothar68 Před 11 měsíci +2

      I'm dropping pimpl too. First it will all be non necessary once we have modules which i guess is just 5 years away. I don't know how the pattern i use now. But the real magic is following John Ousterhouse (TCL/TK maker) to use much larger modules and then set a good API fascade in front of it. It's better to have a 50,000 lines of dirty code then 1000x50 lines clean code. And the former is even easier to maintain.

  • @PaulMetalhero
    @PaulMetalhero Před rokem +11

    Why pass the file name as const string&, if you have the std::filesystem::path object? that´s not clean

    • @colinkennedy
      @colinkennedy Před rokem +6

      It's generally better to have functions with primitive input types. Faster, uses less memory, more generic (more callers can use it), etc. Return types should be rich, though
      "New" modern C++ says uses a std::string_view instead of const string& but I haven't used them much, yet.

    • @lefteriseleftheriades7381
      @lefteriseleftheriades7381 Před rokem +8

      @@colinkennedy i don't buy that, he says avoid primitive obsession a few slides later 31:30

    • @ABaumstumpf
      @ABaumstumpf Před rokem +1

      @@colinkennedy It is in no way faster to first generate a more complex object that has more information, and then extracting out a std::string with no context to its origin. It does not use any less memory, nor is it faster. Being "more generic" also directly clashes with his whole point of avoiding primitives.

    • @tricky778
      @tricky778 Před rokem

      @@ABaumstumpf depends on how much optimisation can be done with inlining. The compiler can do that faster than the programmer.

  • @rickr530
    @rickr530 Před rokem

    I don't feel like IOC containers are so powerful for the example presented. You should already be using interfaces and virtual methods along with composition instead of inheritance, in which case an IOC container is not required to substitute mock objects in tests. Yes a tiny amount of boilerplate can be eliminated with Hypodermic but you still have to code up the registration. Factory methods can be used to contain frequently used "recipes" of registrations and many of these are static anyways. I can see some utility in the container encapsulating all the instances of the related dependencies and maintaining their lifetimes uniformly, when there isn't a more natural way to manage lifetimes. I'm not sure if this is really an interesting design pattern for C++ programmers or if it is just a way to transplant a foreign idiom to C++ instead of using more native idioms.

    • @jonohiggs
      @jonohiggs Před rokem +1

      The problem with trying to introduce IoC in a talk like this is that IoC introduces a small initial bump of complexity but is then log N complexity after that, as opposed to not using it which tends to exponential or even factorial complexity while also making some thing practically impossible. Toy examples that you can present are just too small to show that different and tend to sit around the the breakeven, but in real world codebases with many developers and tens or hundreds of thousands of lines of code the difference is huge
      For unit tests I think he was off the mark saying that you need a container to resolve things, you can and should just pass in some mocked object so you can test what you are trying to test. It is much more powerful for integration and regression tests since you can swap out one or two registrations and suddenly you can test the entire app without needing to connect to a database or some remote service
      The problem with some factory methods is that they are tight coupling hiden behind a IoC facade and break the chain of dependency injection. You are out of luck if the dependency you need to swap out some dependency for your test, or because your code is running on the cloud rather than desktop, is tighly coupled behind that factory
      I would say that IoC is not only a cross-language pattern, or a general OOP pattern, but you could make an argument that functional currying and passing around a partial function is essentially the same idea as IoC

  • @shawnshaw9859
    @shawnshaw9859 Před rokem +2

    clang-tidy issues lots of false positive for c++20 though

  • @SardarNL
    @SardarNL Před rokem +1

    The example with std::expected and private constructor is unbearably bad. The point of "leave exceptions for exceptional situations" is about making erroneous states unrepresentable. Instead of validating duration and throwing if it is negative in runtime, you should instead define an abstraction of duration that makes invalid duration unrepresentable (should not compile).

    • @ChristianBrugger
      @ChristianBrugger Před rokem

      How would you do that in the example above? Where we want movies to have a duration between one and 120 seconds?

  • @chrisminnoy3637
    @chrisminnoy3637 Před rokem +1

    Pimpl idiom doesn't work if the outside class is a templated class, if the implementation needs to call back the outside class, for example to call inherited functions. Pimpl is ok, but in practice its usage is very limited in modern generic code.

    • @piotrarturklos
      @piotrarturklos Před rokem +3

      Yeah, but there's a ton of code that doesn't need to be generic.

    • @krumbergify
      @krumbergify Před rokem +2

      Pimpl is for when compilation speed and ABI stability is more important than runtime performance. It’s a tool like any other - use it where it makes sense.

    • @emislive
      @emislive Před rokem

      @@krumbergify pimpl makes the implementation details (dependencies mainly) private because they shouldn't be part of the public interface. Compilation speed and ABI are incidental. Avoiding pimpl for "runtime performance" reasons seems nebulous, and should be justified with measurements for the situation at hand.

    • @ABaumstumpf
      @ABaumstumpf Před rokem

      @@emislive "pimpl makes the implementation details (dependencies mainly) private because they shouldn't be part of the public interface."
      There are better alternatives if you just care about those thing. If you just want to hide "privat" methods then Pimpl is definitely the wrong way to go about doing so.
      PIMPL is explicitly for creating stable ABIs - removing all chances of ABI-changes due to implementation-changes (which also means it does not mesh well with templated types).

    • @krumbergify
      @krumbergify Před rokem

      @@emislive I don’t disagree, but I wrote ”is for” to explain why someone would consider pimpl. Hiding members that are already private just för the sake of it doesn’t make much difference unless you considered practical advantages like ABI stability, header firewalling and compilation speed.
      Pimpl requires more code (forwarding virtual calls and dynamic allocation) than a straight implementation so even if you don’t care about performance there should be a good reason before you consider pimpl.

  • @ABaumstumpf
    @ABaumstumpf Před rokem

    Why create a factory-method only to then throw during construction instead of validating it in the method that was specifically created for validation??? And this still means that you have to do all the error-handling on the callers side. The callers gets an "expected" and has to first validate that he got a movie.
    Aside from the obvious glaring problem of defining a maximum allowed duration (that also happens to be way to short for any sane movies) there is something even worse - this example-code does the exact OPPOSITE of what it claims to be doing: it gives a false sense of security cause "invalid" movies are still easily created - only the constructor is checking the duration (with the insane way of throwing an exception) while "Duration" is a simple public member... you can just create a Movie with a duration of 1 second and then set it to say -5 years.
    So the object is NOT always valid and if duration is something that must be "valid" then now you have to check it at every point.

  • @shakooosk
    @shakooosk Před rokem +5

    As a counter perspective to this presentation, "Where does bad code come from?" by Casey Muratori is a must watch. czcams.com/video/7YpFGkG-u1w/video.html

    • @yokozombie
      @yokozombie Před rokem +2

      interesting but incoherent

    • @dagoberttrump9290
      @dagoberttrump9290 Před 10 měsíci

      Casey is my guy, every clean coder just gets lost in concepts without doing actual work. "Work avoidance" is probably something every clean coder teaches.

    • @KyleSmithNH
      @KyleSmithNH Před 5 měsíci

      @@dagoberttrump9290 I feel there are extremes at each end. I don't want to work with either extreme, but with people both concerned about delivery time and maintainability.

  • @johanngerell
    @johanngerell Před 11 měsíci +1

    Disappointed he completely disregarded dependency injection with callables instead of interface instances

  • @blacklion79
    @blacklion79 Před rokem

    discuss expected with and_then and unwrapping expected-of-expected and don't say that word «monad». Men of steel.

    • @ABaumstumpf
      @ABaumstumpf Před rokem

      Cause some people want to actually use a language to solve a problem rather than having lengthy discussions about the concept of monads (also in most languages that use that term you also have the consequence that the code becomes an unreadable error-prone mess )

  • @dagoberttrump9290
    @dagoberttrump9290 Před 10 měsíci +3

    As an embedded developer i can't really get behind what he's preaching. In fact i would argue that your code would probably look cleaner and be more maintainable if you did the exact opposite of his suggestions. Funny that he suggests a framework to help you with dependency inversion boilerplate when you can just.. not use dependency inversion at all. And you would first have to show me a repo that benefited from any of the solid principles with notable exception of the single responsibility principle.