Contents
OO Design Heuristics by Arthur J Riel, Part I
This is made up from extracts from the book above. Its title might be slightly
forbidding - I assumed that it was an intermediate to advanced book - but in
fact its advice is given in such a simple, clear manner that it is well worth
reading at all levels of experience.
To start off, what is a heuristic in this context? The following exchange
occurred on
news://comp.object:
>Hi all,
>
>some OOD books describe guidelines, other speak about
>heuristics. What is the difference between guidelines
>and heuristics?
>And what differentiates guidelines/heuristics from
>principles?
Principles, heuristics, and guidelines are all aspects of advice.
They aren't rules, laws, or truths.
Most people impute a hierarchy to the three:
Principles > Heuristics > Guidelines.
Thus we don't violate principles unless we have to.
We don't violate Heuristics unless we *want* to.
We don't *follow* Guidelines unless we want to.
Robert C. Martin | "Uncle Bob"
Object Mentor Inc.| unclebob @ objectmentor . com
I know I quote Uncle Bob a lot, but he does keep coming up with quotable
statements which I agree with.
I have given the heuristics the same numbers as Riel does, so if you get the
book (go
on! ) you can easily cross-reference from here to there - should you
want to. I have also split this up into three sections. This section covers the
first third of the book (up to half way through Chapter 3). The second will
cover the rest of Chapter 3 and Chapter 4, and the final one the rest of the
book with the exception of Chapter 6 on multiple inheritance; I miss this out
as we are using C# which doesn't have multiple inheritance and my own
experience of it is not enormous (i.e. zero); given that, there's not much I
can say usefully.
Classes and Objects: the Building Blocks of the OO Paradigm.
Reference
|
Heuristic
|
2.1
|
All data should be hidden within its class
|
This fairly standard, but some people appear to dislike it. The main arguments
against it I have heard come down to either language limitations (i.e. having
to provide getXXX and setXXX methods) or possible resistance to change when
moving to a language where the 'cost' of providing these methods is mitigated.
The heuristic states that the following code:
public class BadCode {
public string anAttribute;
}
is bad.
If 'anAttribute' has no logic associated with it - it can freely be set to
anything and read in any circumstances - then why not? The answer, the reason
behind the heuristic, is that this freedom is not guaranteed to last: you can
never say, hand on heart, that this will always be so. If at some later point
you need to add some validation to setting the value of 'anAttribute' it's easy
to change it to the property form:
public class NoLongerBad {
private string anAttribute;
public string AnAttribute {
get { return this.anAttribute; }
set { this.anAttribute = value; }
}
}
Sorted. Maybe.
C# compiles this code to two methods, with these signatures:
public class NoLongerBad {
private string anAttribute;
public string getAnAttribute {
return this.anAttribute;
}
public string setAnAttribute(string value) {
this.anAttribute = value;
}
}
All clients of this class which accessed the public member now must be recompiled before they can use this new
version. This could be quite an exercise, especially if you have external
clients.
Another argument against it is that all accesses are now method calls and they have
an overhead. To this all I can say is that - forgetting the possibility of the
call being in-lined - if the overhead involved in this is such that it will
materially affect your application you must be doing hard real-time against
draconian constraints and maybe a high-level language is not appropriate.
The final argument against it is the amount of typing. If you don't use some
sort of utility like
QuickCode then yes, there is more typing; but if you're compulsively
counting key-strokes then remember when you change the code (as you cannot
guarantee you won't have to) that you will end up using more key-strokes than
you might have done if you did the right thing in the first place! If you do
have something like QuickCode then it takes less key-strokes to do the full
property version anyway.
When would you want to break this heuristic? Very limited circumstances. I have
only come across one type of class where I don't get a bad feeling exposing
fields directly: when the class is and always will be a data-only class. For
example, when integrating two disparate systems you will be sending 'messages'
between these two systems. Your system may well have some or a lot of
intelligence about the routing and formats of the messages but the likelihood
is that the messages themselves have no intelligence at all, especially in a
Service Oriented Architecture where moving objects around is not done. In systems like
this the message may well be represented as say an XML Document. Occasionally,
I prefer to put this data in a class; it makes accessing individual data items
just a bit easier and more efficient. These classes may contain some helper
methods, for example to format a Post Code either as a string without spaces or
as an in-code and an out-code. In this situation I'm very likely to use data-only classes
or structs.
Reference
|
Heuristic
|
2.2
|
Users of a class must be dependent on its public interface, but a class should
not be dependant on its users.
|
Having the user of a class pass itself to a method of the class it is using is
ugly and shows high coupling. Sometimes it's sort of required (for instance
some of the GoF patterns do this, but the coupling there is intentional);
however the ugly coupling can always be got around by making the parameter an
Interface which the using class supports (or supplies another class supporting
the Interface). In this case the Interface should be defined in the same
package (namespace or assembly) as the class receiving it as a parameter to
keep coupling down: coupling within an assembly is better than coupling across
assemblies.
This promotes reusability - not only across differing systems or clients but
within one system or client.
Reference
|
Heuristic
|
2.3
|
Minimise the number of messages in the protocol of a class
|
By keeping an interface minimal a class is easier to use - and more likely to be
used. An interface with many, many messages is an indication that 2.8 is being
broken, and is difficult to understand.
Reference
|
Heuristic
|
2.4
|
Implement a minimal public interface that all classes support
|
This is taken care of in .NET as all classes inherit this minimal interface from
the Object class. As a reminder, this interface contains:
bool Equals(object obj);
int GetHashCode();
Type GetType();
string ToString();
Interestingly, he seems to be arguing for a unified type structure, where all types have
a common ancestor (or implement a common interface).
.NET classes provide this, but C++ classes don't (I don't think
IUnknown qualifies, even if it did apply to all). VB6 classes all implement IUnknown and
IDespatch, but again these don't qualify. To do this in a language which doesn't natively
support it is quite a task, I'd imagine. I haven't felt the need for it before, but given
how useful framework classes which take object as a parameter are (ArrayList.Add(), Console.WriteLine()
et cetera), that's probably my ignorance and/or inexperience showing.
Reference
|
Heuristic
|
2.5
|
Do not put implementation details such as common-code private functions into the
public interface
|
If it's private, it should be kept so. You don't want to be intimate with
everyone.
Reference
|
Heuristic
|
2.6
|
Do not clutter the public interface of a class with items that the users of the
class either cannot, do not want to, or should not use.
|
This is a partner of 2.5. In a sense it tells you whether a method is an
implementation detail or not and whether 2.5 applies.
'Should not use' means that if the client calls this method it either will not
do what it is intended to - the class's internal state has not been set up
properly - and might even corrupt the internal state or throw an exception.
'Cannot use' is a stronger form of this where the method requires prior
internal state or parameters whose value the client cannot get hold of or whose
type the client is ignorant of. Think
Design by Contract here.
Reference
|
Heuristic
|
2.7
|
Classes should only exhibit nil or export coupling with other classes
|
Four types of coupling:
-
Nil: No coupling at all. This is the nirvana of every designer.
Unfortunately a system made up of classes all of which exhibit nil coupling
will probably not actually do anything useful.
-
Export: A class is dependant on (uses) the public interface of another
class
-
Overt: A class is dependant on (uses) the implementation details of another
class (via internal methods, say, or 'friend' in C++).
-
Covert: The same as Overt, but where no permissions have been granted.
This can be done via reflection if CAS (Code Access Security) is not
implemented correctly.
Not many classes will exhibit Nil coupling, even if we take the liberal view (in
this case sensible I think) and exclude all Framework classes. An example would
be a class which for instance takes an SQL Connection String in its constructor
and SQL Strings in Select, Insert, Update, and Delete methods. Since
System.Data.SqlClient is an integral part of the Framework it's reasonable to
say that this class has Nil coupling.
However, if the class uses the Microsoft.Data.OdbcClient namespace is the same
statement justified? This namespace is not an integral part of the Framework
and requires a separate download and installation. I would say that this is
definitely Export coupling in that there is an external dependency while the
class using System.Data.SqlClient relies on no more than the Framework which
will be there anyway (at least if the class has any hope of being called).
Export coupling is the standard usage and we're all OK with it.
Overt coupling is something I have used - via internal methods. Thinking back
on it, these usages were sometimes OK and sometimes not: an assembly might have
a class with internal scope - using its public methods is fine; marking methods
as internal, or using methods marked internal, may not be. The latter certainly
increases the coupling and the next time I do it I'll have to ask myself why I'm
doing it (the class may have too many responsilities - one to external clients and
another to internal ones) and whether the design is really correct.
Covert coupling is an obvious no-no in main-stream systems. I have used it, but only
when I'm trying to document or analyse a system. In a main-stream system anyone
accessing methods which the design says they shouldn't is on their own.
Reference
|
Heuristic
|
2.8
|
A class should capture one and only one key abstraction.
|
This is important: A class should capture one and only one key abstraction.
Most of the 3.x heuristics repeat and refine this in various ways.
By the way, did I say that this is important? A class should capture one and only
one key abstraction.
It's easy to break this. As an example, I have a class which processes Returned
items in an Erp integration system. Its main job is to match the item returned
to an order and Return Authorisation. It also makes a refund if the matching
succeeds. This seemed sensible at the time. However expansion of the
Integration system means that Refunds can be given under differing
circumstances: I can now get the other circumstances to use this Returns class
to make a Refund (silly!), duplicate the refund code across these other classes
(also silly) or do what I should have done at the start: put the Refund
mechanism in it's own class. It wouldn't have been any more work originally;
it's more work now.
Reference
|
Heuristic
|
2.9
|
Keep related data and behaviour in one place
|
In a Web Shop, the Checkout page should not ask the Basket object for the retail
prices of each product, the discount status of the customer, look for any deals
which may apply, and then go and calculate the total order price. The behaviour
here (calculate the order value) is taking place away from the container of the
data required to make the calculation.
Always ask 'Does this calculation really belong here?'. If 'here', wherever that
is, has to dig into other objects to get the data to make the calculation the
answer is probably 'No, it doesn't belong here'. (This is also related to the Law of
Demeter.)
This is a refinement of 2.8.
Reference
|
Heuristic
|
2.10
|
Spin off unrelated information into another class
|
An extreme example of this is a case where half the methods of a class operate
on one half of the data in the class, and the remainder of the methods operate
on the other half of the data. You have two classes here. This could happen if,
in my Returns class above, I left the Refund code in the Return class and
called it directly from the new, non-return, situations.
This is an example of an uncohesive class: it appears to be doing two different
things, or doing one thing in two (or more) different ways. In the first case,
split it into two classes; in the second, think of the
Strategy pattern.
Reference
|
Heuristic
|
2.11
|
Be sure that the abstractions you model are classes and not simply the different
roles objects play.
|
Are Mother and Father classes, or are they roles that a Person class can play?
The answer depends on what the system actually is required to do. In a
Corporate HR system it is probable that they are roles; in a Hospital system
they are probably different classes.
The key is in the behaviour required of them: if at some point in the system it
is necessary to send a message to one which cannot ever be sent to the other
(e.g. InduceLabour(DateTime.Now)) then they display different behaviour and are
different classes.
If there is behaviour which will only ever be called on one of them but could
conceivably be called on the other (WashKitchenFloor()) then they may only be
different roles. This latter is a grey area. Riel says:
'This point gets convoluted in more abstract domains where it is not clear what
cannot be executed versus what a designer or domain chooses not to execute'
He goes on to say that this implies that there is a heuristic he is missing and
that this is his best current guess as to what it is, but that he knows it is
imperfect.
I have heard people saying things like 'Yes, but you'd never actually call it'.
This is a clear indication that you're in this area and some thought needs to
be given to the problem.
Topologies of Action-Oriented versus Object-Oriented Applications
I am not 100% sure what Riel means by 'Action-Oriented', but I am fairly
confident that 'Procedural' can be substituted without damaging his meaning. He
says that Action-Oriented is 'involved with functional decomposition through a
very centralised control mechanism', while Object-Oriented 'focuses more on the
decomposition of data with its corresponding functionality in a very
decentralised setting'.
He goes on to compare best and worst cases of each orientation against the
other over an interesting couple of pages, and concludes that
Action-Orientation, when done right, looks like Object-Orientation. He then
introduces the two main pitfalls of OO: the God class and the Proliferation
of classes.
The God Class problem - where too much is centralised into one class - has two
forms: behavioural and data. The Proliferation of Classes starts the next in
this series.
Heuristics to avoid behavioural God classes
Usually due to an inexperienced designer trying to mimic the central control
mechanisms prevalent in Action Oriented programming, the result is a God class
which does most of the work delegating minor details to a collection of trivial
classes.
Reference
|
Heuristic
|
3.1
|
Distribute system intelligence horizontally as uniformly as possible; the
top-level classes in a design should share the work uniformly
|
Do not design from functional decomposition (e.g. Customer Maintenance, Goods
In). Design individual entities (e.g. Customer, Supplier, and Product). The UI
of the system is responsible only for getting events in the outside world and
passing them on to the appropriate entities in your design and displaying the
results.
My personal rule of thumb is that if the domain objects (Customer, Supplier,
etc) could not be used pretty much unchanged by a different UI to the one I
have in mind I've gone wrong. This different UI should not have to duplicate
any logic in the planned UI; I try to imagine a Console Application using my
objects (it's been a long time, but they were quite fun!) as well as the GUI
I'm actually writing.
Reference
|
Heuristic
|
3.2
|
Do not create God classes. Be very suspicious of classes whose name contains
Driver, Manager, System or SubSystem
|
Just how many responsibilities does this class have? Did I say 2.8 is important?
Note that facades may have these types of names, though.
Reference
|
Heuristic
|
3.3
|
Beware of classes having many accessor (get and set, properties) methods in
their public interface. This implies that data and behaviour are not being kept
in the same place.
|
This harks back to 2.9
Again, Facades may look like this.
Reference
|
Heuristic
|
3.4
|
Beware of classes that have too much non-communicating behaviour.
|
I'm not exactly sure what he means here. My guess, given the context of God
classes, is that a class which does a lot of work itself without delegating
parts of that work to other classes is to be avoided. With this I agree.
Classes that do this will tend to be over-complex and difficult to understand
and hence maintain. To get away from a class that does this look to the
Mediator pattern and refactor the work into already existing classes or
new classes (Strategy,
Command,
Visitor) if no suitable abstractions already exist.
By the way, if anyone wants some practice on this I have a wonderful pair of
classes called OrderStatusProcessor and ReturnsProcessor which you can practice
on.
Facades can easily break all four of these heuristics. It is probable that a
Facade will always break 3.2 (naming), usually break 3.3 (lots of accessors)
and almost by definition breaks 3.1 (distribution of intelligence); however as
long as it doesn't break 3.4 - in other words, as long at most of its work is
delegated - then it's probably OK. This just means that Facades aren't as easy
to do properly as they look at first. I haven't done many and I'm not really
happy with any of the ones I have done: not enough
Mediator/Strategy patterns used probably.
Reference
|
Heuristic
|
3.5
|
In applications which consist of an OO model interacting with a UI, the model
should never be dependant on the UI.
|
My comments on 3.1 apply.
Reference
|
Heuristic
|
3.6
|
Model the real world whenever possible.
|
Riel immediately notes 'This heuristic is often violated for reasons of system
intelligence distribution [3.1], avoidance of God classes [3.x] and keeping
related data and behaviour in one place [2.9]'
This is a heuristic I have real doubts about. I agree that it's an excellent
place - the only place, even - to start your analysis and design but very soon
your model will depart from the real world.
In the interests of fairness, I must say that there are successful and respected
designers who swear that they never depart from the real world. Still in the
interests of fairness (to myself) I must also say that I don't believe them for
a minute: when did you last see an abstract product? Or an abstract anything?
Or a Hashtable walking by? We possibly mean different things by 'reality'....
Heuristics to avoid Data God classes
This form of the problem - classes build around a data Structure - occurs very
often when migrating a legacy system to a new OO design.
Riel gives no heuristics devoted to the problem of data God Classes. He
discusses Controller and Entity classes as a popular but incorrect way to do
legacy migration.
The algorithms in these legacy systems are expressed in terms of separate data
structures and code blocks. These data structures are often used by many
different code blocks and composing these into discrete Objects encapsulating
data and associated behaviour (or behaviour and associated data) can be a very
complex task. It's far easier to bung the data into a data-holding class and
the behaviour into (what might as well be static) methods, thus more or less
exactly duplicating the structure of the legacy application. This is one of the
reasons for the popularity of the Controller/Boundary/Entity type of analysis,
which has spread out from legacy migrations to be used in completely new
systems.
I really dislike this split.
An excellent post from Paul Campbell on comp.object says this about them:
"kk_oop @yahoo.com>" <"kk_oop wrote in message news:bnln92$oah$1@bob.news.rcn.net...
> Hi.
>
> I'm looking for opinions on using robustness class stereotpes, namely,
> Boundary, Control and Entity classes. My sense is that these are useful
> categorizations for moving from use case narrative to actual design.
I'm going to be alot less charitable about them that HS ...
IMO they are all but useless for anything. They are a rehash of
70's "data processing systems analyis" contructs of process (control),
datastore (entity), and terminal (boundary). They hark from a time
when "analysis" consisted of a horrid hybrid of high level database
structure design and the deployment structure of functional sub-systems
that accessed them.
They are IMO absolutely useless for describing modern n-tiered
distributed systems whose architecture is often orthogonal to the
functional requirements.
> This is especially good for folks just coming onto the OO train, as it
> forces them to consider encapsulation based on these classes criteria.
> It also seems like it would aid design reviews by showing visually that,
> for instance, interfaces (Boundary) and passive/persistant objects
> (entity) have been isolated from busniess rules (Control).
Yes but the whole idea is that you arnt supposed to seperate the two -
your "business logic" IS the behaviour of your core classes.
These stereo types encorage a muddling of a requirements analysis
and architecture, and if used at the design level the positively dangerous
practice of designing a data model with objects rather than a true
object model.
Paul C.
Mipsleddings and all, it perfectly articulates my view of the
Boundary-Controller-Entity split.
Riel says about the problem of Legacy migration 'There is an enormous amount of
interest in finding a good process for this migration...[but this is] outside the
scope of this text' and then concludes that yes, it's a hard problem.
To return to data God classes, the point is that creating them imitates the
data/behaviour split from previous styles of programming. The essence of OO is
that objects combine data and behaviour; ignoring this looses all the
advantages OO can give.
It might be interesting to consider .NET DataSets, both strongly-typed and
vanilla, in this light.