Private vs Protected Methods: The Debate That Never Ends
As a new generation of PHP web application frameworks start establishing beach heads in preparation for an all out war for mindshare, I’ve been contemplating some of the key changes we’re seeing emerge that may gain traction over time. Today, I just thought to share my thoughts about private methods vs protected methods. Something that has impacted on Doctrine 2 and Symfony 2. And every other piece of PHP code since PHP 4 came along.
Doctrine and Symfony have adopted a practice whereby all methods are flagged as private unless there are compelling reasons to the contrary (i.e. they are necessarily part of a public API, or form part of an abstract implementation). There is an allowance for appealing that a method be switched from private to protected on a case by case basis.
I’m not a big fan of private methods. I’m in a state of persistent internal conflict over them, in fact. At this point in time, I am in favour of protected methods over private methods.
On one hand, there’s the traditional idea that private methods must never be used outside the current class to prevent interaction with and dependencies on highly unstable units of behaviour. This is a common need arising out of refactoring where non-public methods may vanish, move classes, get renamed or find themselves saddled with different behaviour. These non-public methods are inherently unstable. There is no getting away from that. While this is perfectly normal during development, it can become an irritating concept downstream if you are depending on such implementation details in your own work.
On the other hand, non-public methods contain the meat of any class – the implementation specifics. Using Inheritance, you can extend classes containing this implementation code at very little cost if they are carried by protected methods. This usually tends to make life easier, promotes source code reuse, allows for bug and security fixes ahead of schedule (assuming there it’s not one of those lost bugs that will never be fixed), dependence on existing implementation facets, etc. This all does, however, carry the risk of creating a lot of brittle code that may break at the next update due to the effects of refactoring.
These two strands of thought have been in combat for a long time in many programming languages. It’s no different in PHP. Many developers are likely under the delusion that protected methods constitute a protected API, i.e. that there is a rule that protected methods must retain backwards compatibility across updates. There is no such rule. There is, however, a form of peer pressure to conform to these delusions. Ask any set of developers about backwards compatibility and there’s probably a fair chance that the majority will insist that it applies to protected methods. Unfortunately, this is in direct violation of the principles behind refactoring. In other words, it’s a delusional belief with no basis in software engineering. Since delusional is a strong term, I’ll refer to it as the misguided status quo. There – that’s surely less offensive.
In my view, this is where PHP developers seem to like throwing themselves off a cliff. Rather than perform refactoring, they would rather preserve backwards compatibility on methods that are not even unit tested. All for a group of people down the line who are unwilling to accept that they took a risk in depending on them. If that sounds a wee bit bitter, it is. I’ve seen people do some crazy weird shit to avoid refactoring or other key changes that would make my life so much easier. Some circumstances push me into creating ridiculous brittle workarounds instead.
So we not only have two varying views, we also have two extreme solutions: mark everything private or actively discourage refactoring even to the extent of inventing stupid mind boggling excuses according to some unwritten rule book maintained by the anonymous hivemind (that’s the PHP one, not the shit crazy hacker one). Me? I’m a fan of the middle path: use protected methods unless otherwise required by design, refactor at will and tell anyone who complains about downstream brittleness that I don’t unit test protected methods and that it is fundamentally necessary to change them at times. Most developers are intelligent enough to understand that they took a risk. We can both have our cake and eat it without undue hostility by being a little more upfront.
So why did I finally decide that private methods are not worth enforcing? Like many similar beliefs, it crept up on me over time. Back in the day, when I was programming in Java as a young kid with no sense of independent thought, I would have taken my blind puritanical righteousness to extremes and pounced on anyone who questioned the party line about private methods. These days, I program in PHP, Ruby, and Python. I try to avoid Perl. None of these have enforceable private methods. Perl doesn’t have private visibility at all without hacks. Ruby and Python have easily bypassed implementations (i.e. they are more of a low fence with a “No Trespassing” sign you can hop over as needed – at the risk of being prosecuted and fined). PHP stands out due to that evil conniving Java influence that is overquoted. Too much in PHP is already “influenced” or “inspired” by a language I dropped like a hot coal over a decade ago. I can’t wait until we get over Java as the PHP deity of choice…
Private methods represent more than a “No Trespassing” sign in PHP/Java. In PHP, they are enforceable in theory, i.e. you just cannot call or invoke them. End of story. In reality, this is hogwash. As our colleagues in Ruby, Python, Perl, and all the other programming languages without private visibility enforcement already know – you cannot impose a practice. There will always be the vocal group that tell you to fuck off while they blissfully inherit classes and reuse private methods anyway. It’s easy to do. All you need is a text editor and the keyboard keys CTRL, C and V. Or Reflection. Or sed and grep. Or git. Private visibility enforcement is an illusion (or delusion?).
Once you reach into the dark corners of your mind and realise that private methods and properties are just another pointless easy-to-overcome obstacle, the real problem remains entirely unchanged and unaddressed. How to manage the expectations of downstream users who can and will extend and reuse your code in ways you never expected?
All the band aids in the world won’t answer that, and my response would be to be clearly honest and open. Educate downstream users about your need for refactoring and then make it clear that they proceed at their own damn risk if relying on non-public methods! Then I, you, and the rest of the PHP community can hack, inherit, and gleefully ignore best practices when it suits us without worrying about private method blockades. Not that that would stop us anyway…
Anyway, there’s my train of thought for today. Private methods are nice in theory but unenforceable in practice. If they really were enforceable, those of us who keep creating excuses to fiddle around with core library and framework code might blow a gasket or three out of frustration if the Nanny State mentality really took hold.