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)
@piotrarturklos Жыл бұрын
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 Жыл бұрын
I feel like this talk is about what you do after the prototype has been validated.
@ABaumstumpf Жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
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
@MikelLuri Жыл бұрын
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...
@assonancex Жыл бұрын
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 Жыл бұрын
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.
@piotrarturklos Жыл бұрын
Funny fact: the actual number of tips provided is 11, as shown on the last slide 53:15.
@sv_gravity Жыл бұрын
LOL. C++ is so bloated that it leaks to slides as an extra tip.
@jan-lukas Жыл бұрын
The 2 most common problems in programming are naming things, cache coherency, and of by one errors
@colinkennedy Жыл бұрын
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 Жыл бұрын
I stopped at 36:50 to take a break and was going to resume. Now I'm curious to see what you mean. 😆
@TNothingFree Жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
@@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 Жыл бұрын
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.
@Swampdragon102 Жыл бұрын
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 Жыл бұрын
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.
@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 Жыл бұрын
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 Жыл бұрын
@@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.
@ChristianBrugger Жыл бұрын
Would love to use modules, but it's not supported yet by any mayor compiler or build system, apart from some trivial examples.
@llothar68 Жыл бұрын
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.
@llothar68 Жыл бұрын
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.
@andrez76 Жыл бұрын
Lots of useful insights here. Thank you.
@PaulMetalhero Жыл бұрын
Why pass the file name as const string&, if you have the std::filesystem::path object? that´s not clean
@colinkennedy Жыл бұрын
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 Жыл бұрын
@@colinkennedy i don't buy that, he says avoid primitive obsession a few slides later 31:30
@ABaumstumpf Жыл бұрын
@@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 Жыл бұрын
@@ABaumstumpf depends on how much optimisation can be done with inlining. The compiler can do that faster than the programmer.
@markp8418 Жыл бұрын
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 Жыл бұрын
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.
@edgeeffect Жыл бұрын
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)!
@rickr530 Жыл бұрын
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 Жыл бұрын
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
@chrisminnoy3637 Жыл бұрын
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 Жыл бұрын
Yeah, but there's a ton of code that doesn't need to be generic.
@krumbergify Жыл бұрын
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 Жыл бұрын
@@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 Жыл бұрын
@@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 Жыл бұрын
@@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.
@shakooosk Жыл бұрын
As a counter perspective to this presentation, "Where does bad code come from?" by Casey Muratori is a must watch. kzbin.info/www/bejne/bYrTd3qhfJKoZ9k
@yokozombie Жыл бұрын
interesting but incoherent
@dagoberttrump9290 Жыл бұрын
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 Жыл бұрын
@@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.
@SardarNL Жыл бұрын
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 Жыл бұрын
How would you do that in the example above? Where we want movies to have a duration between one and 120 seconds?
@tricky778 Жыл бұрын
That first bit of library code was so regular I wonder if it was generated by a solver from requirements. Anybody know?
@shawnshaw9859 Жыл бұрын
clang-tidy issues lots of false positive for c++20 though
@ABaumstumpf Жыл бұрын
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.
@johanngerell Жыл бұрын
Disappointed he completely disregarded dependency injection with callables instead of interface instances
@dagoberttrump9290 Жыл бұрын
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.
@blacklion79 Жыл бұрын
discuss expected with and_then and unwrapping expected-of-expected and don't say that word «monad». Men of steel.
@ABaumstumpf Жыл бұрын
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 )