The following is a short list of powerful design principles that came to mind while working on the OOP architecture for a small board game:
Class Design
- Fred Brooks wrote that the hope for OOP lay in "Building with bigger pieces" ("No Silver Bullet Refined", 1995 Mythical Man-Month, p. 219). He quotes James Coggins as observing that programmers are too often building low level pieces, "aiming low in abstraction", while "if we design large-grained classes that address concepts our clients are already working with", there are many advantages. This is key - Build with big pieces; classes should represent as high-level big concepts in the problem domain as possible (i.e.: Maximize class conceptual size).
- On the other hand, I feel that the class interface (C++ style header, or UML class diagram) should fit on one single display screen. If it gets larger than that, then it should be broken into separate classes. (i.e.: Upper bound to class size is one screen for the header.)
- Feel free to use functions as signaling messages (even in a 2-way cycle between classes), instead of relying on return values. In particular, don't create new classes just for use as a return value from a function. Shelly/Cashman Systems Analysis and Design was particular helpful here, in its Ch. 5 OOD examples which have no query (value-returning) methods at all.
Function Design
- A function should do one job well. A function should be powerful, and putting as much functionality into one as possible is reasonable. (i.e.: Maximize function power.)
- A function should definitely fit on one display screen. A programmer should be able to take in a whole function at once on his or her display; if a function larger than this, then it must be broken into other functions. (i.e., Upper bound on function size is one screen.)
- It then follows that breaks, continues, and multiple return statements are all completely acceptable within the context of a sufficiently short function. If the programmer can see all the control branches and exit points at once, then these mechanics can make a function's logic more clear (and shorter), without the problems of open-ended GOTOs.
- If a certain job is done in multiple places (say 3+) in a program, then that should definitely be factored out into a new function, even if it is a fairly short one.
- Don't use Polish notation for variable names and identifiers. My personal biggest productivity boost came on the day when I decided to abort trying to use Polish notation everywhere and just give identifiers reasonable, clearly descriptive, short names. With short functions that fit on one screen (see above), it should be immediately evident which variables come locally, or as parameters, versus those from the class (or globals).
Summary
For classes:
- Maximize class conceptual size - build with big pieces.
- Upper bound to class size is one screen for the header.
- Feel free to use functions as signals between classes, using fewer return values.
For functions:
- Maximize function power.
- Upper bound to function size is one screen.
- Break, continue, multiple returns are all acceptable in a one-screen function.
- Jobs done in multiple places (3+) must be refactored into a new function.
- Don't use Polish notation for identifiers.
Written 1/8/05, DRC