It's been a long time since the last time I built something in Ruby. Most of the work that I've done with the language while I was iOS developer were changes on either the CocoaPods
Podfile, or the
- Ruby is the primary programming language at Shopify, my current company. That was a decision made a while ago, and all the internal tools and libraries are built in Ruby. It didn't make any sense to push for another language that couldn't fully leverage the existing stuff or be integrated easily.
Since I joined Shopify, I've been doing mostly Ruby. There are great engineers with a lot of experience here, and it's a fantastic opportunity for me to learn. It felt bizarre the first time that I tried to write some Ruby after some time off. I'd like to outline some of these strange feelings:
Types: This was probably the most awful thing for me. I wasn't aware of how much I was used to types. I just used them, and I safely wrote my apps or Swift scripts. There are no types in Ruby. You call a method that takes a few arguments with some names, but you don't know which types the method implementation is expecting. Should this argument that I'm passing be a string? Should it be an array of String? With Swift you can write the code with a lot of confidence, but with Ruby most of the times, you have to dig into implementations to know what types the method expects. I've seen some Ruby projects, and they seem to leverage documentation to offer types information.
Where should I validate my data: Our software gets input and returns output. If we take a mobile app, for instance, inputs are user interactions, and the output is the views presented on the screen. When an input is received, the action is propagated through our software, to produce the output. In every step of the propagation, we have the types system and the compiler to make sure that all the pieces in our software match. It won't let us go to production or the store if there's a mismatch. With Ruby, the validation happens as the action propagates through the system. If the system hasn't been designed properly, it'll result in a runtime error, and the system will need to recover from it. That scares me, and to be honest, I don't know what's the best approach to minimize this yet. It doesn't make sense to validate all the inputs in our methods because it's slow and we shouldn't assert for something that we've wrongly implemented. But if we only validate the input, can expect some runtime errors to blow up unless we thoroughly test all kind of integrations in our software.
Code organization: In Ruby, there's this notion of modules. Modules are used among others, to create namespaces. That's an idea that didn't take me much to swallow cause there's something similar in Swift, but how to split modules and classes in different
.rbfiles is another story. I've checked multiple open source projects, and each of them does it differently. I've seen some using
require_relative, others using an umbrella entry point Ruby file that defines the project hierarchy autoloading all the components. I've stuck to the latter, and it's working well for me.
autoloadis not thread-safe, and that it'll be deprecated, but since I don't have any thread-safety requirements in the tools that I write, it's safe to use it. This is an example of what an umbrella Ruby file looks like:
module Catalisismodule Builderautoload :Project, 'catalisis/builder/project'endend
I'm getting used to the things that I mentioned above. When you spend some years with a language, you tend to replicate the patterns and styles into the new language. For example, instead of using the naming convention that is commonly used in Ruby projects I literally brought the one that Swift recommends, where if something can be encoded, it should be called
There are things from writing software in Ruby that I'm enjoying a lot:
Minitest/guard: You can see how mature the language and the community are when you start using the tools. One that surprised me a lot if miniguard. When you write tests, it detects the files that you changed and runs the tests of those files, imediately. You can focus on writing your code/tests, and there'll be a parallel process running and telling you whether everything passes or there's an issue. You can do proper TDD.
Editor: The language is not tied to any editor/IDE so you can choose whatever works best for you. If you prefer something closer to the Xcode experience, you can try any IDE from the JetBrains suite. If you are a minimalist and just want a simple text editor with syntax highlighting and pluggable add-ons to customize your workflows and make you more productive you can use Sublime, Atom or VSCode. I personally use VSCode. It' simple, fast and extensible. It has just what I need to work on my projects.
Libraries: Whatever you can imagine can very likely found as a Gem. The community has been building libraries for a long time. This saves you a considerable amount of time.
Distribution: Your projects can be distributed as gems. You can install it on your system, and it'll install all the necessary components to get it working. This is not officially supported by Swift and its package manager, so you have to appeal to non-official tools. I wouldn't be surprised if Apple includes this as a feature in the Swift Package Manager, but even with that, pre-compiled binaries would break with new versions of Swift because it hasn't reached ABI stability yet.
If you have had a similar experience transitioning from a static to a dynamic language I'd like to hear your experience. Don't hesitate to leave a comment right below.