I’m about to start in on a new project, and I need to make a decision about what language to get started in. I’ve spent nearly all my career coding on the Microsoft stack, which lately has meant C# and ASP.NET MVC (and several fruitless years on Silverlight, sigh). However, I’ve been spending some time playing with Ruby on Rails lately, to see if I should make the switch. These are my initial thoughts after some investigation – but although I feel reasonably confident about my assessment of the C# side of things, I’m less confident in my ability to assess the strengths and weaknesses of Ruby. So I would be very interested in hearing other folks’ perspectives.
To start with, there are some areas where the RoR ecosystem is hands-down better than anything Microsoft has put together.
- Getting started. You can get started faster in Rails, largely due to the Rails scaffolding infrastructure. Nothing MS has really compares to the elegance of the various “rails generate” commands, especially when you consider how they can be extended, i.e., through the Rails Composer. It’s much easier to add flexible functionality to the Rails scaffolding infrastructure than it is to add the equivalent functionality to Visual Studio, and so not surprisingly, more folks have done it.
- Extensibility. Third-party libraries integrate much deeper into Rails than into ASP.NET MVC. The structure of Ruby, its dynamic and open type system, and the dominance of the “convention over configuration” mindset, make a number of tasks much easier, such as adding an authentication or authorization system to an existing website. It's possible to write C# code in such a way that you can make it highly modular, and it's more common to do that these days than it used to be. (The newish ASP.NET MVC system is modular in precisely that way, so that at runtime or for testing purposes, you can replace many of the individual modules.) But you have to work to make it that way: not too hard, but it is work, and there's always the chance the author of the third-party library will forget to make some critical piece modular. And because you have to work with named interfaces, even a universal convention doesn’t give you the same results in C# that it does in Ruby. For instance, in ActiveRecord, the convention is that every model has an “Id” property, so you can assume that you can check the Id property of any model. You could only do this in C# if every model from every library inherited from a System.Web.IModel interface – which of course isn’t going to happen. This means that it’s a great deal more work to interact with other libraries: you can’t just assume that there’s going to be a User model with an Id property that you can pull up and talk to. Whereas you can do this sort of thing almost free with Ruby. It's sort of like what you would get if every class in every library in C# were partial and injectable.
Another way to put it is to say that Ruby (and other dynamic languages) are willing to be wrong. This reminds me of the genius of Tim Berners-Lee when he first created HTML. Other folks had taken runs at it before he did, but they were all hampered by the fact that they had a compilation step, which would ensure that you never linked to a page that didn’t exist. TBL made the wild assumption that you didn’t need to do that: it was OK to be wrong, to link to a page that didn’t exist, and you’d recover from it. This removed the need for a controlling central authority, and thus the web was born. You have the same kind of wild and woolly but incredibly valuable ecosystem growing up around Ruby.
- Gems. Even though C# is still a more popular language than Ruby by most measures, the Ruby community has succeeded in providing an enormous and high quality suite of tools and libraries: larger and generally higher quality than what the C# community has so far succeeded in providing. This may be changing: MS is releasing almost all their new frameworks as open source, NuGet means that it's becoming much easier to discover and keep up with open source libraries, and GitHub has produced a system for managing contributions that even C# guys want to use. But it’s going to take a while for C# coders (and their managers) to come around to the idea that they should be spending some portion of each day contributing to public projects up on GitHub.
- Unit tests. I also really appreciate the ease with which it's possible to write unit tests in Ruby. The flow you can get into with Guard watching which files have changed, running the appropriate tests, and then telling you which things you've just broken, is really cool. And the DSLs for writing mocks in Ruby are much more intuitive and simpler than writing mocks in C#.
There are also a number of ways where the MS ecosystem is pretty closely matched to Ruby. NuGet works somewhat differently from bundler: bundler is more flexible, while NuGet is more foolproof. But having used both, they both seem to get the job done. The same is true of the general MVC architecture, or the ORM's they each use, or the routing specifications, or the asset pipelines, or the rollout and hosting systems. They're all fairly comparable. I'd guess that AWS and Heroku are more mature than Azure and AppHarbor, but without doing a detailed comparison, it's hard to say just how much.
But there are several relevant areas where C# is definitely superior to Ruby.
- Performance. Even die-hard Ruby aficionados admit this. Any individual routine will run dramatically faster in C# than in Ruby, but beyond that, C# ongoing improvements around the async/await keywords means that it's possible to write highly scalable asynchronous code almost for free. Even if the Ruby community manages to implement some sort of JIT to address the single-threaded performance issues, it seems unlikely that they'll be able to address the overall scalability issues anytime in the near future. There are many websites where this simply isn’t a consideration; but there are plenty of others where it’s still a pretty big one.
- Static typing. I totally get the advantages of dynamic typing and the value it can contribute to an ecosystem. But even so, for writing my own code, I remain a huge fan of static typing. Like unit tests, it's a great way of catching large classes of errors as early in the development process as possible. (Another way of looking at it is that it's an extension of the DRY principle: you only need to tell the system once how a class needs to behave, rather than repeating those expectations in every class or method that depends on that class.) I wish the C# static typing system was more flexible than it is: it should work more like TypeScript does, which looks at the actual signature of a class rather than at the interfaces it says it implements. That would enable a whole lot of “convention over configuration” in the C# world. But even as it is, I’d argue that C# can catch the same amount of errors with 50% fewer unit tests than Ruby, as the type system catches those errors for you. Since probably half your time in Ruby is spent writing unit tests, that's a significant time savings. This especially becomes noticeable when you've got large code bases that you need to refactor - and that leads into #3.
- Refactoring. It's a little counter-intuitive, but in my experience, statically typed codebases are much easier to change than dynamic codebases. Because the tools understand the structure of the program, those tools can do much of the work for you. To take the simplest refactoring example, if you want to change the name of a field or property, VS will not just guarantee that every instance of “User.Id” gets changed to “User.UserId”, but even more importantly, will also guarantee that no instance of “Organization.Id” gets changed to “UserId”. When you've got hundreds of references throughout your codebase to some field named “Id”, that's a massive time savings. And even if you need to make a change that can't be made automatically (like splitting a class in two), the IDE will tell you the precise lines of code that just broke. There are ways of working around this sort of thing in Ruby, but it involves adding additional levels of indirection around almost everything, to the point where you end up losing the much-touted advantage of dynamic languages of being able to write code fast. In Ruby, the only net you get is the one you write yourself, so you have to spend a lot of time patching holes in that net.
- Visual Studio. Visual Studio is the best IDE around, without question, and that's mostly because it takes full advantage of C#'s static typing. To take just one example, intellisense and code completion is a major help when dealing with large codebases. VS completely understands the structure of your code: it can tell you as you're typing what the possible methods are that you could call, what their parameters are, and use embedded metadata to provide plain-language help, without you ever taking your eyes off the cursor. That's a massive time savings, and Ruby's dynamic nature means that it's simply impossible to do that in the same way, no matter how clever or mature the development environment.
In brief, I get the impression that if you knew both languages equally well, you should choose Ruby if you think the biggest technology challenge is just getting off the ground. I think that someone who knew Ruby and who knew C# equally well could get further along in the first year if they were building the website in Ruby. I also think that after the first year, or as the team grows, you'd probably be able to move faster in C# - and you'd certainly have fewer scalability and performance issues.
The additional data point that I need to consider is that I don't know both equally well. Come September, I suspect I’d have more of a website if it's written in C# than in Ruby, just because I think in C#, I know the tools cold, and it'll take me a while to become that fluent in Ruby. (It's taken me years to become this fluent in C#, so I have my doubts about picking up Ruby as quickly as some folks have said.) There will probably be some period of time after those first three months where I’d have more of a website in Ruby, just because of the additional flexibility and more mature gem ecosystem. But I also think that after a year or so, I’d really start to miss the static typing, refactoring capabilities, and better performance of C#. C# is just inherently better suited to large, complex codebases with significant scalability requirements. (Ruby would simply not have worked for Zango, for instance. I can say that with complete confidence.)
So all of that said, this is a graph of my theory:
Anybody have additional thoughts, or wish to correct any of my assumptions or conclusions?