Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring with dynamically typed language

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?

like image 721
LoveMeSomeCode Avatar asked Aug 12 '10 13:08

LoveMeSomeCode


2 Answers

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.

like image 117
Chubas Avatar answered Oct 15 '22 16:10

Chubas


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
like image 21
Brian Avatar answered Oct 15 '22 14:10

Brian