views:

76

answers:

2

Ok, I'm not trying to start a flame war here, and I know the argument between static and dynamic languages has been hashed out many times, including here. But I have a very practical question that hopefully someone here can shed some light on. Sorry for the length, but this is not a simple question with probably not a simple answer.

Ruby, PHP, and Javascript are pretty popular languages these days, and they have a lot of people that defend them and argue that being dynamically typed doesn't hold the developer back. I'm new to these languages and would like to start using them for bigger projects, but here's a basic refactoring scenario that comes up all the time at work(work == C#), and I'm wondering what the approach would be in Ruby - I chose Ruby because it's OO.

Ok, I'm using Ruby, and I build a Customer object. It has methods for loading/saving/deleting from the database. It is good and people use it. I add more methods for other things and people use it more. I add a method for calculating order history based on some parameters. By now this class is being used all over the system. Then, one day I decide to change the parameters on the GetOrderHistory method. So I:

  • add the new parameters to the method
  • rewrite the code of the method to use the new parameters
  • change the client code I had in mind to pass the new parameters and use this modified method

But now what? I have dozens/hundreds/whoknows how many other places in the system that need to be changed. In a dynamic OO language like Ruby or Javascript, how would I go about this?

Off the top of my head, not knowing Ruby very well, I can think of two dumb answers:

  1. 100% Code coverage. I test the entire app and every time it breaks I see if it's that method and fix it
  2. Find and Replace. I use the text search to look for that method. But I could have other objects with the same method names.

So is there a good answer to this? It seems the IDE would have a hard time. If I had code such as

c = Customer.new

it would be able to figure it out, but what if it's

c= SomeFunctionThatProbablyReturnsACustomerButMightReturnOtherThings()

So what approach would you Ruby experts take in this case?

A: 

This will probably not be the best answer to your question, but I tend to like designing methods to accept a hash to cover future changes.

Example:

def my_method(options = {})
  if options[:name]
    ...
  end
end

I think a lot of the more advanced Ruby folks would look to implementing some sort of Metaprogramming pattern.

Other options may include over-riding the method in a sub-class to perform the functionality you desire.

Or, how about...

def get_order_history(required_param, options = [])

  @required_param = required_param

  if options[:do_something_else]
    result = other_method(options[:do_something_else])
  else
    result = ...
  end

  result      

end

def other_method(something_else)
  ...
end
Brian
Subclassing is a good point. That's one of the options I considered after I posted, but then I thought, 'What if the method really is just changing, like due to a db schema change, and you don't need the old code at all, and everyone needs to use the new method?'
LoveMeSomeCode
+2  A: 

One of the strong arguments you'll hear is that you should write tests beforehand. This, in theory, would show you exactly where does the app need change in case something else changes.

But this is just the top of the iceberg. Ruby is designed with certain guidelines in mind, like short, expressive functions, split of responsibilities in modules, non-repetition of code (DRY), the principle of least surprise, etc; plus a set of recommended practices, like testing first, passing parameters as hash-options, using metaprogramming wisely, etc. I'm sure other dynamic languages do this as well.

If c is not a Customer, then at least I'd expect to act like one. IDEs could look for duck typing, which is more flexible than checking for an instance of a particular class.

Some IDEs (at least Rubymine) look up at conventions too. For example, in Rails applications, Rubymine goes to the schema file and adds the model properties in the database as methods. It also recognizes the associations (has_many, belongs_to, etc.) and dynamically adds the corresponding methods, that Rails generate under the hood.

Now, this pretty much reduces the need for refactoring, at least keeping it to a minimum. But certainly does not solve it. And I don't think it can be solved.

Chubas
good insights, thanks. The duck typing idea is particularly interesting, i'm going to look more into that. Also, in another post here, someone mentioned Flow Analysis, which I think is a more intelligent pre-compile thing that can figure out what the runtime type will.can be. Sounds complicated though...
LoveMeSomeCode
also, not to be argumentative, but the idea of tests bothers me. Not the tests themselves, because I believe all good code should have thorough unit-tests, but the idea of verbosity. A lot of people will argue that dynamic languages are less verbose because you don't need type descriptors, but then when people talk about type-safety they'll mention the need for extra tests, which kind of cancels out the concise code they wrote. just a general observation...
LoveMeSomeCode