Pet programming peeves . . .

Never provide write accessors with mixed units
© Conrad Weisert, Information Disciplines, Inc., Chicago
13 December 2010

NOTE: This document may be circulated or quoted from freely, as long as the copyright credit is included.


Background

OOP experts condemn the strange but surprisingly common practice of providing write accessors (also called "setters") for practically every private instance member of a class. We use the term quasi class to describe this way of seriously undermining the object-oriented paradigm.

Recently we've been seeing a growing number of examples that combine that awful practice with the choice of a mixed unit representation for a numeric quantity. Examples of mixed units include:

Numeric
class
a simple unit
representation
a mixed unit
representation
weightgramspounds, ounces
angleradiansdegrees, minutes, seconds
distancemetersmiles, feet, inches
durationsecondsdays, hours, minutes, seconds

Although the simple unit representations are usually more appropriate for the hidden member data item, that's not our issue here. Whichever choice we make for the private data, we often wish to provide accessor funtions so that users of the class can easily obtain the representations that are most familiar to them and to the ultimate end users. It's perfectly reasonable, therefore, to provide in a C++1 Weight class these accessor2 functions:

  public:
      long   pounds() const;
      short  ounces() const;
      double grams()  const; 
Users of those functions don't know whether the implementation is a simple return of a member data item or the result of a conversion formula, and that's one of a significant advantages of the object paradigm. We can change the internal data representation in order to tune performance, without invalidating any users' code.

Dynes, anyone? (January clarification)

A couple of readers pointed out that weight strictly speaking is a measure of force not mass. For some scientific applications, therefore, it would be inappropriate to specify weight in units of grams, and a mass class would be required.

However for applications that are subject to the earth's surface gravity, such as shipping packages or weighing medical patients, it's customary to treat them interchangeably. Scales that observe metric standards customarily specify units of grams or kilograms, so we'll stay with the example.

But don't do this!

Some programmers who don't appreciate the object-oriented paradigm then almost automatically add these public functions:

   void setPounds(const long x);
   void setOunces(const short x);
   void setGrams (const double x);

Such write accessors are questionable in any case, but they're even more indefensible with the mixed units. In what situations would a program want to change one part of the numeric representation without affecting the other?

Suppose the program wants to set the weight of a package to three and a half pounds. It might execute these statements:

   pkgwt.setPounds(3);
   pkgwt.setOunces(8); 

They can't explain it

When you ask the programmer why he has provided those write accessors and how they support object-oriented concepts, he (they're usually male) is likely to reply that it's a "standard idiom" in C++ or Java. If you follow up by asking where he learned that standard idiom, he may cite a course or a textbook, or just examples that he saw in other programs. He may impatiently assure you that "everyone knows this", but he won't be able to provide a convincing explanation.

If the program gets interrupted just after executing the first statement, the pkgwt object will be in an inconsistent state that corresponds to nothing in the application domain. In many applications, that would be corrected as soon as the task resumes before anyone uses pkgwt, but there may well be subtle situations in which we don't dare leave an object in an inconsistent state even for a few nanoseconds. If the Weight class provides a sensible constructor and assignment operator, the program would accomplish the desired result by coding simply

   pkgwt = Weight(3,8); 
It's then up to the implementer of the assignment operator to assure that the operation is atomic, if that's necessary (trivial with the pure-unit internal representation).

A tempting exception

Calendar manipulations always invite "special" needs. For example, some event is always scheduled on the 7th of every month, so to get from the date of one to the date of the next the program wants to add one to the month without changing the rest of the date (except to advance the year when the month goes from 12 to 1).

In my programming work, assuming a decent Date class, I've found using constructors and operators natural, easy to understand, and efficient, and I've had no need to set components of a date individually. For example, using IDI's Date class and Calendar pseudo-class you can just increment the date by the number of days in the current month:

     d += Calendar::DAYS_IN_MONTH[d.month()]       // C++
       + (d.month()==2 && Calendar::isLeapYear(d.year()));    

     d.addSet(Calendar.DAYS_IN_MONTH[d.month()]   // Java
       + (d.month()==2 && Calendar.isLeapYear(d.year()))? 1:0);

If you need to do that often or if you find it hard to read, make it a function.

Final word

In object-oriented programming it is usually considered poor practice:

To do both in the same class is inexcusable.3


1—The equivalent code in Java, C#, or another object-oriented language should be obvious.
2—Those who like verbs as function names may prefer getPounds(), etc.
3—If I've overlooked some legitimate situation, let me know (cweisert@acm.org), and I'll change this article accordingly.

Return to IDI Home Page
Return to Technical articles

Last modified 18 December 2010