So many problems in legacy or brownfield applications stem from invalid state being dragged around throughout the whole application and causing a bug somewhere far away from its origin or just dying a silent death. I’d like to introduce you to a method to handle invalid state in new code without going down the dreaded legacy rabbit hole and needing to refactor half the application.
When you start out working in legacy, you quickly run into the mistake of making legacy code stricter to combat invalid state.
For example, you write a beautiful new piece of code that uses the legacy Contact model and in particular its setId method. This method takes any value and that value might be persisted as-is.
class Contact
{
public function setId($id)
{
$this->id = $id;
}
}
When you are working with the legacy Contact class you will be tempted to do some boy scouting and add type hints to that setId method. You think this will be fine because your code is always passing an int nicely.
final class Contact
{
public function setId(int $id): void
{
$this->id = $id;
}
}
However, this will lead to a world of pain! Since you are working on legacy code, you can be sure that some code somewhere is passing something other than an int to this method.
After deploying this code and needing to roll it back because you broke half the application, you are mad at the Previous Developers™ for writing code that does not cast a POST variable to an int. The first thing you’ll want to do is update all usage of setId. But if the application is old enough and big enough then this will be problematic as well since you now need to check and/or refactor the 101 places where setId is called.
Since updating all usages is not a realistic option you can swallow your pride and leave setId as is and make peace that your new shiny code uses this damned method.
Or if you are able to just leave it like that, you might want to add a second method to the Contact class, securelySetId.
final class Contact
{
public function setId($id)
{
$this->id = $id;
}
public function securelySetId(int $id): void
{
$this->id = $id;
}
}
This way your new shiny code makes use of a secure, nicely type hinted securelySetId and the other 101 use cases can keep using setId like before.
Grandfathering
A grandfather clause (or grandfather policy or grandfathering) is a provision in which an old rule continues to apply to some existing situations while a new rule will apply to all future cases. Those exempt from the new rule are said to have grandfather rights or acquired rights or to have been grandfathered in. – Wikipedia
If we look at the definition of grandfathering we can say the legacy setId is the grandfather clause and the 101 use cases that still use setId would be grandfathered in because they are exempt from using securelySetId.
The good thing is that we can now start refactoring the 101 grandfathered in use cases one by one (vertical refactoring), instead of refactoring all 101 use cases at once (horizontal refactoring) if we used the initial type hinted setId we created.
To make sure that every developer knows that setId is a grandfather clause from now on I would also strongly suggest marking it as deprecated.
final class Contact
{
/**
* @deprecated
*/
public function setId($id)
{
$this->id = $id;
}
public function securelySetId(int $id): void
{
$this->id = $id;
}
}
Now developers see a hint in their IDE and hopefully not use this method anymore. In addition, if it is possible in your codebase you can rename your grandfather clause method to something like dangerouslySetId and rename the new method to setId. If you do this the new method you want everything to use will already be named the name you will want to eventually have.
Conclusion
This is a very simple example but you can use grandfathering when you make validation on any piece of code stricter and cannot easily update all the use cases that call that piece of code. It will give you the freedom to update all the use cases one by one instead of needing to update them all at once. This makes it so you can keep your focus on working on the slice of code you want to work on instead of needing to start refactoring code that is not related to the thing you are working on at that moment.
Other interesting articles for you
- Getter, Setter, Never?
- Improving code style when working on a legacy code base
- Everything on technical debt
Member discussion