Switching to a new tech stack

Nicolas Zermati
Alan Product and Technical Blog
8 min readDec 8, 2021

Context 🗺️

One year ago, I decided to go on a new adventure. I deeply enjoyed the work and team at Getaround EU but I felt it was time for a change. It seems a bit silly since I warmly recommend people to join them when I have the chance to. I might have had solid reasons or acted a bit like an idealist diva, most likely a bit of both. I also decided to take that leap because “the other company” was Alan.

A ton has changed regarding culture, management, hiring, scope, and so on. This article is about the change of technical stack that I personally went through:

  • full-stack in Python and Typescript, coming from back-end in Ruby
  • new ecosystem, idioms, frameworks, and standards
  • younger codebase: less mature, and fewer non-functional requirements

If I’m not hired for my years of experience on the company’s tools, one could wonder: what am I hired for?

Alan is kind enough to assume software engineers will learn as they go. Alan trusted me to be able to adapt. My future colleagues believed I was mature enough to embrace the change. It’s not that easy.

From one stack to another 🏯

I practiced a few languages: C, OCaml, PHP, Java, Haskell, Scala, Erlang, Python, Go, Ruby, SQL, and so on. I see myself as open when it’s about programming languages and their paradigms.

Sadly, after many years of using the same hammer — and nailing so many things with it — it became hard to use a different tool to do the same job. I’m used to OOP and specifically Ruby’s approach of OOP to organize and reuse code, design APIs, and more generally model problems.

It doesn’t mean that I can’t understand or write using another paradigm. It’s that Ruby’s paradigm is almost always coming to my mind first. I have to make an extra effort, to think harder, not to stop there. And when the effort is made, when I finally produce something in this other style and look at it, I find it worse than the first thing that came to mind. I find it less elegant, less intuitive, less maintainable, less testable, …

Despite my efforts, I’m rarely satisfied with the result. I have to constantly ask myself the questions:

  • Is this worse or is it simply unfamiliar?
  • What is actually bothering me here that wouldn’t there?
  • What am I losing/gaining from this different approach?

This is already frustrating but there is more. There are also the meta-questions like:

  • Is it a waste of energy to answer those questions? Should I focus on being more productive?
  • How aligned with the new paradigm is the code I’m writing? Is it consistent?

Becoming a “polyglot” 🗣️

Getting more and more familiar with the new team’s practices, I ask myself those questions less often. It isn’t that I’ve found a balance, that I’ve got numb, or that I don’t care anymore.

  • The unfamiliar feeling, that was causing noise, leaves more room for valuable signals.
  • The old hammer doesn’t come first anymore. It is a conscious process to think about it.
  • I accepted more trade-offs when I truly internalized some advantages of the new paradigm.

When I compare this new stage with the transition one, I’m not sure which one is best.

  • I’m less frustrated today. But, I’m not taking as much of a step back as before.
  • I’m more productive today. But, I do not challenge as often the status-quo.
  • I’m more flexible today. But, how long before this became my new hammer?

Concretely ⚙️

It starts with the language difference ⌨️

At first sight, one could say that Ruby and Python are very similar, they’re both dynamic languages, they’re both multi-paradigm, object-oriented, and so on. There are subtle differences setting them more apart from each other than it might seem…

In Ruby, method calls are the only way to organize code. Even syntax like hash[key] is actually a call to the [] method, on the receiver hash, and with the parameter key: (ie hash.[](key)). The same goes with a + b which is a.+(b). In Python, on the other hand, syntactic structures rely on magic methods. The same hash[key] will translate to accessing the __getitem__ entry from the hash object, and pass the key to the function found in that __getitem__ entry. Even calling a function goes through those magic methods... It ends up like this: hash.__getitem__.__call__(key).

In Python, each file implicitly defines a module, and to be available in the module’s namespace, dependencies must be imported explicitly. In Ruby, that’s the opposite, we must declare modules explicitly, and when a file is read, all its declared content becomes available globally.

In Ruby, we extend the objects to add new features with mixins. For instance, the well-known Enumerable mixin provides a lot of utilities such as any? or reverse_each. In Python, built-in functions rely on magic methods to provide those basic features. The equivalent for the previous examples would be any or reversed.

To sum up:

In Ruby 💎

  • Everything is a method call
  • All code is sharing a global namespace
  • Modules are defined explicitly
  • No built-in functions, mixins instead

In Python 🐍

  • Everything is a dictionary
  • All code lives in its own module
  • Modules are defined implicitly
  • Built-in functions, based on magic methods

The differences above make Python functions more independent from data. It makes Python more flexible by playing nicer with procedural or functional code. On the other hand Ruby has a bias toward object orientation which makes the language, and its mental model simple and consistent.

Then conventions take over 📏

At Alan, we mostly write procedural code 🙊

To be honest, I didn’t like procedural code at first. It was hard to look at it objectively at first. Those were my — very subjective — fears at the time:

  • Abstraction, not much incentive to build layers of abstractions
  • One can have_long_and_specific_function_names rather than intermediate abstractions
  • Polymorphism, dog_speak(dog), cat_speak(cat), or even speak(dog_or_cat)
  • Rather than dog.speak() and cat.speak()
  • Duplication, specific functions are harder to compose
  • It’s easier to duplicate and specialize or to grow a procedure with a little condition
  • Discoverability, how to know which function is the right one without hierarchy?
  • Objects naturally provide some level of colocation between data and their capabilities.
  • Refactoring, how do I know which functions are working with which data?
  • They might be no reference of one in the other and the other way around

In the end, I was wrong. Looking at what others did in the code base, I’ve found out that none of the above were inevitable. I also found out that there were some very elegant patterns specific to this paradigm, that it was closer to functional programming, and that it can be very nice to work with.

I wasn’t totally wrong either. Procedural code is more forgiving than object-oriented code. I can go a long way without caring for the above list of concerns. When I’m in a hurry, I can easily push something out of the door fast, postponing those hard questions to the future. Sometimes it makes sense to wait, but sometimes it creates an even bigger mess down the road.

In the end, it took me a full year to overcome frustrations and enjoy procedural code, and Python!

A sample use-case 📚

First, let’s look at a Ruby implementation, trying to leverage an object-oriented approach.

We can have a very similar approach in Python: objects with both data and behaviors, sharing code via multiple inheritances, duck typing, and so on.

Let’s look at the procedural version of it…

This example seems a bit trivial but it shows a couple of things already.

In Ruby 💎 (Object-oriented)

  • Must include HasEligibleHealthContract
  • Must wrap functions into classes or modules
  • We move through objects
  • We’ll need an anonymous class to test mixins

In Python 🐍 (Procedural)

  • No need to touch the (data) classes
  • Bare functions, not methods
  • We pass around data
  • We’ll be able to use a fake target during tests

Python allows us to mix object and procedural approaches in a straightforward way. We could adopt the same style in Ruby, of course, but it involves a bit more ceremony as we would still have to define modules to host our functions and they would still be methods of those modules.

There are lots of language differences. I think Python has way more affordances toward procedural code than Ruby. For me, it unlocks a new way of programming, new patterns to experiment with, a new definition of “elegant”, a whole new ecosystem to discover, and so on ✨

That’s a (huge) win 🏆

First for me 🎯

I never thought I would say (and believe) this… I came to prefer Python over Ruby as a multi-paradigm language. I still think Ruby is optimal for “pure” object-oriented programming though.

A lot of things are left for me to pick up. Avoiding my initial fears: designing good systems and procedural APIs are the next thing I want to nerd on. Having all that learning ahead is refreshing!

Acknowledging all that shows that I’m more mature and open than I was. Even if I was aiming at this all along, it is comforting to see all the frustration is finally paying off.

Then for (hopefully) the team 👯

When I read procedural Python code, I was skeptical. It isn’t like someone starting their career, I have opinions. I had this experience with another paradigm and I could bring that to the team.

It’s not about converting what’s existing to what I’m the most comfortable with, not at all. It’s about taking inspiration from something else and bridging the gap to get the best of both worlds. It’s about knowing another ecosystem and bringing back what I truly miss.

The team pays the cost of “training” people like me, that need to un/re-learn things. It might be more expensive than people that are early in their career and that haven’t been used to something for too long. In exchange, the team wins a step-back on their practices and different opportunities to improve.

Let’s jump! 🦘

When I started my career, I didn’t have anything to lose by starting with one technology or another. Then, when I got some experience of it, it was tempting to capitalize on it. It was appealing to hone my language and its ecosystem even more. Employers would generally pay me more if I was already proficient with their frameworks since I could “hit the ground running”.

There were plenty of reasons to stay with the stack I already knew. Jumping is scary, falling is hard, but the lessons learned and the step back I could take so far made it worth it!

--

--