However I've found that as apps grow bigger and accumulate more complex business logic and data flows Rails codebases become increasingly difficult to manage.
However I've found that as apps grow bigger and accumulate more complex business logic and data flows Rails codebases become increasingly difficult to manage.
How about adapters?
There's some great pieces of literature out there about advanced ruby / rails - well worth the read!
The problem comes when that codebase starts to mature: you look up one day and realize that it's no longer just you working in the codebase. The lack of language-level documentation makes it difficult to onboard new members to the team. A new developer is inspecting a method they're tasked with refactoring. It accepts an argument named "input". What the hell is an input? Is it a hash? Is it a class instance? Both? Something else entirely? Idiomatic Ruby/Rails makes it quite hard to intuit expected inputs/outputs. (Ironically the community also vocally disavows the use of comments, which would ameliorate that problem somewhat.)
Meanwhile, "Undefined method `blah` on nil:NilClass" errors start to litter your logs to the point that developers become numb to them. Invariant violations that should have been caught upstream, at their point of inception, sneak all over your runtime and you waste valuable employee hours trying to identify their true source.
And Ruby's poor story around async i/o ties your hands as your traffic patterns begin to require a more sophisticated request processing pattern than "receive request, block thread, return response." Phrases like "worker saturation" and "let's make it an async job" start to dominate your engineering retrospectives.
These are just a few of the sharp edges that come from trying to scale Ruby. You may point to solutions to them, but they don't compare in elegance to working in a language that obviate those problems in the first place. And while it's very easy to start a project in Ruby and gain a quick initial burst of momentum, it's commensurately difficult to switch to a more mature language+framework when you realize that your engineering org is suffering from Ruby/Rails shortcomings.
This is not a blanket indictment of Ruby/Rails -- just an acknowledgement that different languages and frameworks are better suited to certain types of problems. In my opinion, Ruby/Rails are very well suited for small projects with few maintainers, where the total surface area of the domain model is pretty small. Or quick MVP's where you have to deliver next week or die. But the dynamic duo are not well suited to the kinds of problems that start to emerge as those MVP's scale up in complexity/LOC/number of maintainers. It's a bit of a catch 22.
People will point to the big companies running Ruby/Rails. But from having worked at one of them (Shopify), I can say that we were spending an inordinate amount of time trying to plaster over their shortcomings; I'd imagine this is also true of the other big companies that run on the same stack.
I have not found these issues occurring to anywhere the same extent in languages with stronger correctness guarantees.
This is more of a general criticism of dynamic typing than anything specific about Ruby, but If it's unclear from the code why is it so hard to put a binding.pry in there and inspect the object if you need to? You'd usually wanna put a breakpoint in there anyway when refactoring to see what the hell is going on. And on a normal codebase there will usually be an accompanying test.
I'm not 100% denying that in Java you'll have a type like FactoryBeanXmlConfig which you can go ahead and read the documentation to (which is often insanely verbose due to everything being a type in Java. So lots of reading).
To each his own I guess; I think we all get used to whatever stack we use and become very productive if we stick to it. I acknowledge I would've been productive in Java eventually to the same degree as I am with Ruby, I just wouldn't have enjoyed it as much.
The Shopify example is a bit extreme: you are talking about one of the biggest monoliths in the world; not strictly for Ruby, in any language. So yes you're gonna have to build some tools to accomodate that and yes, probably Java would have worked out better at that size. But how many companies become Shopify - 0.1%? It's either a "scrappy MVC" or Shopify? I disagree: Up to that size of millions of LOC and thousands of full time devs there's a whole spectrum of companies size where Ruby/Rails works out well. I can't see why a 50-100 man team can't work with Ruby successfully; if the monolith becomes a pain you break it into a few smaller monoliths/citadel/microservice. This essentially allows you to scale to any size, but I think the monolith will take you far; look at Gitlab. I'd say around 100 devs work on it full time (my guesstimate, could be wrong) and their feature development speed is brilliant imo. There are so many successful Ruby/Rails companies at those sizes it would feel silly to start naming them.
This is a lot slower and more tedious than just inspecting the documentation for a statically typed parameter and the cost of doing it over and over and over again adds up. Also there's no guarantee that whatever type you observe in your particular binding.pry experiment is the only one the function receives.
Of course people continue to build large & successful apps in Ruby. I'm just saying at a certain point it gets harder than it would be in other languages.
I honestly don't see many methods where it expects a user but it can also receive a shipment or a billingInfo where tons of callers just pass all sorts of stuff to it as they feel like, and me as a dev have to keep wondering what the param is. That's not common at all because most Ruby devs just don't operate that way (Why would they? That's pretty insane). Yes it's easier to shoot yourself in the foot in Ruby but that doesn't mean we all design crap code.
Static type people say your dynamic code is always littered with dozens of if param.type == 'user' else if param.type == 'baboon' etc etc. I rarely stumble into this in real life. I'd like to see some real examples (there's enough python/js/ruby open source code for us to make an intelligent discussion)
The issues I describe with Rails are issues I’ve seen across many different apps and teams.
As your team grows it becomes harder and harder to prevent people from shooting themselves (and the rest of the team) in the foot in such a way, doubly so when you're working in a language that more easily permits it. To your point, if you're working in a smaller team with collaborators that you trust not to make these mistakes, that's a different situation.
The inscrutable-method-input scenario that haunts me the most is the case of "options hashes" in Ruby/Rails. Someone writes a method that accepts a single hash of possible options keys, instead of an N-arity method that accepts those scalar values directly. When someone needs to modify the behaviour of that method, the short-term path of least resistance is to just throw another key onto the blob. In the most notorious example I can think of, one of our applications had an options hash with 30 (not an exaggeration) possible keys, after several years of developers deeming that the easiest way to implement whatever feature they were tasked with implementing. It was a total nightmare trying to debug situations where Key A | Key B conflicted in subtle ways with Key C & Key D.
Of course there's nothing preventing that sort of scenario from emerging in a statically typed codebase, but I find there's more friction around doing so. Especially if you start out with a rigid interface (with non-nullable keys) that all your callsites must conform to. If someone tries to add a new key to the argument interface, those callsites will need to be updated to match.
I actually agree with this, but there's also a bit more bloat and less flexibility. Also, I like the fact that Ruby/Rails is powerful and flexible and deserves trained hands. I kidna hate the Go philosophy of "code written by a junior and code written by a senior looks about the same". That's not for me. I wanna enjoy the progress I make in the language.
I'll admit I've never worked on a team over 100, but I have worked on older code bases.
When we found bugs, we wrote new tests to cover the edge case.
Also if someone had "input" as a variable name for a public method, and that made it into main, that's a failing of the PR process.
Most of the complaints here could be replicated in almost any language. In a c++ project I worked on in the 2000's I had a dev create a function void rct(int data). I (as lead) made him rename both the function and the param.