Another newbie question

Steven D'Aprano steve at REMOVETHIScyber.com.au
Sun Dec 11 01:08:34 EST 2005


On Sat, 10 Dec 2005 15:46:35 +0000, Antoon Pardon wrote:

>> Do you lie awake at nights worrying that in Python 2.6 sys.stdout will be
>> renamed to sys.standard_output, and that it will no longer have a write()
>> method? According to the "law" of Demeter, you should, and the writers of
>> the sys module should have abstracted the fact that stdout is a file away
>> by providing a sys.write_to_stdout() function.
> 
> I find this a strange interpretation.
> 
> sys is a module, not an instance. Sure you can use the same notation
> and there are similarities but I think the differences are more
> important here.

The fact that sys is a module and not a class is a red herring. If the
"Law" of Demeter makes sense for classes, it makes just as much sense for
modules as well -- it is about reducing coupling between pieces of code,
not something specific to classes. 

The point of the "Law" of Demeter is to protect against changes in objects
more than one step away from the caller. You have some code that wants to
write to stdout, which you get from the sys module -- that puts sys one
step away, so you are allowed to rely on the published interface to sys,
but not anything further away than that: according to the so-called "law",
you shouldn't/mustn't rely on things more than one step away from the
calling code.

One dot good, two dots bad.

Assuming that stdout will always have a write() method is "bad" because it
couples your code to a particular implementation of stdout: it assumes
that it will always be a file-like object with a write method. What if the
maintainer of sys decides to change it?

Arguing that "this will never happen, it would break too much code" is
*not* good enough, not for the Law of Demeter zealots -- they will argue
that the only acceptable way to code is to create an interface to the
stdout object one level away from the calling code. Instead of calling
sys.stdout.write() (unsafe, what if the stdout object changes?) you must
use something like sys.write_to_stdout() (only one level away).

The fact that people can and do break the "Law" of Demeter all the time,
with no harmful effects, shows that it is no law at all. It is a
*guideline*, and as a guideline I've heard worse ideas than "keep your
options open". That's what it really boils down to: if you specify an
interface of helper functions, you can change your implementation, at the
expense of doing a lot extra work now just in case you will need it later.
But that's not a virtue in and of itself -- it is only good if you
actually intend to change your implementation, or at least think you might
want to some day, and then only if the work needed to write your
boilerplate is less than the work needed to adapt to the changed
implementation.

[snip]

>> But I *want* other classes to poke around inside my implementation.
>> That's a virtue, not a vice. My API says:
>>
>> "In addition to the full set of methods which operate on the coordinate
>> as a whole, you can operate on the individual ordinates via instance.x
>> and instance.y which are floats."
> 
> Yikes. I would never do that. Doing so would tie my code unnecesary
> close to yours and would make it too difficult to change to an other
> class with a different implementation like one using tuples or lists
> instead of a seperate x and y instances.

Do you really think that my class and some other class written by
another person will have the same API? If you change from my class to
another class, the chances are that the interfaces will be different
unless the second class writer deliberately emulated my class interface.

To class users, there is *no* difference in consequences between me
changing my published API by removing named attributes x and y from my
class, and me changing my published API by removing or changing methods.


>> Your API says:
>>
>> "In addition to the full set of methods which operate on the coordinate
>> as a whole, you can operate on the individual ordinates via methods
>> add_x, add_y, mult_x, mult_y, sub_x, sub_y, rsub_x, rsub_y, div_x,
>> div_y, rdiv_x, rdiv_y, exp_x, exp_y, rexp_x, rexp_y...; the APIs of
>> these methods are: ... "
> 
> Who in heavens name would need those? Maybe there is no x or y because
> the implementation uses a list or a tuple, maybe the implementation uses
> polar coordinates because that is more usefull for the application it
> was planned for.

And maybe it isn't a Coordinate class at all, hmmm?

An ordinary, Cartesian, real-valued Coordinate is a pair of ordinates, an
X and Y ordinates. That's what it *is* -- a coordinate class without X and
Y ordinates isn't a coordinate class, regardless of how they are
implemented (via properties, named attributes, or a whole bucketful of
helper functions).

I'm not interested in polar coordinates, lists, dicts, red-black trees,
complex-valued infinite dimensional vectors, byte streams or any other
class. If I wanted one of those, I'd write *that* class and I wouldn't
need to access the X and Y ordinates. But since I want a two dimensional
Cartesian coordinate class, I must have *some* way of accessing the X and
Y ordinates, otherwise it isn't a two dimensional Cartesian coordinate
class.

The question is, MUST I write a whole pile of boilerplate functions?
According to the Law of Demeter, I must, just in case somebody changes the
definition of float and suddenly code like value = 2*pt.y stops working.
In my opinion, that's taking abstraction to ridiculous extremes.

I'm not saying that there is never any reason to write getters and
setters or similar boilerplate. If I suspect (or fear) that the
implementation is going to change after my API is nailed down, then it is
a good idea to write an intermediate public level so I can change the
internal implementation at a later date. That good practice. Bad practice
is to pretend that the boilerplate code making that intermediate level is
cost-free, and that therefore one must always use it.

[snip]

>> My class is written, tested and complete before you've even decided on
>> your API. And you don't even really get the benefit of abstraction: I
>> have two public attributes (x and y) that I can't change without
>> breaking other people's code, you've got sixteen-plus methods that you
>> can't change without breaking other people's code.
> 
> No he would have none.

Do you really mean to tell me that the class writer can change their
public interface without breaking code?



-- 
Steven.




More information about the Python-list mailing list