Also, you're saying that Github hasn't added any new features in the last 18 months?
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.
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).
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.
You can find other platforms we're updating is a lot easier.
That is the issue.
- 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.
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.
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.
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.
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?
That is no easy task, for such a big application.
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.
- 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.
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.
> 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.
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.
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.
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.
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.
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.
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.
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.
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.
[1]: https://www.typescriptlang.org/docs/handbook/compiler-option...
Having a test execute every line in your application doesn't mean your application is _covered_ or _tested_.
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.
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
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
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?