4 ms·
Refactoring tools are nice (I guess; I've honestly only used them in Java; I'm mostly an Emacs person), but my main gripes with Ruby maintenance are harder to f
by wheels 4y ago
Refactoring tools are nice (I guess; I've honestly only used them in Java; I'm mostly an Emacs person), but my main gripes with Ruby maintenance are harder to fix with type annotations:
- In duck-typed languages you have to write a lot of tests to do verify things that the compiler does for you in statically typed languages. That neuters much of the benefit of the concision of such languages. Crystal shoots for the best of both worlds. (Static type checking, but usually without explicit signatures.) My main refactoring tool in statically typed languages is the compiler: if I break something, it'll tell me.
- Monkey-patching, open classes, etc. I do it too. Pretty much every Ruby-ist does. But it makes it damn near impossible to track down bugs sometimes, because even finding out what file the relevant code is in isn't trivial. Again, Crystal seems to mostly side-step that pitfall.
- Speed. I don't even attempt to write fast code in Ruby (though I have written a few C++ extensions for Ruby in a pinch). But if I could get near-to systems-language level performance out of something that was almost Ruby, that'd be pretty amazeballs.
- pmontra 4y ago> In duck-typed languages you have to write a lot of tests to do verify things that the compiler does for you in statically typed languages Would you give some examples? I think I'm testing only functionality but maybe I'm not realizing that I'm testing the types of arguments.
- wheels 4y agoSure. Ruby: def foo(a) raise ArgumentError unless [ :bar, :baz, :quux ].include?(a) return 1 end def test_foo assert_raise(ArgumentError) { foo(1) } assert_raise(ArgumentError) { foo(:moo) } assert_instance_of(foo(:bar), Integer) end In C++ I'd write: enum class Value { Bar, Baz, Quux }; int foo(Value a) { return 1; } In a strongly typed language you don't need to do any of the input validation. I'm not sure that I'd do that level of granularity of tests in an application, but I spend a lot of times writing libraries, where it's pretty important. You also have to call all code paths with your tests in Ruby because otherwise you won't hit errors. Just basic, "this thing runs, the methods it calls exist, it returns a value, and it's of this type" is all guaranteed in a strongly typed language and doesn't need to be explicitly tested.
- wheels 4y agoActually, here's another example: def foo(a) if a == 1 bar else baz end end def test_foo foo(1) foo(2) end In the C++ equivalent there's no need to test those values because the compiler will tell you that the methods exist. In Ruby you need to test them so that you catch those paths in refactoring. void foo(int a) { if(a == 1) bar(); else baz(); }
- pmontra 4y agoI'm not sure I'm understanding this. Don't you have to test that the implementation of foo returns the correct result (or writes the correct value in the db) both in Ruby and in C++? But I've not used compiled languages for a long time so maybe I forgot something important.
- wheels 4y agoThere's something lost in the simplification: Not every code path really has to be tested. Let's imagine that they're e.g. displaying a message box. I feel pretty confident that if I call a system function to display a message box, it'll display a message box. So in C++ I wouldn't test that. In Ruby, I'd need to make sure that I made calls to both code paths so that if the function signature for displaying a message box changed, that I'd get an error in my tests. This isn't a hypothetical: I write Qt applications in C++ and Rails apps in Ruby. The pain of switching major versions in Qt is trivial compared to Rails, mainly because once it compiles in C++, it probably also works. Could you imagine just assuming that a Rails app worked after a major Rails version upgrade just because it didn't throw an error on startup? That's really the experience of working in statically typed languages.
- pmontra 4y agoOK, I got it now. Thanks. You are right, the compiler for a statically typed language can do checks that are impossible to perform for a language like Ruby, that could also create methods calls and add arguments at runtime. I tend not to use metaprogramming except some object.send(method, args) when strictly necessary. The reason is that it slows down the team when trying to understand what a piece of code does and if not properly understood it generates bugs. Metaprogramming is buried down into third party libraries (e.g.: Rails) but we are not testing it there. I occasionally get bugs that a compiler would catch, maybe one every year or two. One happened on Python this year. I can't remember what. I should dig into slack: I remember I wrote a note to my customer (maybe the classic id as string vs int?) But the time not lost writing type annotations is immense. I wasted cumulative weeks on that when I was working in Java 10+ years ago. And cumulative weeks of malloc/free when I was working in C before Java. All considered I prefer the occasional bug and extra test in Ruby and Python. I consider that path (GC and no types) an improvement. Of course it costs performances but customers are happy running Ruby and Python and are still in business. It can't be wrong
- galaxyLogic 4y ago> My main refactoring tool in statically typed languages is the compiler: if I break something, it'll tell me. That really helps. But what's even better in practice is if compiler is integrated into an IDE which can highlight type-errors already while you are editing the code, before you explicitly invoke the compiler. For instance Eclipse IDE does that for Java code. If you need to run the compiler by hand before you get any error-messages it becomes a huge delay and you lose the immediate feedback which is needed to keep your focus on the code, not on compiler error-messages.
- hamandcheese 4y ago> In duck-typed languages you have to write a lot of tests to do verify things that the compiler does for you in statically typed languages. Ruby with Sorbet is not duck-typed.