Writing Rails code makes me miss writing in Objective-C. Or C. Or Java. Or...
sachin.posterous.com
sachin.posterous.com
Understanding your libraries and their implementation is a great thing. Once you realize that library code is not a magical black box, and is instead very much like the code you are writing, you can really become a productive programmer. ("But what about abstraction! If I have to understand my libaries, they're not abstract enough!" Wrong. Abstraction prevents parts of your program from knowing about each other. You, the programmer, still have to know how each part works. Abstraction means you only have to actively think about once piece at a time -- nothing more.)
The Posterous codebase is littered with monkey patches, modifying gems to work as we need them to.
Yeah, you know that "fork me" button on Github? You click it, then you edit the code, then the bug is fixed for every single person on Earth. That's the point of free software; if there's a bug, you squish it and move on.
(I do this all the time. Does it take time to fork someone's project, fix the code a bit, and send them a pull request? Yes. But it's worth it, because now you have a robust and permanent fix... and a community!
Monkey patching is for monkeys. Collaborating is what humans do.)
Another point of the original post is that monkey patching is the kiss of death of many Ruby and Ruby on Rails projects.
I can't think of any change I've ever made to an open source project that hasn't been accepted. This ranges from random Perl modules to Emacs and the Linux kernel. Contributing is as easy as sending an email!
All of these things have annoyed me from time to time over the past 5 years of working almost exclusively with Ruby. However the community also has its strengths which are extremely appealing for passionate programmers.
For one thing, the fact that Rails emphasizes improvement over maturity has made Rails 3 an amazingly modular framework, able to better fit enterprise requirements better than 2004's DHH ever would have wanted or acknowledged the need for. Also, consider the rise of the RSpec and Cucumber frameworks despite the fact that Test::Unit and Mocha already had the bases pretty well covered. In other communities, yes, you get better maintained libraries, but you also get stagnation from having too high a bar for new ideas. In Ruby you have to deal with constant deprecation, but at least you get better chances that your entire ecosystem won't be obsoleted by the next hot language or framework.
On the other hand, if you're talking about the ability to mix, match, configure and replace components of Rails in order to meet your particular requirements, then Rails 3 shows real promise, though it's not yet officially released and a lot of the modularity needs battle-testing.
Don't worry, the Perl and Python web frameworks have been doing this for years, and it works great.
Because I was just talking about Rails 3 getting out in the wild and people getting to actually start using it with heavy customization to shake out some of the bugs. The re-archtecting to this point is quite impressive, but it's still living in a bit of a vacuum.
This is the common wisdom about java, but I don't think it's true. I have a far harder time dealing with other peoples java code than I do dealing with other people's ruby code. And yes, I work in a corporate army that happens to do both java and ruby.
Libraries change over time. In java, if you realize your interface needs to change then you're fucked if someone else is using your code.
If you're using someone else's code and it doesn't do exactly what you need it to do then in java you have to get their code, change it, compile your own custom version, and in many java shops upload it to your companies maven repo with some version like foo-lib.jshens-fork-1.0.1. In ruby you'd just monkey patch it. This has it's risks, but those risks are under your control, you don't have to ask someone else for a favor to update their lib. (using other people's monkey patching is a different animal)
Spring, need I say more? In our corporate army there are 1000's of developers with 1000 page tomes telling them how to glue their libraries together in java. Think about that for a minute. Some of the "problems" that java doesn't have are really still there. They're just pushed into things like maven, ant, and spring.
At the end of the day, bad java code makes my life much harder than bad ruby code.
On a side note, what's up with the recent spate of people complaining about rails because they used some shitty library that isn't a part of rails? Here's a hint, you should do some due diligence before using some random library.
However I do believe from my limited Java experience that it's nearly impossible for a bug to occur that the average programmer can not trace back to it's source using more less standard methods (race conditions and other concurrency nastiness notwithstanding). In Ruby on the other hand, real head scratchers can and do occur on a regular basis that require a higher level of debugging skill than some mass-educated code monkeys are capable of.
I've seen the "code monkeys" you speak of hit a brick wall with spring. Java the language simply pushes these problems into it's tools. I've spent countless hours getting my java tools to play nice. I don't have that problem in ruby. There really is a law of conservation here.
Usually once a year or so I run into one of these mind-bending bugs where something breaks in a bizarrely tangential way that simply can't happen in Java.
I wonder what 3rd party libraries they are using for the iPhone. We've had the same experience he had with ruby libraries with 3rd party iPhone libraries.
Every time you want to write a monkey patch, you should really ask yourself if it is worth the likely future maintenance costs.
If it's not the number and quality of dependencies then it's the wrong type. I usually stay away from gems that provide syntactic sugar or provide an alternative to ruby / rails defaults. If I'm using 5% of some library, I'd rather write it myself and only address the 5% that I need. If it's not maintained, I stay away. Etc.
A little discipline on what gems get included would go a long way to solving the perceived problems. Gems are generally designed to solve a much more generic version of the problem you're trying to solve and maybe not even the exact same problem, if you're messing with the innards too much, that gem is probably not a good enough fit.
Less is always better when it comes to dependencies.
Yes but I think the point here is that the environment you choose can make it hard or easy to avoid dependencies.
I totally agree on "less is better with dependencies" but there are clearly many programmers who don't want to "reinvent the wheel" and always use libraries first. And a lot of times, the programmers willing to piece together lots of different community libraries will be a lot faster initially. They're just incurring technical debt.
If you're part of a team of these programmers, you might not actually be able to convince everyone that "less is always better" with dependencies.
One time, I went to the store and bought a bike wheel. But it didn't come with a little strip to protect the tube from the spoke nipples. So, I threw it away and built my own wheel. Actually, I first built metal-refining plant, and then I built my own wheel.
Oh wait, I died before I built the wheel, but at least i didn't incur any 'technical debt'. If I had to buy a separate piece of rim tape, my wheel just wouldn't have been right! It may have given me years of enjoyment, but who cares? Those other wheel makers did it wrong!
(Oh, and last time I checked, 'technical debt' didn't mean, "I didn't get to write a research paper about the internal string representation." But that was a long time ago...)
I bought a wheel from a bike store without knowing anything about bike wheels or bike stores. The wheel collapsed on me 3 weeks later, I fell into traffic and a car ran over me, crushing my chest. I'm now rotting in a graveyard paying back my 'technical debt'.
Ok that's just a story. Here's an anecdote:
I spent the last 3 weeks, which I could have spent doing things far more interesting and productive, rebuilding Perl modules because so many perl programmers couldn't be fucked to write portable code and our dependencies appear to number 300 or more. (Nobody actually kept track, of course)
For example, somebody's script, somewhere, uses Array::Compare. It's a tiny module that compares arrays (length, index of different elements, etc.), which is like a first year computer science project. Version 1.18 is 500 lines (including docs+comments) and has one dependency: Carp, a core module. Version 2.01 of Array::Compare is 465 lines (including docs+comments) and requires Moose which itself has 23 other dependencies many of which are not core modules and some require compilation. If Array::Compare was part of a standard library, one that ships with the standard Perl distribution, this wouldn't have been a problem. It wouldn't use Moose unless Moose was also part of the core dist, at which point it wouldn't be a problem unless maybe I was compiling everything from sratch, which I'm not.
So I have to downrev that module and use the old version, or else introduce another new significant dependency (which did not compile successfully on my first couple attempts) or else track down the script and eliminate the dependency and check to see if the author is in punching distance.
That's just one example. There were also mismatched compiler flags, modules that didn't install on some days because tests required connections to webservers that were returning 500 errors-- all the kinds of minor problems that aren't that big of a deal when you are one developer installing only the modules you need to solve your immediate problem, but turn into a huge mess when there are 300+ of them.
That's technical debt coming due. (And I'm certainly not paying back all of it, I'm not compiling Perl from scratch I'm not manually finding every scrap of perl code on our systems to see which modules they still really need, etc.)
If you're an active refactorer, that's a good thing. It means those crufty APIs that you had to hack around, they get better, and you can clean up your code.
But if you're a "write it and forget about it" kind of person, that's going to sound like extra work to you.
IMHO there is nothing like the refactoring tools in eclipse, and the code completion. Say what you want about the verbosity of Java, man is it nice cranking out 200 lines of code in 2 minutes with eclipse.
And extract method / variable are just fantastic.
.joe
http://www.jetbrains.com/idea/index.html
I even prefer Netbeans to Eclipse.
There are 3 fully featured Java IDE's. You have only tried one of them.
I became very spoiled by Visual Studio's ability to let you explore libraries via intellisence. Netbeans is the only Ruby IDE I've found that comes close to letting you do that.
If you're not working on a complex codebase, it is total overkill though.
is usually quite sufficient for purpose if you have a reasonable idea what's going on in your codebase (and if you don't, you probably don't want to be renaming things yet).
Doesn't mean a real refactoring tool such as the ones people've been playing with for http://padre.perlide.org/ wouldn't be better, but it's amazing how well a simple brute force replace works on a reasonably tested codebase.
But I take it from your third paragraph that you agree with the OP and myself that IDEs with good refactoring tools are a good thing.
Of course, Ruby being dynamically typed makes it impossible to have most of the refactorings that statically typed languages take for granted, but that's a separate topic.
Look at it this way: you might think you are productive with Emacs/TextMate/vi, but you would be even more productive with an IDE of the Eclipse or IDEA caliber.
An IDE is nevery mandatory, it just makes you more productive.
Most of the refactoring tools that Java IDEs have originated in Smalltalk, an extremely dynamic language.
For more details and examples:
http://beust.com/weblog/2006/10/01/dynamic-language-refactor...
There's of course a limitation on this. To quote the paper: "The major drawback to this style of refac- toring is that the analysis is only as good as your test suite. If there are pieces of code that are not executed, they will never be analyzed, and the refactoring will not be completed for that par- ticular section of code. "
The biggest difference there is that Ruby programmers frequently don't think twice about exposing their object's state to anyone and everyone.
It should be one line of code for each attribute in Java "public SomeClass foo;" if we're going for the same semantics as the Ruby example(admittedly, if you don't want to expose that it's a field, that's not quite as easy in Java, although really, there's no reason to not just do this:
private SomeClass foo_; // I don't know if Java allows methods with the same name as fields.
public SomeClass foo () { return foo; }
public SomeClass foo (SomeClass aFoo) { foo = aFoo; }
Yes, that's not what most Java programmers would write, and it's not too concise either, but it's not nearly as bad as you suggest.I prefer the Ruby. But, frankly, Ruby has too much boilerplate for attributes, too. I have to explicitly add attribute handling code in initialize? Why?
No, thanks, I'll use Perl 6:
has $.foo is rw;
There you go. An lvalue accessor method for an attribute, and the ability to set it in the constructor with the named argument ":foo(someValue)". And, if I want to add a type constraint, it's a simple matter of doing this instead: has Int $.foo is rw;
Or even this if I only want even numbers: has Int $.foo where { $_ % 2 } is rw;
If I really want to, I can manually implement my own constructor, but often, I won't need to.If I want the attribute to be private, I just replace "$.foo" with "$!foo" and it's only accessible within the class.
CLOS also makes this very simple, you just add an entry in the slots list of the class like so:
(foo :accessor foo :initarg :foo)
You can also do type-checking by adding ":type some-type-specifier", specify that it's either a class or instance(the default) slot with ":allocation class" or ":allocation instance", use :reader or :writer instead of :accessor to only generate a reader or a writer accessor or :initform to give the slot a default value.
Ruby is at a nice place on the accessor definition boilerplate continuum, but it's nowhere near the bottom.
'Attributes' are just methods like any other. What code do you think you need in initialize?
(Encouraging people to think of some methods as 'attributes' was a mistake, but also now a long-lost battle. It breaks the concept of the "messages only" Ruby object model, even though that's exactly what's happening. )
Suppose I have this class(if it doesn't exactly work, sorry, I'm not fluent in Ruby):
class Complex
attr_accessor :re, :im
def initialize(aRe, anIm)
re = aRe
im = anIm
end
end
In Perl 6, I get almost the same thing(with explicit attributes as if I had done "def initialize( initargs ); re = initargs[:re]; im = initargs[:im];end" for the initialize method) with this: class Complex {
has ($.re is rw, $.im is rw);
}
If I want to do as in the actual Ruby example, I just add this: method new ($aRe, $anIm) {
self.bless(*, :re($aRe), :im($anIm));
}Sure, but that's true of all instance variables in Ruby. That there is a method of the same name as an instance variable is an implement choice; it shouldn't make things magical. (I don't like the idea that a public API reveals the implementation, so I don't like to encourage this automagic tying of method names and instance variables. But clearly many like to think of Ruby as a language with "public properties", perhaps because of wanting it to be like other languages they are more used to.)
If you want smarter attribute accessor code, perhaps fattr would help:
http://github.com/ahoward/fattr
" but sometimes you don't want an externally mutable attribute."This is my point. Why would you think of Ruby as having "externally mutable attributes"? There is private data (instance vars) and code to respond to messages, which may or not alter instance data. No data are public by default (barring the usual metaprogramming hooks to get around that). But an idiom was promoted to encourage people to think of a coincidence of method name and instance var name as being "attributes". Later, people have to unlearn stuff in order to properly grok how Ruby works.
When I refer to an externally mutable attribute, I am not making any statements about the details of the storage or interface of the "attribute". I am referring to any bit of information that can be retrieved somehow and modified somehow. That might be implemented by performing some operation on some other attribute, it might be an actual physical instance variable, it might just be a variable closed over by the accessors, it might be something else. What matters is that objects of the class present some kind of interface that allows setting a value for it and retrieving a previously set value.
With that said, on the main body of my reply:
"Sure, but that's true of all instance variables in Ruby."
Right. The question is: should that necessarily be the case? If you're going to have a way to auto-generate methods that provide an externally mutable attribute(see above for working definition of this term) as Ruby does, why not provide at least the option of auto-generating the constructor that sets those attributes, with the option of overriding that if you don't want that behavior?
'Why would you think of Ruby as having "externally mutable attributes"?' Languages don't have "externally mutable attributes", particular interfaces within those languages do. Any Ruby class which uses attr_accessor does. Similarly, any Perl 6 class that contains "has $.foo is rw" and doesn't override the generated foo methods does. Similarly, any Java class with either a public field or a private field with corresponding getFoo and setFoo methods does.
"There is private data (instance vars) and code to respond to messages, which may or not alter instance data."
The private data is part of the implementation. The implementation is not part of what I call "externally mutable attributes". The messages may or not alter instance data, but whether they modify instance data is not part of the interface of an externally mutable attribute either. An externally mutable attribute could look up a page on a website, and send a POST request to modify it upon setting. That's a bad idea when that's not the express purpose of the attribute, but it's one way of implementing externally mutable attributes that doesn't have to modify any instance data.
"No data are public by default (barring the usual metaprogramming hooks to get around that)."
If you use attr_accessor, then yes, there is some public data. Given that I was responding to a post about comparing the Ruby way to create an instance variable and read and write accessors to the Java way, it makes sense to compare the Ruby way to other languages, no? By the way, if you don't like data defaulting to public(me, either), you might be happy to know that Perl 6 has an equally convenient way of creating private attributes(they're not even available to subclasses): just replace "$.foo" with "$!foo".
'But an idiom was promoted to encourage people to think of a coincidence of method name and instance var name as being "attributes".' I agree that an instance-var named @foo and accessor methods named foo and foo= do not necessarily comprise an externally mutable attribute "foo". However, two methods foo and foo= or setfoo or whatever you want do, if the foo= method is specified as determining what all future calls of foo before another foo= call returns. foo doesn't even have to return the exact value given to foo=. Its result just has to be determined by the call to foo=.
I suspect our disagreement is largely an artifact of differing definitions. If that's the case, I'm sorry for not sooner clarifying my definition for "externally mutable attribute".
Seems to be the case. Using attr_accessor is no different than defining methods by hand that just happen to have the same name as an instance variable. By one view, having any message that returns a value means the object has public data. I think that's your POV. Still, I think few other people think of all such methods as attributes or accessors; this may be my own skewed view of what I've seen among Rubyists.
I think the attr_* methods grew out of early Rubyists finding themselves writing the same sort of code and decidinf to automate that with some metaprogamming. I don't know why they don't include a means to also set a defaut value, but then fattr may just be a continuation of that process: Use the language to extend the language.
Now, with Ruby(and Rails), 99% of the time, I just grab the latest off Github and roll with it. I need to get better at forking in my changes so that others can share, but that is a self esteem problem of a different kind. Some of the time, I have seen solutions that I would never have though of implemented more gracefully than the brute steel glove I was going for.
As for sachin, to me it looks like now that he has hit success, he is no longer hungry, and just biting the hand that feeds him out of aimlessness... or would this be excentricism? Looks like he updated to say how Rails allowed him to deploy as fast as they did. But still, the post sounds a lot like a duke crossing a river by running atop the heads of his subjects and then complaining when his coat tail and shoes got wet when he gets to the other side.
I also don't see how this problem is specific to rails. Any project where you choose a ton of 3rd party libraries can be riddled with complexity and upgrade costs.
How this article makes it clear that objective-c, java, or ... is more maintainable or easier to work with, is beyond me.
I might be making a bit of a stretch here, but it sounds like Posterous might be complex software. Maybe the iPhone project he was working on did not have as much business logic and 3rd party libraries involved.
That's why there is so much fragmentation in ruby gems. When one gem fails to do something, a developer thinks, "well I can just do this myself and do it better!"
And do you end up with 10 gems that all do roughly the same thing, but not perfectly, each designed by and for a different person.
It's a very easy problem to solve though- build fewer dependencies on thinly supported libraries. That applies to all languages.
They offer a perfectly good hypothesis, actually: Obj-C and Java are older and more mature.
That has obvious drawbacks: It would be impossible to redesign Java, now, to the extent that Rails 3 just got redesigned. But, though certain Java libraries may suck, you can rest assured that they will suck in exactly the same way in five years. Not so with Rails. The platform moves, and you have to move as well.
Another obvious point is that Rails (a) just went through a big version change, which just happens to coincide with a push toward Ruby 1.9; that double-whammy is bound to produce some yelps of pain; and (b) Rails is slowly maturing as well. Sooner or later Rails will be as mature as Java is today. How will we know when that has happened? When occasional gentle rants like this one turn into numerous, big, influential rants about the prospect of breaking backward compatibility; that will be a sign that the cost of change outweighs the benefit. And then it will be time for a new framework to inherit the role of young upstart...
It's really hard to find functionality in the "hammer,screwdriver" size - more often than not I get a whole tool set with all sorts of things I'll never need, and end up having to devote more time figuring out how it all "hangs together" with the rest of the system.
It becomes a matter of whether you end up piecing your system together block by block or chiseling away to your end product.
In short: in ruby community, libraries are starting points, not black boxes.
For those of us who prefer to avoid thick APIs yet appreciate sinatra's dsl, padrino looks very promising.
I first used rails very early on, late 2003 no... maybe it was early 2004. Anyway I didn't like it then but it did save my ass in getting my college project done.
After all, they brought the merb devs into Rails core and radically re-architected the whole framework to support modularity and customization down to a very low level. From the extraction of ActiveModel, to the inclusion of Thor for generators, the decoupling of Prototype and an ambitious attempt at unobtrustive javascript, and the declaration of a publicly support API upon which even the framework itself is built, Rails 3 the biggest overhaul Rails has ever had.
Have they succeeded in making it truly modular? I dunno, it's not released yet for one thing, but to say it's a crap shoot is a bit premature IMO.
The result of these assumptions is an architecture that has a certain elegance, but is impossibly complex, where writing good modules means having tons of domain knowledge of the framework itself which does not transfer to any other form of web development.
This approach makes a viable framework for a lot of low-budget projects (ie. clients with $5000 and a ridiculous laundry-list of general components), but compared to Rails, Django or other frameworks that focus on the basic building blocks that apply to the majority of web applications the results are horribly baroque.
Bah. There's a price to pay for shiny things, eh?
Adequate culture can compensate for lack of fundamental knowledge, but Node doesn't have "The Node Way" (yet.) DC taught us what to avoid, but we don't have a strong body of blessed examples or expressed philosophy.
I have absolutely no problem with this post, and why should it be PR ? It's not like he's speaking on behalf of the marketing department, just another hacker that produces code for a living.
It's interesting actually that you could probably replace 'rails' and 'gems' with 'drupal' and 'modules' and still end up with a valid piece.
I agree that you could replace rails with drupal. Either way you'd be comparing an open source solution with a corporate backed framework.
That seems about right to me. The trunk and the major branches of the tree are generally solid. Minor branches and the leaves of the tree, not so much. I think the pace of the community creates a lot of leaves and minor branches. Anything really important will mature into a major branch and become solid.
Seems like you're trying to pick a fight here.
I think you're focusing too much on the negative aspects of there being a lot of plugins and gems out there that are maybe not maintained that well. I run into the same issues all the time, and a lot of times I end up having to patch other people's code or roll my own solutions. But I'd rather there be a lot of someone good choices out there that are written in ruby and save me a ton of time than relying on a company to provide perfect solutions when they feel like it.
Your headline was also a little sensationalist and considering how us rubyists love to preach about the joy of coding in ruby, a little on the trollish side. You can't say that coding in objective-c is as enjoyable as coding in ruby.
Anyways, I hope that explains my reaction a little better.
Really? I thought I was allowed to voice my opinions on my blog.
Can you call something "trolling" if it's on your own blog?
If I want something in Ruby, I throw my chips in with some guy on GitHub (who was nice enough to pack it as a gem), usually choose based on whether it was good enough to at least come with half-decent documentation.
I wouldn't necessarily agree that JS or non-Apple Obj-C libraries are all that much better than the Rails community effort though.
I'm not entirely sure why. It can't be just the fact that nowadays web programming is often an entry-level job, so often you have to wade through the fallout of "My first Lightbox". Maybe it's the idea of packaging something you just whipped up for a project for public consumption.
>Rails needs an approval process for Gems
I have a solution: set up a gem repo that does just that, and see if people like it. Gems are distributed by nature, anyone can host a repository.
Though I'd say the utter lack of such a repository's existence (as far as I'm aware) is a sign there's little demand for one.
(j/k :) )