Closed tomdalling closed 4 years ago
Nick: this section didn't provide enough context ion the perils at large scale. You can't build flexibility into everything, need limitations. Good example: recently Uber has been promoting microservices, but now say "reasonable sized services". They realised microservices make a significant amount of extra work for each team.
Adam: I find the deeper i get into the book, the more it annoys me. It's too prescriptive. No nuance, "just do it". If I read this earlier in my career, I might have followed the advice too rigidly.
Lachlan: They say "wrap an API around it," so now you have to do versioning, documentation, just to pass a global variable around.
Elle: I think it's useful to wrap external libraries. It's easier to mock. If the interface changes, it doesn't break all your code, just the wrapper. ActiveJob is an example. Previously, you would pick your job gem, and couple your codebase to the gem. Now active job has all the same methods, and you can choose different backends.
Antoine: The tone of the book is a bit prescriptive. "This is how we do things". It felt a bit weird, because things aren't so simple. Inheritance is stated as very bad, with no nuance. Maybe they are trying to make you think twice before using the "bad" things -- use it more consciously.
Tom: I think they contradict themselves quite a lot, so they must be presenting all these ideas like a toolbox of things to choose, and it's up to you to work out when it is or isn't appropriate to use them.
Adam: It can be antagonistic. The OO section says you use inheritance because "you don't like typing". That's not why I use it. Their points are good, but I don't inherit from Rails base classes to avoid typing.
Nick: This is the first chapter I've notices them saying "pragmatic". My gripe is that it's their experience, not necessarily pragmatism. It's centered around the specific languages their dealing with. In newer languages which are type safe, some things in this chapter are less relevant. They've taken a narrow view that programming is dynamic languages, procedural languages, without compile time checks. Any more gripes while we're at it?
Lachlan: I noticed that they referred to "a Pragmatic Programmer", capitalised, like it's a formal thing. I haven't read the old version, and I'm a bit shocked people are raving about the book, when it's effectively a marketing term. I agree with most of the principles, with some minor differences, but I don't want to self identify as that term. Am I supposed to put that on my resume? In job ads?
Adam: Pragmatic Certified (tm)
Lachlan: Put it on your Twitter bio.
Nick: After reversibility they looked at coupling, talking about OO, procedural, functional, and encapsulation. This topic flowed through he rest of the chapter. It revolves around separation of concerns -- providing a level of separation between your logic, and the part that triggers it. I felt like this section didn't need to be a separate section.
Lachlan: Is Law of Demeter part of decoupling?
Elle, Nick: Yes.
Lachlan: "Tell don't ask" was interesting, but they give some examples that are unclear.
Elle: I would have structured that method or class very differently.
Lachlan: They're like, "do this, do this, do this", then they ignore their own rules. "You might be thinking tell don't ask would mean we do this, but it's not a law, so we're not going to do it." Their example wasn't a good example of when to use or not use it.
Adam: That's probably more important than following the pattern.
Elle: I think they're giving new developers rules they should follow, then in a situation they do need to break the rules, they need a good reason. It's not really a "law", it's poorly named, But it has good reasons behind it. And if you follow it in general, it's good.
I never thought about inheritance and decoupling they specific way they talk about it. Junior developers tend to do everything as inheritance, or ActiveRecord models, and they don't know better. Teaching them object composition is already a good step to decouple the codebase.
Nick: I am interested how the language changed from the previous version of the book.
Richard: The book used to be more dogmatic. They realised it was followed too slavishly, so they pulled back a bit.
Nick: These days, a lot of these topics would be discussed as "separation of concerns" and how you structure your code to be testable. I don't feel like I'm seeing those terms. I'm curious if they're trying to retain terms from the last book instead of using more commonplace terms.
Richard: Yes I think they are.
Nick: They talk about chaining methods as a bad thing. It didn't fit with mobile app dev I'm used to, because it's a compiled language. Each chained method has a contractual return value in Swift/Kotlin. They're type safe, so you can rely on the API. If a library does change its API, then you can pin the version.
Richard: They talk about the observer pattern being sometimes a good thing. They couch the language saying "it can get out of hand", but haven't warned enough that things get very messy very quickly. I've fallen into this trap in large apps. You can't know what happens when the observers get triggered. Has anyone had an experience with it, where they were able to keep track of it all?
Antoine: Whether it's observers or events, when they're linked to data, it gets messy quickly. They seem very easy to reason about, because they're so verbose, but they always get gnarly for me. They're easy until you don't understand all the implicit stuff happening. I always get bitten by ActiveRecord callbacks in Rails. When I started, they seemed to make so much sense. Now I try to hunt them down and remove them. Coupling to data might make it even worse. In React, I've found that the more private and contained the data is, the less messy it is.
Elle: They started with state machines, which I used to love, but haven't used in years. They're really pushing for an elixir way of thinking, as in immutable, functional, data, instead of OO. Immutability is one of the reasons I don't lean towards state machines so much now.
Antoine: I know FSMs were popular in Ruby, but eventually everyone dropped them. Was it because it becomes hard to follow?
Everyone: Yes.
Adam: I had a quick question, not from the book. Ruby lets you get away with basically anything, but would other people append items to a const array?
Lachlan: Why would you?
Adam: In the observer pattern section, they made a constant for callbacks and appended to it. I wouldn't do that.
Lachlan: I guess it depends.
Nick: Their example is very basic. The object which is being observed shouldn't care about the object observing it, so I would have a local global to store the callbacks if I were writing that.
Adam: Constants say "this won't change" and other kinds of variables would fit the use case better.
If I didn't understand state machines, I still don't think I would after reading this section. I had to download the code to my computer just to understand it.
Lachlan: Me too. It's a lot of code all at once. Reading on the kindle isn't doing it for me.
Richard: I agree they're not easy to understand. But after reading that section, I did write a state machine. I was parsing something, and it got complicated. I realised by the end of the day that a state machine isn't the clearest way to do this, so I removed it.
Lachlan: What did you use instead?
Richard: I just removed all the parts that weren't necessary.
Lachlan: Has anyone written a pub/sub anything?
Nick: I don't like pub/sub. Anything, anywhere can subscribe. It's very very difficult to track down all those locations, especially if you have to remove a publisher. I don't think that's addressed very well in the chapter.
They talk about having to go through code to change every instance when they talk about global data, and I think pub/sub has very much the same issue, but even worse. Even in compiled languages, the type system can't know the relationships between publishers and subscribers, so you need to do a text search to find all the references.
Lachlan: Agreed. I wrote a pub/sub thing once for Node. Twitter released an ill-fated library for JavaScript. Anybody could subscribe, and components would update when events fire. But once the apps grew to a certain size we couldn't tell which components relied on which events. And when you're evolving a pattern of data flow through a system, you have to keep changing stuff all the time. It was brutal. It made merges and rebases super difficult, which is why we gave it up.
Richard: At Shopify we did have a partial solution. Firstly, everything that could be published has a schema, and changes were backwards compatible. We made everyone use the same pub/sub library, and the library would send record all subscribers in a big database, so we knew who all the subscribers were. It was still messy, but it helped.
Nick: I can think of one example where I used pub/sub and it was the right approach. It was when we had event driven behaviour in an app, driven by the server. The app would subscribe via a socket and the server would send down events. That worked well because the server didn't need to know who the subscribers were. They were two separate applications. I don't like it for a single application, but it can work well for multiple applications which are connected. So you could probably use it for microservices, but maybe those microservices should have been a single service too.
Nick: I have a fair bit of RxJava experience. I like reactive programming.
Adam: My experience is mostly RxJS in Angular, in mobile apps. Unfortunately I've been burnt, and consider it to be overused. Everything is an observable instead of a promise, so I keep converting observables into promises. It has a lot of overhead, and I can't async/await them. But the concept is amazing. There has been one or two situations where I did need to stream data and in those situations it was valuable, but it's overused.
Nick: I'm not familiar with RxJS, but "single" is what you're looking for, instead of observable.
Adam: I'm not sure why Angular uses observable everywhere. And since it's done at the library level, I can't do much about it.
Nick: Googles push for the observer pattern for live data, in Android. So rather than having Rx streams, they provided a data object which has subscribers/observers on it. Their approach now is to have view models observe the live data, and the view reacts to the model changing. It works well in a mobile app context.
Antoine: Have any of you used an event streaming architecture on the server side?
Richard: It seems to be the most extreme version of this. Your database is a series of events.
Lachlan: A new client wants a bunch of ledger software, so we're like "hmmmmm". Insert meme of guy with butterfly saying "is this event sourcing?"
Adam: Like my inspiration with functional programming, I feel like the concept is too big, so I take bits that make sense for my smaller applications.
Richard: What are the good bits?
Adam: Just the concept of immutable events. I just wanted some way to store events so I can see how I got to my current position.
Antoine: I saw some event sourcing talks that promised you could revert state to any point in the past. Then I saw some "event sourcing in practice" talks that said it doesn't happen.
Nick: Of these four: FSMs, pub/sub, observers, Rx, I felt like they were separated for no real purpose and you'll use a combination of those in a large application.
Nick: I thought they missed the opportunity to tie it back to previous topics. E.g. Rx streams are very much about transforming data.
I thought it was kind of unnecessary. We do a lot of this in code already.
Tom: I think it's a bit of a trap to be thinking about "I'm going to make these classes" and "I'm going to use these design patterns". The most important thing is the data -- how it gets from the DB to the screen and all the transformations in between -- and code is just the glue that makes it happen. So it's a good reminder to think about the data before the code.
Elle: When they talk about the pipeline operator in elixir they didn't actually mention guards or pattern matching which solves the "what if the results have an error" scenario.
Nick: I'm not familiar with Elixir, but I have this problem in Swift. We use different terms to mean the same thing.
Nick: They talked about inheritance always being bad, but then talk about inheriting multiple smaller mixins instead of one big base class.
Adam: I agreed with most of what's in this chapter. They did explain how inheriting from libraries can be problematic. My gripe is that the choice of language could put off developers, calling them lazy for "not wanting to type".
Nick: It comes back to how polymorphism should be done -- the classic "car inherits from vehicle" scenario. The concept was to reduce the amount of code you write, which they cover, but the problem is how often frequently developers are using it. It's important to understand the balance. I used inheritance recently, where I used an abstract base class to define the interface for subclasses. There were one or two methods in the base class but all the rest was abstract. I don't think I would use inheritance for anything else now.
Adam: I do wish Ruby had better support for abstract base classes or interfaces. I've used them a bit with sorbet now. The book could have also explained plain old objects better. Sometimes I use mixins, but most of the time I'm using composition.
Tom: I think they presented mixins and composition as equals, when in reality it should be more like 90% composition and 10% mixins.
Nick: I'm surprised this topic is so late in the book.
Nick: The idea of an external service to provide configuration for an app is maybe overkill. It's valuable to have external configuration, but always use them in moderation. It allows you to do something stupid to an application without having it properly tested.
https://pragprog.com/book/tpp20/the-pragmatic-programmer-20th-anniversary-edition
Aiming to cover:
Chapter 5 MC: Nick Notes: @tomdalling
See you 12 pm Tuesday, April 14th @ https://whereby.com/blackmill
Ping gday@blackmill.co if you want a calendar invite and access to the low-volume Slack beforehand.