Am I Doing it Wrong?
gist.github.com
gist.github.com
I suspect the 'senior developer' has similar feelings about OOP to my own; he just sounds a lot more forceful about them. I don't think either position is necessarily 'right', but he's probably not a good person for you to work with.
I think the OPs code looks clean and follows OOP.
I'm starting to get a pretty good feel for it, but what are the criteria that you use to determine if a class should be stateless or not?
I find stateless code easier to reason about.
module Foo
def self.some_stateless_method
...
end
end
Foo.some_stateless_method
class MyController
include Foo
def index
some_stateless_method
end
end
and so forth.People think of modules as being mixins, but they're actually just function namespaces--which you can happen to bind into class contexts via include/extend.
I tend to use class methods where:
a.) The class exists, because you want state, and
b.) The function operates principally on, or is only useful in, the context of that class, but
c.) The function isn't bound to the object's state.
That last requirement is fuzzy, since the call semantics are different: self.class.foo is awkward, so I'll often write instance methods which don't actually depend on the state, just taking advantage of the local namespace.
I am writing a class to represent a car. I need to know if the cars doors are opened or closed. The `open` or `closed` attribute is the state of the car, so it makes sense that the Car object would have that state.
Now compare that to the use of `LanguageDetector` from the example:
LanguageDetector.new(@comment).set_language
Presumably this object is keeping a reference to the comment for it's internal state. When I'm dealing with an object, I like to say in my head "language detector, can you tell me the language for some text?". Nothing in my question gave the text a 1:1 relationship with the language detector. Since my question did not identify a relationship, I would not make the comment a part of the LanguageDetector object. I'd rather write something like this: detector = LanguageDetector.new
language = detector.detect_language(@comment)
@comment.language = language
What might the LanguageDetector actually want to have as it's state? I think maybe things like encodings, or particular languages it supports. The question I ask is "language detector, what encodings or languages do you support?", and the relationship becomes clear. For example: detector = LanguageDetector.new(:encodings => [...], :languages => [...])
# etc
I hope that makes sense, and that I don't sound too crazy (talking to myself and whatnot).To add some real value, by this I mean that I've run into this crap a lot. In Java, sometimes I want the ability to have a function which returns other functions based on an input. This is doable in a variety of languages other than Java.
In Java, I didn't call it such, but I ended up writing a FactoryFactory. (I am not proud.) I had one layer of abstraction to produce Foo objects with a certain configuration, and a layer above that to parameterize the creation of a Foo-creator. sigh. Scar tissue indeed.
1. Testability. Isolating these bits of application logic and testing them on their own has proven less prone to issues than including it all in, say, controller methods or helpers, which get messy over time.
2. Swappability. Okay, sure, you may never need a second kind of CommentPoster, but you never know. Keeping things like this stateful and properly abstracted out saves me plenty of time as the app's requirements get more complex.
It's not exactly premature optimization if I always follow this pattern and know exactly how it will work. (Sometimes it's just an unused optimization.) To me, there is very little added complexity, and if anything it makes it a bit easier in the long run.
2. Swapping implementations is easier: would you rather pass a function, or an object with a single function? This ties back in to testing: it's easier to create a test variant of a function than it is to create a new class.
Don't get me wrong: state is great, and I use it all over the place. I generally avoid it, though, unless the state is needed, simpler to understand, or more concise.
Keep things in one place to start and then if the need for sharing or abstracting comes up I the future, refactor the code.
This approach should be reversed if you're shipping a library for use by third parties (abstract and isolate everything you can).
I'm coming to believe more and more strongly that there should be a difference in the way writing library code, and writing code that uses libraries, is approached. (This applies to the use of design patterns, abstractions, and various other "best practices.")
In many cases, there's a big difference in both programmer quality and the amount of time spent on each line of code between library code and application code. And worse, most of the time, when programmers come across good code, it's library code, not application code. This leads programmers to think that library code--with its multiple abstractions and design pattern complexity--is the right way to do things, and so then then apply this approach, quite inappropriately, to their application code.
"Who", not "whom". There's no reason to use "whom" at all any more unless you really know when you should. "Who" is acceptable at all times.
The code seems fine though... ;)
I was unaware. Is this just laziness slipping into common usage or has it always been the case?
Any good counterexamples?
How I would approached it: refactor the logic in FacebookHandler/TwitterHandler, make the Comment object send an event when one is added, make the handlers listen and do their magic. Implement a common interface for grabbing the Tweet/FB post text. Done. Now all your models can post to Twitter/FB with a common interface.
EDIT: For clarity
CommentPoster.new(@comment).post
Looks slightly better to me.While this example is not an abomination, the desire to "clean up" code like this can be indicator of a compulsive over-builder - and that can extract a heavy price down the road. So the sr dev is right to be cautious.
What we need is a new acronym (well, maybe somebody has probably already coined it, but whatever) YPNGTNI: You're Probably Not Going To Need It.
As long as the code is easy to refactor - and in this case, it's a piece of cake - I think you should err on the side of YAGNI.
There need be no real danger of it ever becoming a drudge, for any processes that
are quite mechanical may be turned over to the machine itself.I would have approached it by sending a "new comment" signal when a new comment is inserted in the database, and have the specific logic for posting to Twitter and Facebook elsewhere. I don't know if Rails has support for that, and maybe that's the disease because most Rails codebase I've come across seem to just stick to whatever is built-in instead of thinking outside of the box a little for the sake of better design.
And these four things have different failure and performance characteristics - so maybe we should not try hard to unify them.
CommentPoster.new(@comment).post
In this case, it might be a premature optimization, but I find that classes like this are a lot more testable than controller logic. One thing, though. I would pull lines like this out of CommentPoster and put them back into the controller: LanguageDetector.new(@comment).set_language
SpamChecker.new(@comment).check_spam
CommentMailer.new(@comment).send_mail
This keeps the important steps a bit more mutable.