- You need a design
(a plan, a structure)
Only on 1 day
in 10,000, and only for systems with less than 1000 lines of code,
and only for systems that will endure less than 100 person-hours of
use can anyone start coding without a design.
- You need an
analysis model or a metaphor
Only on 1 day
in 1000 can anyone start a design without a subject-matter model or
a metaphor to guide them.
- You need requirements
If you don't know
what is required of a system, how can you hope to know what to study,
what to build, how to build it?
- Don't waste
any time allocating methods (member functions) to your software objects
on the basis of the processing that their subject matter (analysis)
counterparts do
If you are producing
a subject-matter model (doing analysis) you have to remember that
it is a model, and that the continuity between a model and whatever
is being modeled, lies in state and not in action. Including processing
or actions in analysis models is largely a waste of time or is dangerous.
In the analysis activity, establish the relevant subject-matter objects
(state packagings exhibiting identity), establish their attributes
(intrinsic state), establish their characterizing relationships (extrinsic
state). At that point you will have good ideas for some creatures
in a structure.
This sounds like
a big thing; but really it's a small thing. Method allocation is a
design step and just comes a little bit later than is sometimes thought.
As a design step (invention rather than discovery) you can decide
what your creatures' software counterparts are going to do. While
the existence and state of the software creatures will have a good
match with their subject matter counterparts, their doings will not.
- Relentlessly
question both many-to-many and bidirectional association relationships
Both of these
are difficult to implement successfully in today's languages and databases.
And they are actually rarer (perhaps much rarer) than people imagine.
Unnecessary many-to-many and bidirectional association relationships
are frequently the result of early models that are vague and too coarse-grained;
they nearly always occur because of overlooked or missed model elements.
- N-ary relationships
are an abomination
Relationships
have two ends. Anything else is vague or imaginary and, anyway, is
unimplementable.
- Pounce on any
hint of conflict or tension in a model
If one modeler
thinks a relationships is aggregation but another thinks not, there's
a very good chance that the model is too coarse-grained. If one modeler
thinks an association is 1..1 and another thinks it's 0..*, there's
a very good chance that the model is too coarse-grained. Conflict
and tensions are valuable flags that model elements may well be missing.
And a model with 5% of its elements missing is worth only 50% of what
it might have been worth.
- You will need
to know the UML well; but not so much for knowing what to use, as for
knowing what not to use and where the difficulties lie
Of the 15 types
of UML diagrams, you will probably only need 2 or 3, (maybe 3 or 4
for a complex project) on a regular basis. And of the diagram types
you do use, you will only need between 50% and 90% of the content
they offer. And with that content, you will have to be quite careful
as to what it signifies; very little is obvious or unambiguous. As
to which diagrams and what content, might I take the liberty of referring
you to a rather good book ...
- Everyone should
know and agree if any use of the UML is as sketch or as blueprint (or
as programming language)
If the UML is
acting as a formal specification vehicle, its use requires more care
and attention than people usually give. If the UML is being used as
a programming language, then you are among the few, the brave or the
foolish.
- Vet UML multiboxes
with more than one substantive compartment
Far too often
every UML box is casually shown with two substantive compartments
(two feature-listing compartments). Entity (analysis) boxes, type
boxes and interface boxes usually have a single compartment. It's
partially abstract classes and concrete classes that have boxes with
two lists. (And classes are sufficiently complicated that they often
need three or four lists.)
- Many models
need no state machines at all; those that do, need fewer than is sometimes
suggested
Just about all
"business" objects (commerce, science, engineering) should
not be modal. They should be boring -- doing the same old, predictable
thing, day in, day out. They should have what we can call combinational
complexity; they take only pure data inputs. Only 5% of hard real-time
systems' objects should be allowed to live modal, exciting lives --
lives where some of their inputs are control inputs -- sequentially
complex lives. This means that only 5% of 15% of the software producing
world's objects will need state machines (or something similar) as
part of their specification.
- The state machines
that you do need require a more care and attention than is usually given
The creation of
correct state machines is a technique that cannot be learned in one
casual attempt. Each kind of deliverable, model or model constituent
-- requirements, analysis, design, interface, implementation, ...
-- requires a different manner of using and interpreting state machines.
And each interpretation of a state machine will take more than one
go to learn. And even when you have learned to create and interpret
the kind of state machine you need, state machines are among the trickiest
things to get right and they also have a tendency toward "one
mistake and the whole thing's useless". So, repeating, you probably
need fewer state machines than you might have thought; and you need
to lavish much more care on the state machines you do end up needing.
- Be object-oriented
(and message-oriented) not class-oriented
Instances are
just as important and should be designed before classes. Analysis
models suggest object instances, not classes. It's the object instances,
once we start to know them, that suggest the types and the classes.
And object instance design is best driven by the messages the object
instances are there to service. Exemplar sequence diagrams are just
as important as class diagrams.
- To design good
objects you must think like an object, not a programmer
Anthropomorphic
thinking is necessary for designing and programming good objects.
You can't think like a programmer; you can't think like some overall
program executive (which there shouldn't be anyway); you must think
like an object instance running its methods in response to its messages.
CRC is a good way to encourage this and to put it into practice. [A
famous computer scientist thought otherwise. See Edsger
Djikstra .]
- Interfaces are
more important than implementation
The type system
is more important than the class system. Public method (member function)
signatures need more thought, care and attention than they are usually
given.
- Design (high-level
and low-level) from the outside inwards
Having followed
the previous three rules, and thus having made a good "outside-in"
start, continue with the outside-in approach when you arrive at class
design. Begin with the "they will come back to haunt you"
signatures of the public methods (member functions) and arrive at
the "here today, gone tomorrow" instance variables (date
members) last. (Last, that is, apart from deciding what implementation
you're going to inherit).
- Inherit interface
first, and implementation later
Inheritance isn't
the wonder-device/savior/silver bullet that was originally hoped for.
If your language
forces you to use inheritance to set up a polymorphic type system,
focus on type sharing (inheritance of interface) first. In
languages like Java and C#, don't use extends (inheritance) if implements
(classes implementing interfaces) is appropriate.
In any language
deal with inheritance of implementation later, when the concrete
classes are settling down and most have appeared.
- Treat superclasses
(base classes) with care
Never instantiate
superclasses; and seldom use superclasses that are only partially
abstract as types. Instantiate only leaf-node subclasses; and tend
towards using purely abstract superclasses or interfaces as types.
Almost a golden
rule in its own right but covering three of four of the preceding
points, variables have types; objects have classes.
- Make all names
(identifiers) precise, colorful and no more concise than they should
be
Have all model
and code names be honest and accurate. Avoid wishy-washy, gray names.
Avoid handle(r), process(or), manager(r), and their ilk, as parts
of names. You want a name to put a clear picture and the same picture
into everyone's head.
- Respect the
"Need to know" principle
Restrict what
your software elements know about each other: they know no more than
they absolutely need to know. This is another phrasing of "strive
to minimize coupling".
- Respect the
software engineer's affirmation
Ensure you follow,
"the thing, the whole thing
and nothing but the thing". An object should empower one thing,
all of that thing and nothing but that thing. A method (member function)
should do one job, the whole job and nothing but that job. Another
phrasing of, "strive for maximum cohesion". Database designers
know this exhortation as "normalization".
- Be guided by
use cases but don't be driven by them
Use cases are
a requirements artifact (and a very useful one at that). However,
be aware that like sequence diagrams and like testing, they are a
sampling activity; if you don't know when to stop, you never will.
Also, they take a functional picture; and as such, there is a risk
that if they drive the design, rather than answer questions asked
by the design, the ensuing object architecture will be poor and fragile.
And don't confuse
use cases with object interaction. Use cases look at the system-to-be
from the outside; sequence diagrams (object interaction diagrams)
look at an emerging system design from the inside.
- Understand the
concept of identity
It still astonishes
me, as the years go by, just how much you can figure out if you clearly
understand the concept of object identity. It helps distinguish analysis
model secondary elements like attributes from primary elements like
entities. It helps choosing between composition by value and composition
by reference. It helps with cloning decisions (in Java and C#) and
with reference/pointer/value passing decisions in C++. ...
- Program in the
future tense
Foresee the inevitability
of having to change and extend tomorrow, what you are writing today.
- Use enough self
messaging
Self messaging
is elegant and powerful. If you (as an object) aren't self messaging
then you (as a designer-programmer) have missed a step in your ascension
to true object-orientated greatness.
- Delegate and
empower
Understand the
Law of Demeter. It's not possible to explain it fully in a paragraph,
but ...
This law is important
because of tendencies that we all seem to have, but that seriously
degrade the value of an object orientation. Allow (empower) your objects
to deliver normalized services.
Don't tell them how to do it; and don't ask them to tell you or give
you what you don't need to know, just so
that you can do some of the work that they ought to be doing.
- 7 ± 2 really
does apply
Some course participants
think we're joking when we say that 85% of methods should have 7 ±
2 non-blank, non-comment lines of code. We aren't. Furthermore, stabilizing
class in a stabilizing architecture should tend towards adding or
overriding no more than 7 ± 2 public methods; UML diagrams should
have no more than 7 ± 2 multiboxes, 85% of whose compartments should
list no more than 7 ± 2 of their features.