39

My school's CS program avoids any mention of object oriented programming, so I've been doing some reading on my own to supplement it -- specifically, Object Oriented Software Construction by Bertrand Meyer.

Meyer makes the point repeatedly that classes should hide as much information about their implementation as possible, which makes sense. In particular, he argues repeatedly that attributes (i.e., static, non-computed properties of classes) and routines (properties of classes that correspond to function/procedure calls) should be indistinguishable from each other.

For example, if a class Person has the attribute age, he asserts that it should be impossible to tell, from the notation, whether Person.age corresponds internally to something like return current_year - self.birth_date or simply return self.age, where self.age has been defined as a constant attribute. This makes sense to me. However, he goes on to claim the following:

The standard client documentation for a class, known as the short form of the class, will be devised so as not to reveal whether a given feature is an attribute or a function (in cases for which it could be either).

i.e., he claims that even the documentation for the class should avoid specifying whether or not a "getter" performs any computation.

This, I don't follow. Isn't the documentation the one place where it would be important to inform users of this distinction? If I were to design a database filled with Person objects, wouldn't it be important to know whether or not Person.age is an expensive call, so I could decide whether or not to implement some sort of cache for it? Have I misunderstood what he's saying, or is he just a particularly extreme example of OOP design philosophy?

  • 1
    Interesting question. I asked about something very similar very recently: How would I design an interface such that it's clear which properties may change their value, and which will remain constant?. And I got a good answer pointing towards documentation, i.e. exactly what Bertrand Meyer appears to argue against. – stakx Aug 26 '13 at 21:34
  • I have not read the book. Does Meyer give any examples of the style of documentation that he recommends? I find it hard to imagine what you described working for any language. – user16764 Aug 26 '13 at 23:09
  • 1
    @PatrickCollins I suggest you read 'execution in the kingdom of nouns' and get behind the concept of verbs and nouns here. Secondly OOP IS NOT about getters and setters, I suggest Alan Kay(inventor of OOP) : programming and scale – AndreasScheinert Aug 27 '13 at 11:55
  • @AndreasScheinert -- are you referring to this? I chuckled at the "all for the want of a horseshoe nail," but it seems to be a rant about the evils of object oriented programming. – Patrick Collins Aug 27 '13 at 15:27
  • 1
    @PatrickCollins yes this: steve-yegge.blogspot.com/2006/03/…! It gives some points to ponder, the other ones are: you should turn your objects into data structures by (ab) using setters. – AndreasScheinert Sep 3 '13 at 12:15

12 Answers 12

58

I don't think Meyer's point is that you shouldn't tell the user when you have an expensive operation. If your function is going to hit the database, or make a request to a webserver, and spend several hours computing, other code is going to need to know that.

But the coder using your class doesn't need to know whether you've implemented:

return currentAge;

or:

return getCurrentYear() - yearBorn;

The performance characteristics between those two approaches is so minimal it shouldn't matter. The coder using your class really shouldn't care which you have. That's meyer's point.

But that's not always the case, for example, suppose you have a size method on a container. That could be implemented:

return size;

or

return end_pointer - start_pointer;

or it could be:

count = 0
for(Node * node = firstNode; node; node = node->next)
{
    count++
}
return count

The difference between the first two really shouldn't matter. But the last one could have serious performance ramifications. That's why the STL, for example, says that .size() is O(1). It doesn't document exactly how the size is calculated, but it does give me the performance characteristics.

So: document performance issues. Don't document implementation details. I don't care how std::sort sorts my stuff, as long as it does so properly and efficiently. Your class also shouldn't document how it calculates things, but if something has an unexpected performance profile, document that.

  • 4
    Moreover: document time/space complexity first, then give an explanation of why a function has those properties. E.g.: // O(n) Traverses the entire user list. – Jon Purdy Aug 27 '13 at 2:40
  • 2
    =( Something as trivial as Python's len fails to do this... (In at least some situations, it is O(n), as we learned in a project in college when I suggested storing the length instead of recalculating it every loop iteration) – Izkata Aug 27 '13 at 12:44
  • @Izkata, curious. Do you remember what structure was O(n)? – Winston Ewert Aug 27 '13 at 14:26
  • @WinstonEwert Unfortunately not. That was 4+ years ago in a Data Mining project, and I'd only suggested it to my friend on a hunch because I'd been working with C in another class.. – Izkata Aug 27 '13 at 15:41
  • 1
    @JonPurdy I would add that in normal business code, it probably doesn't make sense to specify big-O complexity. For example, an O(1) database access is most likely going to be much slower than O(n) in-memory list traversal, so document what matters. But there are certainly cases where documenting complexity is very important (collections or other algorithm-heavy code). – svick Aug 29 '13 at 20:53
16

From an academic or CS purists view, it is of course a failure to describe in the documentation anything about the internals of the implementation of a feature. That's because the user of a class should ideally not make any assumptions about the internal implementation of the class. If the implementation changes, ideally no user won't notice that - the feature creates an abstraction and the internals should kept completely hidden.

However, most real-world programs suffer from Joel Spolsky`s "Law of leaky abstractions", which says

"All non-trivial abstractions, to some degree, are leaky."

That means, it is virtually impossible to create a full black-box abstraction of complex features. And a typical symptom of this are performance issues. So for real world programs, it may become very important which calls are expensive and which are not, and a good documentation should include that information (or it should say where the user of a class is allowed to make assumptions about performance, and where not).

So my advice is: include the information about potential expensive calls if you write docs for a real-world program, and exclude it for a program which you are writing only for educational purposes of your CS course, given that any performance considerations should be kept intentionally out of scope.

  • +1, plus most of the documentation that gets created is for the next programmer to maintain your project, not the next programmer to use it. – jmoreno Sep 1 '13 at 2:04
12

You can write if a given call is expensive or not. Better, use a naming convention like getAge for quick access and loadAge or fetchAge for expensive lookup. You definitely want to inform the user if the method is performing any IO.

Every detail you give in the documentation is like a contract which has to be honored by the class. It should inform about important behavior. Often, you will see complexities indication with big O notation. But you usually want to be short and to the point.

  • 1
    +1 for mentioning that the documentation is as much part of the contract of a class as its interface. – Bart van Ingen Schenau Aug 27 '13 at 16:54
  • I support this. Further in general trying to minimize the need for getters by providing methods with behaviour. – sevenforce Sep 12 '13 at 19:25
9

If I were to design a database filled with Person objects, wouldn't it be important to know whether or not Person.age is an expensive call?

Yes.

This is why I sometimes use Find() functions to indicate that calling it may take awhile. This is more of a convention than anything else. The time it takes for a function or attribute to return makes no difference to the program (though it might to the user), although among programmers there is an expectation that, if it is declared as an attribute, the cost to call it should be low.

In any case, there should be enough information in the code itself to deduce whether something is a function or attribute, so I don't really see the need to say that in the documentation.

  • 4
    +1: that convention is idiomatic in quite a few places. Further, documentation should be done at the interface level - at that point you don't know how Person.Age is implemented. – Telastyn Aug 26 '13 at 15:57
  • @Telastyn: I never thought about documentation in quite this manner; that is, that it should be done at the interface level. It seems obvious now. +1 for that valuable comment. – stakx Aug 26 '13 at 21:40
  • I like this answer a lot. A perfect example of what you describe that performance is not a concern for the program itself would be if Person was an entity retrieved from a RESTful service. GET is inherent but it is not apparent if this will be cheap or expensive. This of course isn't necessarily OOP but the point is the same. – maple_shaft Aug 27 '13 at 0:14
  • +1 for using Get methods over attributes to indicate a more heavy-weight operation. I've seen enough code where developers assume a property is just an accessor and use it multiple times instead of saving the value to a local variable, and thus execute a very complex algorithm more than once. If there is no convention to not implement such properties and the documentation doesn't hint at the complexity, then I wish whoever has to maintain such an application good luck. – enzi Aug 30 '13 at 11:04
  • Where does this convention come from? Thinking of Java I would expect it the other way round: get method being equivalent to an attribute access and so not expensive. – sevenforce Sep 12 '13 at 19:16
3

It's important to note that the first edition of this book was written in 1988, in the early days of OOP. These people were working with more purely object oriented languages that are widely used today. Our most popular OO languages today - C++, C# & Java - have some pretty significant differences from the way that the early, more purely OO, languages worked.

In a language such as C++ & Java, you must distinguish between accessing an attribute and a method call. There's a world of difference between instance.getter_method and instance.getter_method(). One actually gets your value and the other does not.

When working with a more purely OO language, of the Smalltalk or Ruby persuasion (which it appears that the Eiffel language used in this book is), it becomes perfectly valid advice. These languages will implicitly call methods for you. There becomes no difference between instance.attribute and instance.getter_method.

I wouldn't sweat this point or take it too dogmatically. The intent is good - you don't want the users of your class to worry about irrelevant implementation details - but it doesn't translate cleanly to the syntax of many modern languages.

  • 1
    Very important point about considering the year in which the suggestion was made. Nit: Smalltalk and Simula date back to the 60s and 70s, so 88 is hardly "early days". – luser droog Aug 27 '13 at 0:17
2

As a user, you don't need to know how something is implemented.

If performance is an issue, something has to be done inside the class implementation, not around it. Therefore, the correct action is to fix the class implementation or to file a bug to the maintainer.

  • 3
    Is it always the case that a computationally-expensive method is a bug, though? As a trivial example, let's say that I'm concerned with summing up the lengths of an array of strings. Internally, I don't know whether strings in my language are Pascal-style or C-style. In the former case, since strings "know" their length, I can expect my length-summing-loop to take linear time dependent on the number of strings. I should also know that operations that change the length of strings will have an overhead associated with them, since string.length will be recalculated every time it changes. – Patrick Collins Aug 26 '13 at 17:27
  • 3
    In the latter case, since the string doesn't "know" its length, I can expect my length-summing-loop to take quadratic time (that depends both on the number of strings and their lengths), but operations that change the length of strings will be cheaper. Neither of these implementations are wrong, and neither would merit a bug report, but they call for slightly different styles of coding in order to avoid unexpected hiccups. Wouldn't it be easier if the user had at least a vague idea what was going on? – Patrick Collins Aug 26 '13 at 17:28
  • So if you know that the string class implements C-style, you will choose a way of coding taking that fact into account. But what if the next version of the string class implements the new Foo-style representation? Will you change your code accordingly or will you accept the performance loss caused by false assumptions in your code? – mouviciel Aug 26 '13 at 18:57
  • I see. So the OO response to "How can I squeeze some extra performance out of my code, relying on a specific implementation?" is "You can't." And the response to "My code is slower than I would expect it to be, why?" is "It needs to be rewritten." Is that more or less the idea? – Patrick Collins Aug 26 '13 at 19:03
  • 2
    @PatrickCollins The OO response is rely on interfaces not implementations. Don't use an interface that doesn't include performance guarantees as part of the interface definition(like the example of C++11 List.size being guaranteed O(1)). It doesn't require including implementation details in the interface definition. If your code is slower than you would like is there any other answer than you will have to change it to be faster(after profiling it to determine bottlenecks)? – stonemetal Aug 26 '13 at 20:09
2

Any programmer-oriented piece of documentation which fails to inform programmers about the complexity cost of routines/methods is flawed.

  • We are looking to produce side effect-free methods.

  • If execution of a method has run time complexity and/or memory complexity other than O(1), in memory- or time-constrained environments it can be considered to have side effects.

  • The principle of least surprise is violated if a method does something completely unexpected - in this case, hogging memory or wasting CPU time.

1

I think you understood him correctly, but I also think you have a good point. if Person.age is implemented with an expensive calculation, then I think I'd like to see that in the documentation too. It could make the difference between calling it repeatedly (if it's an inexpensive operation) or calling it once and caching the value (if it expensive). I don't know for sure, but I think in this case Meyer might agree that a warning in the documentation should be included.

Another way to handle this might be to introduce a new attribute whose name implies that a lengthy calculation might take place (such as Person.ageCalculatedFromDB) and then have Person.age return a value that's cached within the class, but this may not always be appropriate, and seems to overcomplicate things, in my opinion.

  • 3
    One could also make the argument that if you need to know the age of a Person, you should call the method to get it regardless. If callers start doing too-clever-by-half things to dodge having to make the computation, they run the risk of having their implementations not work correctly because they crossed over a birthday boundary. Expensive implementations in the class will manifest as performance problems that can be rooted out by profiling and improvements like caching can be done in-class, where all callers will see the benefits (and correct results). – Blrfl Aug 26 '13 at 16:45
  • 1
    @Blrfl: well yes, caching should be done in the Person class, but I think the question was intended as more general and that Person.age was only an example. There are probably some cases where it would make more sense for the caller to choose - maybe the callee has two different algorithms to calculate the same value: one fast but inaccurate, one much slower but more accurate (3D rendering comes to mind as one place where that may happen), and the documentation should mention this. – FrustratedWithFormsDesigner Aug 26 '13 at 17:07
  • Two methods that provide different results is a different use case than when you expect the same answer each time. – Blrfl Aug 30 '13 at 11:56
0

Documentation for Object-oriented classes often involves tradeoff between giving the maintainers of the class flexibility to change its design, versus allowing consumers of the class to make full use of its potential. If an immutable class will have a number of properties which will have a certain exact relationship with each other (e.g. the Left, Right, and Width properties of an integer-coordinate grid-aligned rectangle), one might design the class to store any combination of two properties and calculate the third, or one might design it to store all three. If nothing about the interface makes clear which properties are stored, the programmer of the class may be able to change the design in the event that doing so would prove helpful for some reason. By contrast, if e.g. two of the properties are exposed as final fields and the third isn't, then future versions of the class will always have to use the same two properties as being the "basis".

If properties do not have an exact relationship (e.g. because they're float or double rather than int), then it may be necessary to document which properties "define" the value of a class. For example, even though Left plus Width is supposed to equal Right, floating-point math is often inexact. For example, suppose a Rectangle which uses type Float accepts Left and Width as constructor parameters is constructed with Left given as 1234567f and Width as 1.1f. The best float representation of the sum is 1234568.125 [which may display as 1234568.13]; the next smaller float would be 1234568.0. If the class actually stores Left and Width, it may report the width value as it was specified. If, however, the constructor computed Right based upon the passed-in Left and Width, and later computed Width based upon Left and Right, it would report the width as 1.25f rather than as the passed-in 1.1f.

With mutable classes, things can be even more interesting, since a change to one of the inter-related values will imply a change to at least one other, but it may not always be clear which one. In some cases, it may be best to avoid having methods which "set" a single property as such, but instead either have methods to e.g. SetLeftAndWidth or SetLeftAndRight, or else make clear what properties are being specified and which are changing (e.g. MoveRightEdgeToSetWidth, ChangeWidthToSetLeftEdge, or MoveShapeToSetRightEdge).

Sometimes it may be useful to have a class which keeps track of which properties' values have been specified and which have been computed from others. For example, a "moment in time" class might include an absolute time, a local time, and a time zone offset. As with many such types, given any two pieces of information, one may compute the third. Knowing which piece of information was computed, however, may sometimes be important. For example, suppose that an event is recorded as having occurred at "17:00 UTC, time zone -5, local time 12:00pm", and one later discovers that the time zone should have been -6. If one knows that the UTC was recorded off a server, the record should be corrected to "18:00 UTC, time zone -6, local time 12:00pm"; if someone keyed in the local time off a clock it should be "17:00 UTC, time zone -6, local time 11:00am". Without knowing whether the global or local time should be considered "more believable", however, it's not possible to know which correction should apply. If, however, the record kept track of which time was specified, changes to the time zone could leave that one alone while changing the other.

0

All these rules about how to hide information in classes make perfect sense on the assumption of needing to protect against that someone among the users of the class who will make the mistake of creating a dependency on the internal implementation.

It's fine to build in such protection, if the class has such an audience. But when the user writes a call to a function in your class, they are trusting you with their execution-time bank account.

Here's the sort of thing I see a lot:

  1. Objects have a "modified" bit saying if they are, in some sense, out-of-date. Simple enough, but then they have subordinate objects, so it's straightforward to let "modified" be a function that sums over all the subordinate objects. Then if there are multiple layers of subordinate objects (sometimes sharing the same object more than once) simple "Get"s of the "modified" property can end up taking a healthy fraction of execution time.

  2. When an object is in some way modified, it is assumed that other objects scattered around the software need to be "notified". This can take place over multiple layers of data structure, windows, etc. written by different programmers, and sometimes repeating in infinite recursions that need to be guarded against. Even if all the writers of those notification handlers are reasonably careful not to waste time, the entire composite interaction can end up using an unpredicted and painfully large fraction of execution time, and the assumption that it is simply "necessary" is blithely made.

SO, I like to see classes that present a nice clean abstract interface to the outside world, but I do like to have some notion of how they work, if only to understand what work they are saving me. But beyond that, I tend to feel that "less is more". People are so enamored of data structure that they think more is better, and when I do performance tuning the universal massive reason for performance problems is slavish adherence to bloated data structures built the way people are taught.

So go figure.

0

Adding implementation details like "calculate or not" or "performance info" make it more difficuilt to keep code and doc in sync.

Example:

If you have an "performance-expensive" method do you want to document "expensive" also to all classes that use the method? what if you change the implementation to be not expensive any more. Do you want to update this info to all consumers, too?

Of course it is nice for a code maintainer to get all important infos from the code documentation, but i donot like documentation that claims something that is not valid anymore (out of sync with the code)

0

As the accepted answer comes to the conclusion:

So: document performance issues.

and self-documented code is considered to be better than documentation it follows that the method name should state any unusual performance results.

So still Person.age for return current_year - self.birth_date but if the method uses a loop to calculate the age (yes): Person.calculateAge()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.