When reading or refactoring code, it is important to be able to easily find all callers of a method or to go to the implementation of a method. In static languages, IDEs make this reasonably easy. However, if reflection is being used, they may still fail us. It is simply easier to parse a static language to know the types involved and find the right ones to tell us about. In dynamic languages that is much harder.
In a dynamic language, a key way to find all references to an object’s method call is “Find in Files”. This means what we choose to name things may make it harder or easier to change later. It may also make it harder to discover who is calling the method – or even that the method exists.
A unique name
In order to refactor a uniquely named method on a class
- search for the method name as a string
As we know it is unique, this will work. In fact, you might be able to run a simple find and replace in files instead of looking at each result individually.
This scenario however is unlikely. At least it is unlikely that we emphatically know that a given method name is unique.
A more likely scenario
In order to refactor a descriptively named method such as full_price_for_tour on a Tour class
- search for the method name as a string
- in each search result – check all references to the method name to see if they are in fact using a Tour object
- if this is a Tour object call, rename the method call to the new name.
This is more work as we need to look at each result. Hopefully with a descriptively named method the number of usages will not be too high. Even if the number of usages is high, hopefully all usages of the name will in fact be on the Tour class.
However, we do need to look at each result as this process is potentially error prone. There could be other method definitions using the same name that we need to NOT rename. Hopefully there are tests that will tell us if we fail. And hopefully the number of callers to change isn’t too high due to the descriptiveness of the method so the changes to the callers is clear.
Sometimes the results are less simple
Now imagine repeating the above exercise, but now the name of the method to refactor is name. Suddenly we may have a huge number of hits with many classes exposing a name method for their instances. Now the ratio of search result hits that are to be updated is no longer almost 100%. The probability of error is much higher – the greater the number of hits, the more actual choices that need to be made.
An IDE may help
Immediately the IDE lovers will point out that, using an IDE is the solution. And yes, it could help. But IDEs for dynamic languages are generally slow and CPU/memory intensive as the problem is a hard one to solve. And they won’t always be correct. So you will still need to employ strategies using a human mind.
Naming things more explicitly can help
A more useful model – even if you’re using an IDE – is to name things descriptively, without being silly. Things like tour_name and operator_name instead of name may help someone discover where / how a method is being used more easily.
Designing code to only expose a given interface can help
Building cohesive units of code that only interact through a well defined interface makes changing behind the interface a lot easier. However it still doesn’t discount developers reaching in behind the curtain and using internals that they should not. So you will still need to check. Hopefully code that breaks the design like this will be caught before it gets merged into the mainline, but you never truly know without looking.
Reducing scope where possible can help
Knowing the scope of access of the thing you need to change can make changing it easier as it reduces the area you need to look in. For example, if something is a private method then we know that as long as all usages in this class are updated, then we are completely free to change it. Unless someone has done a private_send from somewhere else… Or we are mixing in a module that uses the private method from there… Both of which I’d like to think no one would be that silly to do.
Testing can help
Obviously having a comprehensive test suite that calls all implemented permutations of the usage of the method will help to validate a change. It will hopefully help us discover when we have missed updating callers. Or when we’ve accidentally changed a caller that shouldn’t be changed. However if there are name clashes for the new name, it is plausible that it might not give you the feedback that we expect so it isn’t a silver bullet if you aren’t naming things well.
Think about naming. Think about discoverability. Is there something that will make changing this easier in the future?
Think about the cost of making discoverability harder. Be aware of the implications of a naming choice. Is there something that can be done to make it easier to safely refactor away from this choice later?
Can we make things worse? Discoverability is a design choice to make easier or harder.