4 ms·
Ooof, Uncle Bob. It's important to understand that he's a consultant with a heavy emphasis on pedagogy, and he has been since around 1990. As far as I can tel
by sevensor 2y ago
Ooof, Uncle Bob. It's important to understand that he's a consultant with a heavy emphasis on pedagogy, and he has been since around 1990. As far as I can tell from his blog and public internet presence, he's never maintained a piece of software for the long term. His incentives are all about getting people to pay for training, and to that end he's succeeded beautifully. Object oriented programming caused massive confusion about writing maintainable software, and it's both easier and more profitable to tell people that they need to OOP better than to use more appropriate paradigms in the design of their code.
- mrkeen 2y ago> it's both easier and more profitable to tell people that they need to OOP better than to use more appropriate paradigms in the design of their code. I don't think this is quite on target. Sure, he needs to go where the people are in order to communicate to the most people, but that doesn't mean he doesn't speak out against fads if it's appropriate. This talk was really formative for me: https://www.youtube.com/watch?v=Nsjsiz2A9mg https://www.youtube.com/watch?v=Nsjsiz2A9mg If I remember correctly, he rails against just sticking the database in the middle of your system and calling that your architecture. If I'm remembering https://www.youtube.com/watch?v=QHnLmvDxGTY https://www.youtube.com/watch?v=QHnLmvDxGTY correctly, he says OOP is no big deal, it's just encapsulation-polymorphism-inheritance, and then goes on to say things like C had better encapsulation than OO languages (you can keep your .c file to yourself, and just give someone the .h to program against.). And c-style bytes with stdin/stdout was designed with perfect forward-compatibility - programs like your terminal don't need to be recompiled even though they interact with your program (which was written after). And for the last 5+ years he's been pushing FP pretty hard, especially closure. I don't see him as pushing OO on people at the expense of anything else.
- sevensor 2y agoI've made it a bit of a project lately to understand Uncle Bob and his effect on our profession, so I took your recommendation seriously and watched the first video you linked. I'll start with the bit I agree with. Uncle Bob is right that you want to be able to test your core logic without depending on a database and a webserver. I also found his statement at the beginning that your folder organization should reflect the purpose of the application appealing, but I'm not sure it's actionable. He also spends some time kicking RUP while it's down, before turning around and saying "actually, those guys had a point." A strange rhetorical tactic, and probably some ancient score-settling was involved, but it's a fair point. I can't fault him for saying "Ruby is about to get really big" in 2013, but he timed that trend just about exactly wrong. Where I disagree with Uncle Bob is in his proposed solution. Define a plugin interface for every side-effecting part of your code, and pass in safe plugins when you want to test it. He's explicit about his preferences: the core of your application should be pure objects, exposing only methods and no data. And since you want those to be encapsulated, you can't let them leak out, you have to pass the actions in, so that you can do them in your core. This is a bonkers, topsy-turvy world where you can't just have a pure data processing routine. If you could, you would just write it, test it, use it in your side-effecting procedures, and go about your day. No plugin architecture required. Infinitely more testable, because your data processing routines don't have side effects. Uncle Bob's design forces you to deal with what happens when your side-effects go wrong somewhere in your core logic. This kind of thing is the reason we have a "law of leaky abstractions." Side effects can't be encapsulated. We try anyway, and they leak across the barrier. I also went and looked at some of his blog posts about functional programming, and as far as I can tell, he's mostly repackaged his traditional advice for Clojure. I think a much better source there would be Eric Normand.
- mrkeen 2y ago> Define a plugin interface for every side-effecting part of your code, and pass in safe plugins when you want to test it. I think we're on the same page so far > the core of your application should be pure objects, exposing only methods and no data. I'll put my thumb on the scales here and say that classes should be all-behaviour or all-data. In that context, he's talking about structuring the behaviour classes in your application. Take the following (I don't recommend it because it violates DIP, but that's not the point I want to make yet): UserService -> UserStore { User getUser(uid) {..} } Since getUser is exposed as a method, you could swap out UserStore for PostgresUserDatabase or anything else that exposes getUser(). But if you reached directly into UserStore's HashMap (exposed data), then you're stuck with HashMap storage and can't replace it with a database. As for User itself (all-data) I don't think it's a big deal if you read its data directly (e.g. user.name.firstName). Some folks insist on a getter there, but I say if you're doing the architecture well, it should be no problem to change it to a getter later on if that's what you actually need. I think that is all that is meant by "exposing only methods and no data". > you have to pass the actions in I think I get where you're going with this, but first > This is a bonkers, topsy-turvy world Topsy-turvy synonyms: reversed ... upside-down ... inverted? Yes, inverted! I, for one, vote that we rename DIP to BTTDWP, the "Bonkers topsy-turvy dependency world principle." I suspect that Uncle Bob used a particular convention for his arrows and didn't define them in this talk, which is why you find it bonkers. The arrows are dependencies in the "who-knows-about-whom" sense, not the "who-invokes-whom" sense. The call-chain still goes in the expected ('forward') direction, but it's the knows-about arrows that are flipped in BTTDWP. Here's an (incorrect) 3-part system, prefixed with (W)eb, (B)usiness and (D)atabase, for a typical getUser() flow. WUserController -> BUserService -> DUserStore There's no risk here of BUserService accidentally coupling to Web stuff (unless BUserService decides to return a WUser instead of a BUser.) But there is every risk that BUserService will treat DUserStore like a concrete database. BUserService will start doing things like opening Hikari connection pools and beginning/ending SQL transactions, and then you'll never be able to test it in isolation. All BTTDWP means here is to flip the 'knows-about' arrows so that the concrete, outside bits (which Uncle Bob keeps calling plugins) know about the B classes, instead of the other way around: WUserController -> BUserService <- DUserStore The call chain still flows in the original direction! This is done with interfaces in mainstream OO: WUserController -> BUserService -> IUserStore <- DUserStore I think that's how I'd push back on "you have to pass the actions in". > If you could, you would just write {pure data processing routine}, test it, use it in your side-effecting procedures, and go about your day. Yes this is right. Your side-effecting procedures are typically the Database or other plugins, and your data processing is typically your business logic. Calling into the business logic from the plugin means that BTTDWP is already satisfied and you don't have to reverse any arrows. > And since you want those to be encapsulated, you can't let them leak out, you have to pass the actions in, so that you can do them in your core. I don't think leakage is a problem in the above W->B<-D setup. If you're standing in W, and you call B for something that's stored in D, the usual W->B->(I)D call-chain happens, but because B doesn't know about D (hidden behind I), it can't possibly leak D-specific stuff out to W. It's completely up to B as to what it wants to return/leak back to W. FWIW, I believe Uncle Bob slightly misspoke from 52:20: "I don't want your application to have outgoing dependencies" <- Correct "Your application is composed of business rules, and those business rules are the family jewels." <- Correct "You keep those jewels in a nice little bag and you don't let people look in." <- Misleading "You keep them isolated; you don't let the frameworks touch them." <- Possibly misleading / needs context "Looking in" is fine. He's just spent 30 minutes saying that all the (know-about) arrows point in, right!?!? And you certainly don't do the reverse: the family jewels aren't allowed to "look out" at the database or the web. I think he clarified it slightly in the next sentence, though: "you don't let the frameworks touch them". I think he meant that you do not put framework code into your business logic classes - you shouldn't see any Spring or Postgres or Hikari in the imports sections of your PayrollCalculator ("PayrollCalculator shouldn't wear the Spring wedding ring"). But Spring can ask your PayrollCalculator for the numbers it needs to return back out to the web client, which I would consider somewhat "looking in". It would be out of character for me to not plug Haskell at this point - so I will say just this: That pesky IO monad thing that everyone keeps complaining about? That's just another incarnation of this, but it's less forgiving to newcomers because it results in a compile-time error (rather than untestable code that gets monkey-patched with shudder Mockito.) IO can call pure, but pure can't call IO. That's all it is! It just happens to be more important in a lazy setting where the compiler will lift and lower expressions in and out of each other and only evaluate expressions insofar as a caller is demanding results.
- brodo 2y agoExactly. I like to get advice from people like Rob Pike, Matteo Collina, and John Carmack. People who build and maintain big, real, and important software projects.