Upgrading GitHub from Rails 3.2 to 5.2(githubengineering.com) |
Upgrading GitHub from Rails 3.2 to 5.2(githubengineering.com) |
Also, I want to add a big proviso to the lesson "Upstream your tooling instead of rolling your own". Historically in ruby, and now even moreso in node, the ease of pushing new packages and the trendiness of the language at times has led to a lot of churn and abandoning of packages. The trick is to include stable dependencies, and that requires quite a bit of experience and tea-leaf reading to do right. Often times maintaining your own stripped down internal lib can be a performance and maintenance cost win over including a larger batteries-included lib that ends up being poorly supported over time. For example, a lot of people got burned by using the state_machine gem that at one time was very hot and actively maintained but went on to get left in an aggressive limbo (https://github.com/pluginaweek/state_machine).
Longer writeup is at https://thomasleecopeland.com/2015/08/06/running-rails-updat... - it's 3+ years old now, but, hey.
I feel like this bit needed more of an explanation about how this applied to GitHub.
If I were to write a post about working in a 10 year old Ruby codebase I'd definitely include "Kill your dependencies" as a bullet point.
Or at least your monkeypatches!
Every piece of externally-maintained code is a security risk, surely? You are implicitly trusting the maintainer of that Gem to not hide bad things in their code. And every Gem that they depend on. If the Gems are old and the maintainer is unpaid and doing other stuff, how sure can you be that they're still vetting all contributions for security? Or that they haven't handed over the maintenance to someone you no longer trust? Or that the maintainer hasn't succumbed to economic pressure and included some malicious code in their Gem?
Or do you have to manually review every single line of code in every dependency yourself? That seems like a lot of work... I would definitely prefer to write my own code for a feature than review 1000's of sloc of someone else's code to spot any problems.
I get that the core Rails codebase gets security-reviewed regularly, but does that happen for Gems? And is it methodical and thorough, or is it just "lots of eyeballs"? And if so, is there a threshold of Gem popularity below which there aren't enough eyeballs to spot problems and the Gem should be considered insecure?
And if you do spot a problem, do you report it and hope the maintainer has time to do something about it? Or do you write a PR and submit it, hoping they accept it? Doesn't that then mean you're maintaining someone else's code base? Again, I would massively prefer to write and maintain my own code than maintain someone's else code (or wait for them to fix a problem that they may no longer care about).
How do you build a secure application for something as trusted as Github while gleefully incorporating all this third-party code?
Rails is now a mature framework and part of the problem is its lack of consideration for large existing codebases running in production. While there are nice tools to help migrate (e.g. rails:update) that hit surface issues, the deep problem is that there are a lot of decisions made going from version to version that are obviously unfriendly to established projects. e.g.: https://github.com/rails/rails/issues/27231
Additionally, there are a lot of gems that are losing momentum, which are near-core to Rails. e.g.: https://github.com/thiagopradi/octopus/issues/490. This is a side effect of the above issue, where the alternatives to Rails are taking a lot of the community away to focus on newer/shiner things. Fortunately, we have companies like GitHub and Shopify that are still very much invested in the success of the ecosystem.
All that said, it's still a great framework to go from 0 to production with a new idea or project.
Other ecosystems we're entrenched in (Node for example) have their share of issues as well, but we won't go into those.
People don't upgrade their dependencies across the board, and it's a massive problem for long-term security and maintainability.
Not sure about the upgrade early. It’s a different kind of pain to be one of the first to use a new Rails version vs lagging a couple of months behind.
Looks like the first scheduled milestone was 9.5 (a year ago) and the current is set for 11.4 (next release).
It seems daft to keep seeing this lesson being learned by tech companies, and keep seeing blog posts where most of the pain would have been handled easily by just making upgrading a key feature.
Instead, tech managers and engineers seem to make the same mistakes over and over again, delaying those upgrades, until suddenly they discover it's a hard task to upgrade. I get delaying to _some_ degree, it's better to let other people figure out those sharp bits on the bleeding edge for you, but you need to set an explicit target for upgrading.
At another large tech company I worked for, it took the security team swinging the sledgehammer to get teams to upgrade from known-vulnerable versions of Ruby on Rails. When they came to do it, they discovered the changes were so extreme that the effort involved in migrating was likely more than the effort involved in a complete re-write (they did at least have pretty comprehensive tests)
This is why we call it 'tech debt'.. it is just like any other debt. You take it on because you don't have the current resources to avoid it, and you calculate that it is worth taking it on. But then, you are carrying the interest on it, and if you aren't careful it will grow to be unmanageable, and all your dev effort goes into just paying the interest without paying the principle.
I doubt anyone would enjoy that gig, but it would be a very useful person to have in almost any multi-person team.
So back in 2012 rails had a default behavior where you could mass assign values from a POST to a user and there wasn't any scrubbing of that, by default. Someone realized this was a Bad Idea and issued a pull request that would have fixed it. Instead of accepting the PR, DHH (I think it was him) said something along the lines of 'competent programmers would not leave that setting in place' and rejected the PR.
The exploit discoverer thought about this and tried it against github, which was known to run on rails and the code worked! From there he was able to manipulate the permissions on github to get access to the rails repo where he reopened and accepted his own pull request.
He was promptly banned.
Did GH just rewrite those scopes in their respective models and maintain a ton of if/else blocks for the different versions? And if so, didn’t they run into issues without the code not being DRY, e.g. someone fixes a 3.2 query, but not the corresponding 5.2 version?
I think in general, there are lots of reasons to like a language outside of its runtime performance.
I love working with Go and Rust due to their performance. Any I work every day in C#, which ends up nice and quick, too.
But I still love Ruby due to its expressiveness, and the way it works just seems to align with the way I think. But that's poetically because I used Smalltalk in the past and I like the bits of it that Ruby borrowed. :)
To answer to original question, though. I'd say it's irrational to hate languages that are slower than necessary because it's irrational to hate a programming language at all. No matter what language it is, it's just a bunch of words on a screen. Use the ones you like and don't waste any brain cycles thinking about the ones you don't.
Unless you're locked in a cube farm and forced to write Cobol at gunpoint all day. Hate might be rational then.
Is it rational to love the fastest languages? Do you rank your programming language love by an arbitrary speed index?
I’m not sure why this still surprises me. For a company the size of Github, there should most certainly be a team responsible for these type of upgrades.
And perhaps they have little to gain and possibly much to lose if they ditch it?
You didn't say much in your question, so I don't know if you feel they ought to rewrite with a popular SPA framework or use something like Elixir Phoenix, but if their Rails-based solution handily serves 30 million users, why do you feel so strongly they should move to something else?
If Github wanted to integrate a lot of real-time features, then Elixir + Phoenix can't be beat. Depending on what they replace, a 10x in performance and a fraction of the servers needed is a nice win.
I can't really take that seriously.
That's just untrue. Almost half of the Rails core team works at GitHub and Shopify which both have huge 10 years old codebases and I can tell you they take breaking changes very seriously.
To be fair - this has gotten a lot better. Upgrading 2.3 -> 3.2 was terrible. 3.2 -> 4.0 was terrible. 4.0 -> 4.1 was rough. Since then, I've found the upgrades pretty easy - to the point I ran rails 5.2.0-rc's in production for a while.
As you fairly note, a big problem is that related gems lose momentum and they don't get updated - which blocks other updates. On the flip side, they usually aren't that hard to update and submit a PR on, either.
Even not having those patches merged quickly is not so bad in ruby - it's easy to tell bundler to look at your fork of a gem on github rather than pulling the upstream.
I think the upgrade path from 2.3 being terrible is generally accepted as being true. But I don't remember any hard problems from 3.x onward.
At least for Rails itself. Gems dependencies are another problem.
Like at one project we had pains every time Rails upgraded their minor version because the previous devs thought using Squeel instead of the builtin ActiveRecord a good idea. Just for being able to write slightly "nicer" queries and now this is a major stopper for going to Rails 5.
For example most of our configuration management is setup to just pull in the latest cookbooks from upstream during tests, and as long as all integration tests across all projects succeed, they get uploaded to our chef-server.
People argued that it would be annoying because things would break all the time. And yeah, things break with updates, though the opscode community is remarkably disciplined about semver. But that's what we have tests for.
And honestly: I'd rather deal with one broken update per day than 300 broken updates once per year. One bad update usually requires some nudging and that's it. 300 bad updates at once are a fully blown nightmare and you'll need days just to figure out what is even going on.
Upgrade Early does not mean Upgrade Instantly. I would argue that "lagging a couple of months behind" is upgrading early, because these upgrade horror stories always come out of companies that are years and years behind.
Having some patience and waiting a little, combined with the discipline to not wait too long is part of a mature skillset.
* track master, and notice breakage as soon as possible (you get much smaller deltas when trying to figure out breakage, which is always a big help), and are able to fix it or report it upstream ASAP, or * hold off slightly till the new release has had more bugs found post-release.
I'm talking big packages, less-loader. Htmlwepbackplugin still requires @next for webpack4 to work right.
That said Microsoft's ASP.NET MVC would be a good contender for your call to suggest a better language.framework.
It is indeed very productive. It has a massive package ecosystem, great documentation, a very long lifespan (not as old as Rails, but probably about 10 years old now). ASP.NET MVC is all over the enterprise so it is well proven, plus StackOverflow if you want a high traffic example - there are probably many more. Where I think it beats Ruby in particular is the C# language is really excellent.
C# is my favourite language for getting things done and I've tried Ruby, Java, Python, Haskell, Basic and Javascript and gone into some depth with all of those. The reason is the excellent language features, one of the first to have async/await, good generics system, Linq is awesome, there is even some dynamic types support if you need it - which is nice for web page stuff while you are experimenting and then can 'shore it up' with a class later on.
The downside of C# I think but I need someone else to confirm is it is probably a bit confusing for a newbie, because of the vast number of features and many ways of doing things because of it's history. Not so bad if you have been doing it for years.
Another downside with ASP.NET is different ways of doing things in .NET Core so lots of relearning to do and tutorial roulette where if the tutorial is using .NET Core you may not find it easy to integrate into your classic application. Although I think RoR would suffer such upgrade issues I am sure.
IMHO there isn't much to gain with static languages when doing web development. A good framework is much more important. Static languages are awesome when coding business rules, though.
The defaults are quite terrible. For example the original developer put everything in the default.py file because there is nothing in the framework that suggests to create multiple controllers or models. The idea of auto generating and auto applying migrations is extremely dangerous IMHO. The ORM is pretty verbose and it pollutes the code with all those db(). On the other side, Django pollutes the code with all those Model.objects so it's not worse than that.
Still, Rails is terrific. If it wasn’t for Rails Java today wouldn’t be as productive either.
I don't have a hate-on for Ruby on Rails; my last job was as a Rails dev and I liked it. I still like, even though I haven't used it in a while (simply because I have no need). It also definitely has some advantages over Go.
But Go offers some great advantages: type safety; still fairly productive; boring understandable Just Works™-kind of language, and has most required components in stdlib (you need a few external dependencies, but not many, although how many depends a bit on your approach as well).
The biggest downside is that there's no standard web framework, and that a lot of Go devs seem to eschew them, too. There are some good reasons for that in some contexts (RoR is not a solution for everything either, that's why Sinatra exists), but it has the effect that a lot of organisations crank out their own internal semi-frameworks. Basically it's Greenspun's tenth rule.
Go-buffalo is probably the closest thing to Go, but I haven't had the opportunity to check it out in depth yet. There is also Revel, but that has some unfortunate design decisions IMHO, and isn't something I would recommend.
Is Go a complete drop-in replacement for all RoR use cases? No; not yet anyway. But I think it is for a lot of cases.
> ... The biggest downside is that there's no standard web framework
I hate to say it, but this why I feel justified in earning 50% higher pay than the younger devs on my team who are more passionate, more ambitious, faster, and put in more hours, yet always want to rewrite the boring web code in whatever is the new cool language of the year.
I mean if you feel it's worth it to stay late and work weekends in order to reinvent the same old boring code to deal with marshaling HTTP to types, process forms render templates and Json, etc - precisely the things that were mature and battle tested in dozens of other frameworks years ago - I guess that's fine if at least you're learning why it's such a bad idea (unless your job is to wrote such a framework instead of actual business requirements).
But your managers and especially your customers could care less that you did so.
So, spending a year and a half on a major version upgrade of your web framework is "productive" how exactly?
I mean, whatever time you think you've saved by using Rails during the initial prototyping phase (and I don't think even that's true), you'll more than pay for in maintenance costs.
Obviously, there's a break-even point there that's different for every framework and every application. But I think the ongoing popularity of web app frameworks suggests that a lot of people find the tradeoff acceptable.
Personally I'm fascinated with Clojure but it doesn't yet have the ecosystem (for me as a Clojure noob) to easily punch out product prototypes. Likewise I've messed with JS frameworks and they're fun but they don't offer me proven established patterns; too many competing and fast-moving choices. I'd rather find my market fit first then worry about scaling / paying off tech debt.
Again, what's your suggested model? To me Rails offers a semi-boring yet productive middleground between high and low ceremony. TAANSTAFL.
2) A year and a half of how much effort?
Interesting, I didn't know Ruby has been around just as long as PHP. I still would choose PHP if my opinion matters, just from my experience with the slow performance of ruby on rails when I gave it a go just a few years back.
PHP - 23 years
Ruby - 23 years
Java - 23 years
JavaScript (nodejs) - 22 years (9 years)
https://en.wikipedia.org/wiki/Dynamic_programming_language#E...
I’d suggest most frameworks are better than rails for long lifespans. They just frontloaded productivity, and it shows. The UI and functionality of github is largely the same as in 2010.
And then stuff that was sensible then probably shouldn't have changed much.
Also, you're saying that Github hasn't added any new features in the last 18 months?
Consider that 6 years, 2 months, and 20 days passed between Rails 3.2 and Rails 5.2. That's quite a bit of time for the framework to evolve. Then factor in the customizations from several non-framework dependencies and those added by GitHub.
This is an incredible achievement no matter how you slice it.
I could take a big, unmaintained 10 years old Haskell codebase and upgrade it to the newest compiler and libraries in a couple of days, at most (and it would most likely work on the first try after it compiles).
I'm curious to what you think would be a better framework for Github to have used, that would've allowed for easier, speedier point upgrades? Rails likely was a big advantage (as it usually is) in the initial stages. Are you seriously expecting it to be just as smooth when the site experiences exponential user and feature growth? That moving from Rails 3 to 5 was doable, with what sounds like a small team and no massive service disruption, seems like a very strong argument that Rails can still be effective in a company's middle-age years.
That is no easy task, for such a big application.
The upgrade to .NET Core is probably worse than a Rails upgrade though, although it's not really the same thing as .NET Framework will continue to be updated for awhile. Switching to Core is really only necessary if running on Linux servers is a big win for you.
In my experience all large or very large scale apps upgrades (and I've done quite a few) are complicated in a way or another, no matter the stack. Technical debt stacks up in subtle ways (dependencies get obsolete, a specific feature used the framework in unusual ways, stuff can be rewritten with more built-in framework features etc).
I don't see how this article would give Rails bad publicity, personally; I'll add that the advice they provide is pretty much what I would recommend for any tech stack too.
In such a language, any change on framework update would cause compiler errors if the framework's type constraints didn't match what your code expected.
As a result, upgrading in a dependently typed language is simply a matter of fixing compiler errors, and then it's upgraded.
For non-dependently-typed languages that take advantage of the type system, it's still significantly easier, though you probably will have to do a little more than just make sure it compiles.
This is not the case in my experience. I've upgraded pretty decent sized apps (hundreds of models,lines of routes, etc) and in my experience it would take a couple hours a day spread out over a few days a month and then I was done (for versions: 3-4 and 4-5, never done 3-5).
I would say most of the problem is making sure everyone on the team just keeps all functionality as-is. It can be tempting for team members to refactor as they go through but this then becomes a huge time-sync. Anyways, thats my exp on rails but I have no other frameworks to compare it to.
Has anyone migrated a massive app from some PHP Framework like Symfony or from a java framework like Play, or any framework with a large code base?
I have had to upgrade massive systems that were not done with any framework and full of one-off solutions with in-house developed libraries and it was an absolute nightmare, but I'm sure this depends on the language and team. However, in general I think an open-source library used by millions or even hundreds of people is going to have better documentation, bug coverage, support, etc. than something done in house, just IMHO.
So I guess my question would be, what does the alternative look like?
Add to that that a Ruby code base will be significantly smaller than a codebase in most statically typed languages. That means less code to maintain, and probably fewer bugs.
1. https://www.i-programmer.info/news/98-languages/11184-which-...
The "statically typed" languages that you're focusing on (I say probably because they're the ones with high bug counts in the data) are probably C and C++, which have other issues making them higher in bug count. C is hardly even typed. Both have manual memory management.
Also, there's no control for commit frequency. Some people put everything in one commit, while others commit every line change. The Rails Tutorial even recommends the latter.
Lastly, Scala and Haskell killed in this study, as far as raw numbers go. But it doesn't seem significant.
I'll stick with subjective evaluations for now. This is just too hard to measure.
Note, in particular, that there's a high confidence, true, but the claim is "picking language X reduces the chances for bugs by a tiny bit." To quote the abstract:
"It is worth noting that these modest effects arising from language de- sign are overwhelmingly dominated by the process factors such as project size, team size, and commit size. However, we hasten to caution the reader that even these modest effects might quite possibly be due to other, intangible process factors, e.g., the preference of certain personality types for functional, static and strongly typed languages."
Personally I like statically typed languages due to playing nicer with autocompletion and in-editor documentation. Every time people make claims about "upgrades being done when project compiles" I die a little inside.
A thousand times this. It's so much easier to do breaking changes and refactors in a language that's supporting you, instead of working against you.
It also doesn't help when most codebases are using ActiveRecord or something in every complex class and wind up increasing the interface width and ancestor depth of their code significantly. The point is - I think the language does a pretty good job supporting the developers, but there are a lot of bad practices that are still in use and recommended. Can't fault the language because people are writing shit code
Can you name a framework where upgrading a very large (several hundred thousand LOC or more) application across 6+ years, two major versions, and multiple minor versions is not a significant undertaking?
FWIW at my former employer we had a huge Rails monolith with something like 500K+ lines of code. On top of that, our genius architects had split it up into a very nonstandard Rails architecture.
We hired these folks (no affiliation, other than that I used to work for a company that hired them) and they did a solid job. They blogged fairly extensively about each incremental upgrade and the problems they encountered:
https://www.ombulabs.com/blog/tags/upgrades
Do you see anything there that's much more painful than a similarly ambitious upgrade in other frameworks?
Also you're measuring effort in "X number of months" but as the article states it started as a hobby side-project for a few engineers. There is no notion of how much effort it actually represented. Heck I could need 5 years to upgrade from angular 1 to angular 2 if I put in 30 secs per day...
I would actually advocate for a framework that's past its prime/hype period over any newly untested hyped framework any day.
When moving from 4 to 5, we relied on simple smoke tests and unit tests, and had no major issues or bugs. The biggest effort was to make sure all of the application and environment configurations were up to date and using all the new settings introduced etc.
My very subjective opinion is that either most of these code bases are low on quality (meaning they are harder to maintain in general), too tightly coupled with Rails itself (models stuffed full of logic, instead of using plain ruby objects for logic and keeping ActiveRecord for persistence level logic), or engineers are just too scared to make changes to the codebase - which again is perhaps a combination of bad test coverage and bad code quality.
Either way the stories of upgrading major versions being a huge undertaking always make me scratch my head and wonder what are we doing wrong if its easy for us.
And inb4 someone claims our apps are just small and simple - we run about 12 Rails applications in production in various sizes, about half of them being relatively large.
I've been using Rails since late 2005, and in my last job upgrades a few Rails apps that haven't been touched since 2008 or so.
(And time under the "took the opportunity to clean up technical debt" heading shouldn't really count.)
1. You're citing a specific anecdote (some people... java1.5) and trying to generalize. What matters is not some people, but the average case, which "some people" will not tell you.
2. "easy to upgrade" is not being argued; "easier in general" is. Just because it's easier to upgrade in a statically typed language doesn't make it easy, just easier than for a dynamically typed one.
In effect, you're saying "there are people using statically typed languages who didn't update, so it must not be easy to update".
A statement that makes a similar fallacious jump is: "There are some people who still type slowly on computers so I can't see how anyone could claim typing on computers is generally faster than typing on typewriters".
Anyway, the fact that the compiler catches more errors at compile-time means it should be obvious that it's easier to upgrade a statically typed language.
If I have a method in ruby "user.get_id" which used to return an int, but now returns a uuid in a new version of the framework, for a statically typed language my code just won't compile on the new framework until I handle that, regardless of test coverage... where-as in ruby, I'll need to have test coverage of that path or read the upgrade notes or something.
There are valid arguments to be had about dynamic vs static typing, but whether it's safer/easier to perform an upgrade of a library/framework is not an argument that dynamic typing can win easily.
If you think someone is using HN abusively, you should email us at hn@ycombinator.com so we can investigate. Attacking them in the comments is not cool, and being personally nasty is of course a bannable offense.
If you'd please review https://news.ycombinator.com/newsguidelines.html and follow the rules when posting here, we'd be grateful.
How long was AWS Lambda not able to support Python 3?
How long was GCP not able to support Java 7?
How hard is it to upgrade any core framework or language?
Not in the slightest. But security updates are part of them.
> Good projects backport security updates (for LTS versions anyway), and new versions with new features come with their own new security issues.
There's a limit to how far back patches go, even on the good projects. Rails 3.2 hasn't received a patch in over 2 and a half years, and while there is some desire to backport security fixes, you're dependent on a single individual having spare time to work on it, where more up-to-date releases receive patches far faster, and are far easier to test and integrate in to your existing platform.
I've seen teams, and heard of companies, that are still running services on top of Rails 2 and the like. Now when they look at upgrading Rails the sheer number of changes is mind-boggling, and often represents a non-starter.
I'm certainly not arguing to keep up on the bleeding edge, but making routine upgrading part of your regular workflow is most definitely an important part of remaining secure.
EDIT: Went ahead and found it. It was an issue. https://github.com/rails/rails/issues/5228
EDIT2: It looks like DHH may have even gone so far as to delete his comments in this issue. There's folks referencing him and one side of a conversation in places. Pretty funny.
https://github.com/rails/rails/commit/b83965785db1eec019edf1...
Github blog post on the incident:
https://blog.github.com/2012-03-05-responsible-disclosure-po...
But I've also found that writing new applications in Go has some good benefits over e.g. RoR. Things like processing forms and whatnot is all in the standard Go library. No need to reinvent anything.
I understand some people feel frustrated with "new tech of the yeah, yawn" (JS frameworks being the canonical example), but I don't think that standing still is a good option either. There is probably some reasonable middle ground.
The unique implementation of automatically satisfied Interfaces and first class functions (ie. easy to inject dependency) in particular makes testing very easy and designing code using these abstractions for testability also improves modularity.
For database, generally you just use the standard library to access and well tested database drivers provide the actual access mechanics. So if you want you can actually decouple tests checking the business logic side of database (eg. instantiate and hit an in-memory dB locally during testing - or even use a completely different driver if you're not using a weird "flavor" of SQL) from actual connectivity testing (where you wrap things into a transaction in a real mirror dB)
Coming from a rails background, this was the closest out of the box testing experience. Tests are generally handled within a transaction and also offer fixtures and other goodies. Definitely recommend giving it a test drive.
https://gist.github.com/kyleterry/55468cb4ff9ce2e9f0156491c4...
Works pretty well.
And like all open source, somebody has to do it.
Generally speaking at that job, though, we tended to not wean ourselves off the deprecated features - we'd use the extracted gem to keep the functionality going. Which works fine for one minor release, but doesn't work when you are 3 minor releases later.
Strong parameters is one that hurt bad. We used the workaround gem to avoid that for a long time, and it just made it more painful when we had to get rid of it.
I think generally there were a lot of related gems that were hard to get updated along the way from 3.2 to 4.0 as well. Seems like a fair amount of libraries were late to the 4.0 party, then jumped ahead to 4.1 or 4.2, and never really got ironed out well against 4.0, so you'd have goofy compatibility issues.
Squeel was a related problem that was horribly painful to remove from that stack as well, I forgot about that.
This is the inverse of survivor bias, in that you are retroactively applying "best practice" at the wrong scale. What gets you from $0 to $7B may hurt you at $7B. Heck, may hurt you way earlier than that.
However, and YMMV on what problem you want to solve, but saving $1.5M, heck lets 10X it and call it $15M, saving $15M when worth $7B isn't the problem I'd personally be concerned with.
> The project originally began with 1 full-time engineer and a small army of volunteers. We grew that team to 4 full-time engineers plus the volunteers.
They had four full-time people working on upgrading Rails at some point!
Yes, modern IDEs for dynamic languages can also do this 95% of the time via type inference. The problem is that you never know if this time it's going to be the other 5%. And dynamism tends to encourage clever hacks that make code less verbose, but also make it especially hard for any sort of automated tool to figure out - and those can lurk in corners people don't even remember are there.
- https://www.infoq.com/articles/converting-struts-2-part1
- https://www.infoq.com/articles/migrating-struts-2-part2
- https://www.infoq.com/articles/migrating-struts-2-part3
Granted, the "upgrading" situation has improved since then in the Java world, but it hasn't been always super nice in the past.
- 2 major versions and 4 years out of date.
- Upgrade started as a half-hearted "in your free time" effort with no official team, or maybe 1 dev (article says both).
- Upgrade included "cleaning up technical debt and improving the codebase", (you could define almost any work under that umbrella).
- At the end there were only 4 devs full-time on the upgrade.
I don't know of any platform that would make the above situation all that much easier...
Care to name the platforms that would be much easer to update at Github's size across two major versions?
Rails was hugely popular for years (and still is, in a lot of ways). There are countless articles about it and it's been for a ton of projects. There are a lot of internal Rails apps built on earlier versions that are owned by companies with either limited in-house development resources, or none at all; in which case, it's easy for decision-makers on the business side to push off updates (assuming they even know about them). "That doesn't sound like a big deal, we'll just do it for the next one." A bit of time passes, and then you're two major releases behind and you're looking at a serious effort. Or maybe it's developers who make that decision for what are likely valid reasons in the short-term. Upgrades across multiple major releases aren't exactly uncommon because of that, and there are a lot of articles detailing them, blog posts discussing or complaining about them, questions on SO, etc. as a result.
For the most part, though, I don't that that Ruby or RoR is uniquely difficult to upgrade compared to other frameworks. I've handled upgrades across versions that have gone ridiculously smoothly, and some not so smoothly.
The “problem” with ruby is that you basically don’t know if code is valid unless you execute it, whereas a statically typed language will enforce a lot of things during compilation and simply not allow the program to compile, if API is used in the wrong way, this could be calling private functions, referencing undefined symbols (simple typos), wrong number of arguments, passing a string where integer is expected, etc.
Additionally access control is limited in Ruby, which makes it difficult to release a library and have the language enforce that people do not rely on things which are implementation details subject to change.
For example, it's possible to fetch a class's entire interface before and after a refactor and validate it is the same. It's possible to dynamically wrap every method you're refactoring track them and type check them. And if you extend that idea, now you can output this data to files and perform static analysis. Sure, it all relies on you having some safe execution context to get this information but Ruby probably has the best testing tools of any language and many projects have great test coverage
For library owners, I agree with you - there's no hope. But for application developers maintaining their monoliths with nothing depending on them there's a lot you can do to ensure safe refactoring
The migration is a pain, but just upgrading from MVC 4 to 5 wasn't painful.
I am sure 2 to 5 would have been a nightmare, especially if you were using the deprecated Microsoft JavaScript libraries, and needed to replace them with their jQuery alternatives.
Doesn't .NET Core's runtime have significant performance benefits over the .NET Framework?
I agree .NET MVC is as close as you get to something like Rails with regards to productivity in a statically typed, enterprisy language.
But using .NET/C# at Github would still have ended up with a significantly larger codebase -- which means more code to maintain, and therefore also in all likelyhood more bugs.
> As a result, upgrading in a dependently typed
> language is simply a matter of fixing compiler
> errors, and then it's upgraded.
This is very naive, and is probably hilarious to a lot of people who've been through upgrade hell in a dependently typed language. A few (and then some) major reasons:1. It's really the subtle runtime behavior changes that bite you. The ones that a compiler doesn't help you with. (This is not a Rails-specific thing; ask Unity or OpenGL etc. devs)
2. A lot of the pain of upgrading a project (Rails or otherwise) is dependency hell. You upgrade the framework, but some of your dependencies haven't been updated and don't work with the newer framework version. This is true whether it's a dynamic or compiled app.
3. It's certainly true that in a strongly typed language, these sorts of trivial problems would be caught at compile time and that's certainly an advantage. However it's not exactly rocket science to catch these in a Rails app. Assuming your test suite is anywhere near adequate, it's going to spit out a comprehensive list of these problems just like a compiler would, albiet not as instantly.
3a. Rails is pretty good about documenting these breaking identifier changes between versions. They don't exactly sneak up on you, unless you get drunk one night and decide to upgrade your enterprise Rails app without looking at the release notes.
3c. Rails is also quite conscientious at loudly announcing to you, via log messages, when you use functionality that is deprecated and targeted for removal. Assuming you're not willfully ignoring these (ie, drunken late-night upgrade bender?) they don't typically catch one by surprise.
I agree that dependency hell and getting the versions to line up right is equally hard.
However, I disagree that 3 is a good argument.
> these sorts of trivial problems would be caught at compile time and that's certainly an advantage. However it's not exactly rocket science to catch these in a Rails app
Actually, it kinda is. To model the constraints you create in a dependently typed language, you have to create a set of tests and checks in your dynamically typed language which are basically the equivilant of a full dependent-type-system. Creating an ad-hoc human-enforced type-system and test suite is incredibly hard and I can't think of a single large project written in a dynamically typed language that adequately does this.
Regardless of how good the documentation and testing and warnings in rails are, it's not a replacement for a full type system, and the only way to get those benefits is to implement a poor ad-hoc type system in your methods and tests.
Yeah, big upgrades are always hard. Good type systems make them less scary and have less chance of breaking stuff, which honestly is the most important thing... But after you've finished getting through the stuff that all languages share (hardware sucks, dependencies suck, etc), a dependently typed language will be a matter of fixing compiler errors, not watching percentages of 500s in prod and crossing your fingers.
> But after you've finished getting through the stuff
> that all languages share (hardware sucks, dependencies
> suck, etc), a dependently typed language will be a
> matter of fixing compiler errors, not watching
> percentages of 500s in prod and crossing your fingers.
I am speaking from very direct experience here.I was involved in a Rails 3.x --> Rails 5.x upgrade of one of the larger Rails monoliths in the world and the trivial sorts of things a compiler can catch were... well, also pretty trivial in our upgrade path. Just not quite as trivial as they'd be with a compiled language (nobody's denying they have the edge here)
> To model the constraints you create in a dependently typed
> language, you have to create a set of tests and checks in
> your dynamically typed language which are basically the
> equivilant of a full dependent-type-system.
No, that's not how you do it.You don't "model the constraints" explicitly. You write integration tests, same as you'd do in any sort of language. If MethodA from Class1 is passing the wrong stuff to Class 2 from MethodB, your integration tests will fail. At least, assuming you've got proper coverage.
But even in a staticly typed, compiled language you have to write that test anyway, right? Because you need make sure that code path actually works anyway and MethodA is getting the correct response from MethodB there.
There are definitely advantages to strongly typed, compiled languages! To be honest, after a few years in Ruby land, I'm ready to GTFO and go back to something a little more static. But Ruby's not the nightmare you describe it as.
Python can use a framework like Django to reach the web more effectively.
Compared to..
A language like PHP in the beginning was more directly coupled to the web (for better or worse).
Having a test execute every line in your application doesn't mean your application is _covered_ or _tested_.
You say Scala and Haskell killed it in the study, and you are right, they were the third and second best language respectively with regards to low rates of bugs. Perhaps you also happened to notice (but failed to mention) what language did best of all: Clojure, a dynamically typed language.
One person can keep a rough mental model of how the project fits together in their head, which lets them have good intuition about contracts between different subsystems.
When you have 5 developers on one project, that intuition quickly breaks due to developers mental models being slightly different. That's where having a type-system really helps.
I'd argue that the you lucked out in that the stack was able to be developed by one person, and that's what enabled it to be dynamically typed with relatively little loss.
It's once you have 3+ coders working on the same code-base that static types really start helping.
I consider myself working alone to be more than one person when I come back to something I haven’t touched in months. For all practical purposes, I feel like I’m reading someone else’s code.
A statically typed language makes it much easier to make changes in that situation without worrying about unforeseen breakages.
Again, even if "rapid prototyping" was a thing in Rails (i.e. rapid as opposed to what?) you start paying back the price tenfold once your project becomes moderately complex, test coverage or not.
So if your question is specifically "What's a good stack for a blog I'll never touch again or maintain with X and Y integration, tomorrow", then ok, cool, Rails.
> literally any modern web framework in a static language
I was part of a SaaS startup using Java Spring and the verbosity and front-loaded design costs slowed our iteration enough that we couldn't respond to market input and stalled.
My next SaaS engagement was with a language more maligned than Ruby - Perl - and while the codebase was mildly fugly we could move quickly and I tell you what the founders made serious bank.
> you start paying back the price tenfold once your project becomes moderately complex
This is a problem you want to have because you have skin in the game. Better to start paying back your debt than to never kick off. Back to you - you didn't address my question - if you were to launch a product idea, what static stack would you pick? Elm is the only choice that comes to mind, and that's pretty niche. Ask yourself why. (I'm not dissing elm, it looks lovely)
There's no definitive answer here. Rich Hickey makes some excellent talking points about static vs dynamic. I'm not saying you're categorically wrong, but you haven't presented any science to support your dismissive tone. TAANSTAFL. Pick the right tool for the job. I wouldn't use Rails to build whatsapp, but for many midsized SaaS products Rails is a cromulent choice.
You may accuse me of shifting the goalposts, but do notice I said modern above. I'm not up-to-date with Java anymore, but iirc Play [0] would be worth checking out.
> This is a problem you want to have because you have skin in the game.
I'm saying that this is a problem you don't have to have at all.
> Back to you - you didn't address my question - if you were to launch a product idea, what static stack would you pick?
In the context of SPAs, I'd choose some of the Purescript React libraries and some generic Haskell REST/Websocket server, like Warp/Servant (this is a stack I've used in production).
For a standard web app, I'd go with Yesod [0], which is a somewhat Rails-like framework, but it fully leverages the advantages of static types, i.e. it turns things like dead links, trying to inject user input into a DB query without escaping it first, invalid queries (i.e. querying a person by product id), and many more into compile-time type errors.
> I'm not saying you're categorically wrong, but you haven't presented any science to support your dismissive tone.
Still, to claim that the context of the current thread at least doesn't suggest that Rails is a maintenance nightmare would be disingenuous at best.
> Pick the right tool for the job.
That's what we're discussing here, right? I can't see when Rails would ever be the "right tool" for anything (except for the one use case I alluded to above) but that's obviously subjective, rendering that phrase utterly meaningless.
There is nothing out there that has anything close to the productivity of rails. Not that there can't be; it's just not a mindset/approach the industry has embraced.
Personally, I'm a fan of very strong, static type systems, so I would prefer to annotate all the things, but I understand other people have different views.
Coverage tools could only measure quality of a test suite if you're assuming that either the code is perfect or that the existing tests cover (logically) everything about what they test. Without either of those guarantees, it doesn't tell you anything very meaningful, as you discovered.
If you really think that the metric is meaningless and useless for deriving confidence, then you are necessarily asserting that code bases with 100% coverage have indistinguishable bug yields compared to those with 50%, 5%, or even 0% coverage. A claim like this is too extraordinary to be believed without considerable evidence.
I could agree that a high coverage score is a prerequisite for having confidence that your test suite is comprehensive (in general), but that's such a low bar, it's like saying a full bath is a prerequisite for a nice house, just knowing that shouldn't do much to convince you it's a mansion.
Obviously this isn't a tenable position in every circumstance, but I think it should be the default. Particularly in a world where the vast majority of security fixes go without an announcement or CVE.
I prefer the middle ground of pinning all dependencies, and using an external service to warn if any dependency has a known CVE.
Most updates aren't breaking. The overwhelming majority of updates that are breaking are trivially discovered and fixed with one or two tweaks. Almost all the rest can be fixed with a single search/replace.
Being eight minor versions (or two major versions) behind and having to find and fix all of these at once is when people land themselves into trouble.
If you update regularly and have decent tests, it’s easy to find and isolate the problem. And if it’s more than you can fix that day, pin it for now and try again later.
I'll check out Warp and Yesod, although tbh the next stack I'd like to try is Clojurescript/Spec/Figwheel. Spec driven design seems to offer some of the benefits of static typing with the flexibility of dynamism.
One of the most cited qualities of languages like Haskell are their "refactorability" - i.e. one can routinely rip out the guts of a 100KLOC codebase and replace fundamental data structures and functionality in order to implement new features in a couple of hours, no problem whatsoever.
In Rails, I'd do everything in my power to not touch any "important" code if I can get away with it, because no matter how good the test suite, it's still a test suite. It shows the existence of bugs, it doesn't prove their absence. Many people claim that's not a problem in practice, but I personally tend to avoid more fundamental code changes in dynamic codebases precisely because of that fact.
To drive that point home, I'd trust a Haskell codebase without a test suite more than a Rails codebase with an average to good test suite.
Also, it seems to me that "moving fast" is directly at odds with the topic at hand, upgrading Rails. If Rails allowed one to move fast, then surely upgrading Rails shouldn't take a year and a half, even in a big codebase?
> Spec driven design seems to offer some of the benefits of static typing with the flexibility of dynamism.
Definitely, though it's not a replacement for types. Also, the probably more important thing here is that Clojure is functional, making a whole class of bugs stemming from mutability and OOP impossible.
If you're planning to go down the Clojure/spec route, you might want to check out Ghostwheel [0], a lightweight DSL which seems to be gaining a lot of traction lately.
function foo(a, b) {
if (a != 3) {
return a + b
} else {
return "hello"
}
}
console.log(foo(2, 5)); //prints 7
console.log(foo(3, 5)); //prints "hello"
You can compile it and run it on the typescript playground: https://www.typescriptlang.org/play/This is not like C#, where would be required to specify that every type is "dynamic". Here, you can optionally choose to specify that the types are "Any", but it's still gradual typing.
EDIT: you said "implicit opt-out"... which sounds like a synonym for "opt-in". I don't think "implicit opt-out" is a term.
[1]: https://www.typescriptlang.org/docs/handbook/compiler-option...
function foo() {
return 1;
}
let x = foo().substring(1, 2);It still compiles the code to JavaScript even with the error, and you can still run it, which makes this more of a warning than anything else.
That warning based on type inference seems like a positive feature... because there's no situation in which that code is right. You can add `: any` to the function declaration and it will stop complaining, but I wouldn't contend that the language is not dynamic because it encourages you not to make a mistake.