Note: this is me venting about programming and isn't meant to be read by non-programmers. Well, it isn't really meant to be read by programmers either.
Ruby has a few nice features. Rails means that you can set up a dynamic website quickly. There are a few things that make collaboration easy (ie, rake tasks for installing dependencies and database independence).
Ok, now that we have the nice parts out of the way, I can get to my real feelings.
I'm not a fan of Ruby.
In a language, letters are constant. For instance, in English there are 26 letters. The words are also fairly stable. They might differ as the years go by, but if I read a book, the way that it uses its words will be pretty much the same as in any other book. The language is relatively static. Creativity comes from how people use the language much more than how they change it. We are no longer living in Shakespeare's time. Many of the 'new' words that we have now are just variations on old words (ie, email was once e-mail or electronic-mail), with a notable exception for cultural phenomenon or proper nouns that become words (ie, Google, Facebook, Frisbee, Bandaid). Even new words that are uniquely created aren't integrated into our lexicons until they become sufficiently popular. If I'm using a new word, I shouldn't expect someone else to understand me until the word gains recognition by the culture. This is particularly true of words that start out as scientific jargon and then come into everyday usage as time changes.
In the programming languages that I like, this is also true. The language itself is controlled, and the creativity comes with how you use the language, not from how you change the language. In C, if you know how to do basic math (including some binary math), function calls, and manipulate pointers, then you can understand what a C program does.
Ruby, however, had the great (sarcasm) idea of making some basic language features changeable (in fairness, C++ also has some of these flaws. Maybe I just don't see them used as much in C++ except when it really makes sense). For instance, brackets and equals signs can be used for any function. That means that I can have a "foo=(bar)" function, and then when I type "foo = 2", it will pass in 2 as an argument to the "foo=" function. "foo" isn't an l-value; it's the name of a function (oh yeah, because in addition to making everything a function, they made it so that you can't immediately identify something as a function by looking for parentheses because parentheses are optional. Just by looking at code, I don't know if I'm looking at a function or a variable).
This is fine in some cases. Normally, the idea of the language is to trick you into thinking that foo is an l-value, just one that you don't really know where it is, so even though it's tricking you, it behaves mostly as expected. Usually, also, it's used as a method within a class (ie, "my_object.foo = bar", so that you are tricked into thinking that you're actually modifying something like you would a struct in C rather than just passing an argument to a function), and there it usually behaves as expected.
This can trip you (you == me) up when you start believing the lies that the language tells you. Notably, Ruby can't tell whether foo = bar means that you want to create a variable named foo and assign bar to it or whether you want to pass bar to the foo= method. Earlier today, I wrote current_user = user. current_user= is a method. But Ruby didn't know that, so it allocated new space for the current_user variable. This would have normally been unexpected but have left my program running fine; creating a local variable that was assigned to the value that I wanted would have left me without any immediate bugs. However, current_user is also a method (not a variable) that I use in other parts of the function to get the current user. When I added the current_user= in to a block of code that was never getting executed, it changed the behavior of a different part of the program because now Ruby had to allocate space in the local scope for a variable called current_user, which meant that, in the other part of the code where there was no current_user variable but where I was calling the current_user method, I was getting nil values unexpectedly. Commenting out a line in a block of code that isn't being executed shouldn't change the behavior of my program!
Now, it was my fault for not realizing that Ruby was lying to me about current_user = being an assignment operation, but it's hard to not believe the lies that Ruby and Rails tell because they're everywhere. In Rails, they try to make coding easier by including external files by default so that you don't have to. This translates to using a lot of code that you see no reference to in the file.
In the defense of Ruby, it does have a strict naming system for different variable types, so it is obvious to veteran Ruby coders that they are really dealing with a function call rather than with a variable assignment, and it would be nice if other languages enforced stylistic conventions so that code looked similar.
I guess that Ruby would work well if all of the abstractions provided were really black boxes with well defined input and well defined output. But that has not been the case with any of the libraries that I have used (except the built in ones -- the Ruby built ins are wonderful). I had to look at the source code of every one of them to figure out why I was getting some bizarre bug. This is because the documentation is usually incomplete. For instance, on the question of what type of variable I pass in or what type it returns to me. The documentation might say "pass in the request." Do they mean the body of the request? Do they mean a signed request? Do they mean some request object defined in their library? Do they mean some other request object?
Often, the most commonly used part of a library will have some documentation. Often, that will be the only documentation. This is especially annoying when that documentation says "returns a Foo object" or "for use with the bar method" and neither Foo nor bar have any documentation. It's also common for a library's documentation to say "works with all of the options from the Bar library" and for the Bar library not to have any documentation on its options because the method that Foo used was internal to Bar rather than one of the main methods.
I guess when you're getting code that other people wrote from free repositories online, you can't expect them to write a perfect documentation. But that's why it's important to have code that is more structured. If I see an array in C or Java, I know exactly what it is, what it holds, what I can do with it, and how to give it to another function that needs it. I can see the type, and I can easily learn about any variable with a known type. I don't need to look at the source code of the function that gave me the array to figure any of that out. In Ruby, if I see an array, I can hope that it has a certain set of behaviors, but that is really just a hope: you can modify built in classes as much as you want to do whatever you want, so the foo array might not be very similar to the bar array. And most objects aren't just vanilla, but rather are classes with a bunch of methods that you probably shouldn't use and one or two poorly documented methods that you should.
In programming, you aren't typing that much. Most of your time is spent looking at code for bugs or looking at other people's code to understand it. Thus, I find it problematic that one of the major selling points of Ruby and higher level languages is that you don't have to type as much. They make the easy part of programming easier and, in the process, make the hard part of programming harder.
As Nick Parlante says, for small, independent, quick projects, scripting languages can be, but if you want to work with other people or work on big projects, then languages like Java are much nicer.
No comments:
Post a Comment