|
Object Oriented Analysis & Design Best Practices
|
| Use UML Use UML because it is the standard for OO notation. With deference to all the great work by other methodologists, engineers, and "thinkers", UML has won. The probability that someone understands what you are doing is significantly enhanced by having a (good-enough) core shared notation. Start with UML and then enhance it for needs it does not address. |
| Dont draw models for everything Dont draw models for everything; instead, concentrate on the key areas. It is better to have a few diagrams that you use and keep up-to-date than to have many forgotten, obsolete models. |
| Prove Performance Do not sacrifice a guideline for performance reasons until you see the profiling numbers. Only optimize when it will quantifiably be worth the maintenance penalty. Optimization is always harmful and rarely beneficial unless you have numbers to back it up. A better design (which these guideline try to encourage) will allow more precise and effective optimizations later when the numbers come in. |
| Two important principles to consider for high-quality software development
are to:
Think from the clients point of view |
|
The following maxims are from Kent Becks Smalltalk Best Practice Patterns [Beck 96]. These describe properties that should be in good OO designs and good OO code: Once and only once In a program written with good style, everything
is said once and only once. |
| Elegance always pays off. In the short term it might seem like it takes much longer to come up with a truly graceful solution to a problem, but when it works the first time and easily adapts to new situations instead of requiring hours, days, or months of struggle, youll see the rewards (even if no one can measure them). Not only does it give you a program thats easier to build and debug, but its also easier to understand and maintain, and thats where the financial value lies. This point can take some experience to understand, because it can appear that youre not being productive while youre making a piece of code elegant. Resist the urge to hurry; it will only slow you down. |
| First make it work, then make it fast. This is true even if you are certain that a piece of code is really important and that it will be a principal bottleneck in your system. Dont do it. Get the system going first with as simple a design as possible. Then if it isnt going fast enough, profile it. Youll almost always discover that your bottleneck isnt the problem. Save your time for the really important stuff. |
| Remember the divide and conquer principle. If the problem youre looking at is too confusing, try to imagine what the basic operation of the program would be, given the existence of a magic piece that handles the hard parts. That piece is an objectwrite the code that uses the object, then look at the object and encapsulate its hard parts into other objects, etc. |
| Your analysis and design must produce, at minimum, the classes in your
system, their public interfaces, and their relationships to other classes, especially base classes. If your design methodology produces more than that, ask yourself if all the pieces produced by that methodology have value over the lifetime of the program. If they do not, maintaining them will cost you. Members of development teams tend not to maintain anything that does not contribute to their productivity; this is a fact of life that many design methods dont account for. |
| Automate everything. Write the test code first (before you write the class), and keep it with the class. Automate the running of your tests through a makefile or similar tool. This way, any changes can be automatically verified by running the test code, and youll immediately discover errors. Because you know that you have the safety net of your test framework, you will be bolder about making sweeping changes when you discover the need. Remember that the greatest improvements in languages come from the built-in testing provided by type checking, exception handling, etc., but those features take you only so far. You must go the rest of the way in creating a robust system by filling in the tests that verify features that are specific to your class or program. |
| Write the t est code first (before you write the class) in order to
verify that your class design is complete. If you cant write test code, you dont know what your class looks like. In addition, the act of writing the test code will often flush out additional features or constraints that you need in the classthese features or constraints dont always appear during analysis and design. Tests also provide example code showing how your class can be used. |
| All software design problems can be simplified by introducing an extra level of conceptual indirection. This fundamental rule of software engineering 1 is the basis of abstraction, the primary feature of object-oriented programming. An indirection should have a meaning This meaning can be something as simple as putting commonly used code in a single method. If you add levels of indirection (abstraction, encapsulation, etc.) that dont have meaning, it can be as bad as not having adequate indirection. |
|
Make classes as atomic as possible. Give each class a single, clear purpose. If your classes or your system design grows too complicated, break complex classes into simpler ones. The most obvious indicator of this is sheer size: if a class is big, chances are its doing too much and should be broken up.Clues to suggest redesign of a class are: 1) A complicated switch statement: consider using polymorphism. |
| Watch for long argument lists. Method calls then become difficult
to write, read, and maintain. Instead, try to move the method to a class where it is (more) appropriate, and/or pass objects in as arguments. |
| Dont repeat yourself. If a piece of code is recurring in many methods in derived classes, put that code into a single method in the base class and call it from the derived-class methods. Not only do you save code space, you provide for easy propagation of changes. Sometimes the discovery of this common code will add valuable functionality to your interface. |
| Watch for switch statements or chained if-else clauses. This is
typically an indicator of type-check coding, which means you are choosing what code to execute based on some kind of type information (the exact type may not be obvious at first). You can usually replace this kind of code with inheritance and polymorphism; a polymorphic method call will perform the type checking for you, and allow for more reliable and easier extensibility. |
| From a design standpoint, look for and separate things that change
from things that stay the same. That is, search for the elements in a system that you might want to change without forcing a redesign, then encapsulate those elements in classes. |
| Dont extend fundamental functionality by subclassing. If an interface element is essential to a class it should be in the base class, not added during derivation. If youre adding methods by inheriting, perhaps you should rethink the design. |
| Less is more. Start with a minimal interface to a class, as small and simple as you need to solve the problem at hand, but dont try to anticipate all the ways that your class might be used. As the class is used, youll discover ways you must expand the interface. However, once a class is in use you cannot shrink the interface without disturbing client code. If you need to add more methods, thats fine; it wont disturb code, other than forcing recompiles. But even if new methods replace the functionality of old ones, leave the existing interface alone (you can combine the functionality in the underlying implementation if you want). If you need to expand the interface of an existing method by adding more arguments, create an overloaded method with the new arguments; this way you wont disturb any existing calls to the existing method. |
| Watch out for giant object syndrome. This is often an affliction of procedural programmers who are new to OOP and who end up writing a procedural program and sticking it inside one or two giant objects. With the exception of application frameworks, objects represent concepts in your application, not the application. |
| If you must do something ugly, at least localize the ugliness inside a class. |
| If you must do something nonportable, make an abstraction for that
service and localize it within a class. This extra level of indirection prevents the nonportability from being distributed throughout your program. (This idiom is embodied in the Bridge Pattern). |
| Choose composition first when creating new classes from existing classes.
You should only used inheritance if it is required by your design. If you use inheritance where composition will work, your designs will become needlessly omplicated. |
| Use design patterns to eliminate naked functionality. That is, if only one object of your class should be created, dont bolt ahead to the application and write a comment Make only one of these. Wrap it in a singleton. If you have a lot of essy code in your main program that creates your objects, look for a creational pattern like a factory method in which you can encapsulate that creation. Eliminating naked functionality will not only make your code much easier to understand and maintain, it will also make it more bulletproof against the well-intentioned maintainers that come after you. |
| When you think youve got a good analysis, design, or implementation,
do a walkthrough. Bring someone in from outside your groupthis doesnt have to be a consultant, but can be someone from another group within your company. Reviewing your work with a fresh pair of eyes can reveal problems at a stage when its much easier to fix them, and more than pays for the time and money lost to the walkthrough process. |