This post brought to you by static typing
Sun, Feb 14, 2021
ruby go programing-languages
I first learned Ruby and Ruby on Rails back in 2011. At the time I was coming from the Java world and it was a breath of fresh air. Ruby and Rails has much less verbosity than Java and you could get a lot done in a short amount of time. I happily did Rails for six years and didn’t see too many problems with it. For various reasons, the next project I worked on was done in Go. I worked on that project for a few years, and am now back on the Rails, except all of the rough edges in Ruby/Rails are much more obvious.
Ruby is an extremely dynamic language. You can basically redefine everything during runtime. There are benefits to such flexibility, but it’s not without pitfalls. The biggest thing that I find lacking in Ruby/Rails is static types. Static typing might feel like handcuffs when you’re used to being able to define and redefine anything at anytime, but it has some serious benefits. The biggest benefit in having static types is that the compiler can point out things that are wrong before you even get a chance to run your code. Let’s look at some Ruby code:
def divide(x, y)
return "Can't divide by 0" if y == 0
x / y
end
You wouldn’t write this to use in the real world, but Ruby won’t stop you. Is it better this way though? Is it just “caveat emptor” for developers? Let’s look at a real world situation I ran into. A Ruby library I used documented a feature that you could implement a method that would be called when a query wasn’t authorized and it would return the response of that method instead of raising an exception. It looked like this:
class MyQuery < BaseQuery
def unauthorized(owner, value)
{
status: "Error",
message: "#{owner} is now allowed to access that."
}
end
end
It’s a neat feature except for one thing: it completely bypassed permission checks. Of course, the permission checks were what we were using to enforce permissions. We actually had some tests that would still pass because the correct result was being returned, the problem was that the underlying GraphQL mutation was still being executed. Digging into the library code (and about 75 levels deep stacktrace) I found a function that was doing this:
def authorized?
authorized = check_authorization
result = if !authorized && self.method_defined?(:unauthorized)
self.unauthorized
else
authorized
end
end
Where this goes wrong is that typically methods ending in ?
return true
or false
. Nothing in Ruby enforces this, it’s just a convention. Further, Ruby will coerce other types when used in conditional so that our hash in the unauthorized
override above when interpreted in a condtional will be evaluated to true
. If Ruby had a stronger type system, the return value of the authorized?
function in the library would be a bool
and a compiler would have reported the type mismatch.
In order to get the same checking that a compiler would give you in a statically typed language, Rubists have taken to writing large amounts of unit tests. You can get a long way with such tests, and having a statically typed language doesn’t preclude the need for testing, but you can eliminate some classes of errors without any work by the developer by using a statically typed langage.
Thanks for reading.
— Beardo