Thursday, July 19, 2012

Privates Done Right

When I started learning Python, I quickly learned that there is no way to officially make a member variable private. There is only a convention that any variable starting with a single or double underscore should be treated as an implementation detail that is likely to change. If you try to access it, however, Python will not stop you.

I think that this is the right approach. Sometimes there are legitimate reasons for accessing a private variable. For example, when testing legacy code, or using a third-party API. When I have a legitimate reason to do so, I don't want to have to go through some laborious process to get at the data, such as reflection.

Of course, with new code, the real solution is to write more modular classes with a single responsibility. This way, there are more public entry points to test classes in isolation.

For example, consider this trivial class that performs a calculation and writes the result to the console:

class Logger(object):
    def write(value):
        print value


class DataDisplayer(object):
    def __init__(self, logger):
        self.__logger = logger


    def display(self, value1, value2):
        value = self.__do_calculation(value1, value2)
        self. __logger.write(value)


    def __do_calculation(value1, value2):
        return value1 * value2

In order to test the __do_calculation method, we have a few options. We can call the pseudo-private method directly. Or we can test it through it's public interface, display. This isn't desirable, though, because it has the side effect of writing a value to the screen.


The ideal solution is to the pull the responsibility of __do_calculation into its own class so we can test that in isolation:



class  DataDisplayer (object):
    def __init__(self, logger, calculator):
        self.__logger = logger
        self.__calculator = calculator


    def display(self, value1, value2):
        value = calculator.do_calculation(value1, value2)
        self.__logger.write(value)


class Calculator(object):
    def do_calculation(value1, value2):
        return value1 * value2


Now we have a public interface to test do_calculation in isolation.


In other words, when we write our tests and classes correctly, we shouldn't have to access private variables very often. For those rare cases when we need to, though, it's kind of nice when the language doesn't try to prevent us.