- Published on
What Ruby taught me about flexibility in Software Engineering
- Authors
- Name
- Eyji Koike Cuff
🧠 This is part of my series inspired by Seven Languages in Seven Weeks (Tate, 2010). I’m exploring each language not to master it, but to learn how different paradigms shape how we think as software engineers. These are the takeaways I’d share with another backend dev over coffee.
🧵 Ruby in a Nutshell
Ruby is expressive, opinionated, and built for human-friendly syntax — developer speed and flexibility are at stage here. Everything’s an object. Blocks are first-class. And the standard library feels like it wants to do the thinking for you. It’s meant to create Domain Specifc Languages (DSL) and scripts.
Appreciate the following lines of code:
File.foreach(file_name).with_index do |line, idx|
if line.include?(search_string)
puts "The line #{idx} includes #{search_string}"
end
end
How cool is the fact that you can literally ask each line if it includes something?
Coming from Python with strict typing, Ruby felt like coding with silk gloves — powerful, but a bit slippery.
🧠 Takeaway #1: Duck Typing vs. Type Awareness
Ruby and Python both embrace dynamic typing, but they diverge in how much they lean on structure.
Ruby is unapologetically flexible — no type hints, no static checks, no complaints. It trusts that if you send a message to an object, it knows how to handle it — or it’ll throw a clear error when it can’t.
if it quacks like a duck, then it's a duck
Meanwhile in my Python world:
I use Pydantic for data validation and Pyright for static type checking. My models are explicit. My tooling catches mistakes early. It’s a kind of defensive confidence that scales well in real-world services and essential for growing codebases.
What hit me:
Ruby feels like a jazz musician — improvising, expressive, and fluid. My Python is more like chamber music — precise, coordinated, and rule-driven.
What I’m rethinking:
Structure adds clarity, especially in growing systems. But maybe I’ve leaned too far into it — Ruby reminded me that some trust and flexibility can make code faster to write and more fun to read. I will steal Tate's (Tate, 2010) comparison with Mary Poppins:
Mary Poppins made the household more efficient by making it fun and coaxing every last bit of passion from her charges. Ruby does the same thing and with more syntactic sugar than a spoonful.
All that syntactic sugar — the things that make code more readable — goes directly into the software engineer’s bloodstream.
🧠 Takeaway #2: Metaprogramming as a Language Feature
Ruby’s metaprogramming is not a side trick — it’s built into the DNA.
You can define methods at runtime, open up classes, and intercept missing methods with method_missing
. And somehow, it still feels readable.
You can literally create a class to create methods and properties inside another class:
module ActAsCsv
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def act_as_csv
include InstanceMethods
end
end
module InstanceMethods
def read
@csv_contents = []
filename = self.class.to_s.downcase + '.txt'
file = File.new(filename)
@headers = file.gets.chomp.split(',')
file.each do |row|
values = row.chomp.split(',')
@csv_contents << CsvRow.new(@headers, values)
end
end
attr_accessor :headers, :csv_contents
def initialize
read
end
def each(&block)
@csv_contents.each(&block)
end
end
class CsvRow
def initialize(headers, values)
@data = Hash[headers.zip(values)]
end
def method_missing(name, *args)
@data[name.to_s]
end
def respond_to_missing?(name, include_private = false)
@data.key?(name.to_s) || super
end
def to_s
@data.inspect
end
end
end
class RubyCsv
include ActAsCsv
act_as_csv
end
m = RubyCsv.new
m.each {|row| puts row.one}
Compared to Python:
Python can do metaprogramming with decorators, __getattr__
, and metaclasses — but it’s not as encouraged. In Ruby, it’s almost expected in idiomatic code, especially in DSL-heavy frameworks like Rails.
What I’m rethinking:
Could some of my rigid abstractions in Python be replaced with smarter, more flexible object behavior?
🔁 From Ruby Back to Python
Reading bout Ruby challenged me to be less defensive with my design.
- 🧼 I appreciate how Ruby handles optional structure with elegance.
- 🔍 I’m more intentional about where strict types help — and where they overcomplicate.
- 🧰 I’m curious to explore small, metaprogramming - inspired tools where Python makes sense.
- ⌨️ I know Ruby and Rails can be a tool where low time-to-market is key
⚖️ Tradeoffs: Flexibility vs. Safety
Ruby’s dynamic nature makes it incredibly expressive — but it also means you won’t catch certain bugs until runtime. For quick scripts or developer-friendly DSLs, this is a win. But in large, evolving codebases, that same flexibility can lead to uncertainty and false assumptions. And let’s not forget: all that sugar adds a questionable performance penalty, plus the fact that methods can change behavior at any time.
In contrast: Python’s ecosystem has grown to embrace stronger type safety through tools like Pydantic and Pyright — and it’s changed the way I write code. I get clearer contracts, better editor support, and earlier bug detection.
But this comes with overhead:
- You write more boilerplate
- The feedback loop tightens, but creativity can sometimes feel boxed in
- You optimize for maintainability, not always velocity or engineer experience
So where’s the line?
Ruby reminded me that some looseness — when used intentionally — can improve developer experience. But in production systems with long lifecycles, I still lean toward Python's newer type — heavy practices.
Both approaches have a cost. Ruby bets on developer intuition. Python (with static types) bets on structure. Knowing when to trade one for the other? That’s the real move.
💬 Final Thought
Ruby reminded me that flexibility isn't the enemy of clarity — it can enhance it, when used with discipline.
Next up: Io, the tiny prototype-based language that made my brain hurt — in a good way.
Want more like this? Subscribe here to get new posts straight to your inbox.