Dashboard > MbUnit > ... > Development > Design Standards
MbUnit Log In View a printable version of the current page.
Design Standards
Added by Jay Flowers, last edited by Jay Flowers on Jul 18, 2005  (view change)

Good Design


  1. Test First
    This is the practice of writing unit tests before writing production code.
    Write More Here!

    What is a unit test?
    Where can I find how to do this?

  2. Payback on Code Debt
    As a project progresses life happens and shortcuts must be taken. The shortcuts or debts accumulate. At some point they need to be paid off. This can happen by refactoring during the project or on a great walup at the end of the project.

This is from that paperback in the Martin Fowler signature series.

  1. Coding by Intention
    Coding by intention is the practice of pretending a piece of function you need already exists and is
    in the form you want it to be in. In other words, if you find that you have to write some function,
    instead of coding it in the place you need it run, put in a call to method you pretend exists. In our
    example in the "Write Tests First" section, this would mean writing the method we described as:
    s= getStatus();
    s= formatStatus(s);
    s= encryptString(s);

    This is much better than writing one long method that is all implementation. This gives us an easy
    to read method that describes what is happening. Note that each of these methods has strong
    cohesion and should be correctly coupled because no knowledge of how it is being implemented is
    being taken advantage. Since each method has a well-defined interface with a clear contract, each
    of these should also be relatively easy to test.
    Coding by intention assists:
    Testability: because a well-defined intention is easier to test.
    Cohesion: because the intention should be about one thing, resulting in method cohesion
    Encapsulation: because it encapsulates the implementation of the method being defined
    Correct coupling: because the calling routine is written only to the interface
    Readability: because functions are contained in well-named methods.
    It may not be quite as clear that it also helps eliminate redundancy, but in practice it does. This is
    because coding by intention results in stronger cohesion which makes it easier to identify when a
    redundancy has been introduced.

Code Qualities and Practices

  1. Refactoring
  2. Up Front Design
  3. Don't do more than you need
    Don't do more than you need is our version of XP's "Doing the simplest thing possible". We
    prefer to state it this way because it isn't always clear what simple is. Don't do more than you need
    springs from too many projects doing much more than needed. Common examples include:
  • features that are implemented that are never used
  • bells and whistles that are added because developers were "sure" they were useful
  • frameworks that handle more than is ever required of them
    We're not suggesting architecture isn't important or that you shouldn't think ahead. The difficulty
    is that developers often think much too far ahead (or not ahead at all). Doing something you need
    now is a good idea. Doing something you may need in the future is often a waste of time. What's
    worse, is that it often causes the design problem to greatly increase in magnitude. Over- designed
    software can be just as problematic as under- designed software.
    The bottom line is, if you follow this mandate, you may improve the quality of your code because:
    1. You might not actually need it (YAGNI), thereby saving time to work on things you do
    2. If you do need it in the future, you may be smarter at that point when you have to
    implement it.
    3. Not doing things you don't need to do now allows you to focus on those things you do need
    to do now
    4. If you do need it later, more of the system will be built that should make writing the
    function easier.

Code Qualities and Practices

There is an old saying: "Fool me once, shame on you. Fool me twice, shame on me." This is a powerful attitude in software design. To keep from loading our software with Needless Complexity, we may permit ourselves to be fooled once. This means we initially write our code expecting it not to change. When a change occurs, we implement the abstractions that protect us from future changes of that kind. In short, we take the first bullet, and then we make sure we are protected from any more bullets coming from that gun.
From Robert Martin's "Agile Software Development"

  1. Continuous Integration


  1. Single Responsibility
    Why is it important to separate responsibilities into separate classes?
    Because each responsibility is an axis of change. When the requirements change, that
    change will be manifest through a change in responsibility amongst the classes. If a class
    assumes more than one responsibility, then there will be more than one reason for it to
    If a class has more then one responsibility, then the responsibilities become coupled.
    Changes to one responsibility may impair or inhibit the class' ability to meet the others.
    This kind of coupling leads to fragile designs that break in unexpected ways when


  1. Open Close
    A module should be open for extension but closed for modification.
    Of all the principles of object oriented design, this is the most important. It originated
    from the work of Bertrand Meyer2. It means simply this: We should write our modules
    so that they can be extended, without requiring them to be modified. In other
    words, we want to be able to change what the modules do, without changing the
    source code of the modules.
    This may sound contradictory, but there are several techniques for achieving the OCP
    on a large scale. All of these techniques are based upon abstraction. Indeed, abstraction
    is the key to the OCP.


  1. Inversion of Dependency
    Depend upon Abstractions. Do not depend upon concretions.
    If the OCP states the goal of OO architecture, the DIP states the primary mechanism.
    Dependency Inversion is the strategy of depending upon interfaces or abstract functions
    and classes, rather than upon concrete functions and classes. This principle is the
    enabling force behind component design, COM, CORBA, EJB, etc.
    Procedural designs exhibit a particular kind of dependency structure. As Figure 2-17
    shows, this structure starts at the top and points down towards details. High level
    modules depend upon lower level modules, which depend upon yet lower level modules,
    A little thought should expose this dependency structure as intrinsically weak. The
    high level modules deal with the high level policies of the application. These policies
    generally care little about the details that implement them. Why then, must these high
    level modules directly depend upon those implementation modules?
    An object oriented architecture shows a very different dependency structure, one in
    which the majority of dependencies point towards abstractions. Morevoer, the modules
    that contain detailed implementation are no longer depended upon, rather they
    _depend themselves _upon abstractions. Thus the dependency upon them has been


  1. Substitution
    Subclasses should be substitutable for their base classes.
    This principle was coined by Barbar Liskov2 in her work regarding data abstraction
    and type theory. It also derives from the concept of Design by Contract (DBC) by
    Bertrand Meyer3.
    The concept, as stated above, is depicted in Figure 2-14. Derived classes should be
    substitutable for their base classes. That is, a user of a base class should continue to
    function properly if a derivative of that base class is passed to it.


  1. Interface Segregation
    Many client specific interfaces are better than one general purpose interface
    The ISP is another one of the enabling technologies supporting component substrates
    such as COM. Without it, components and classes would be much less useful and portable.
    The essence of the principle is quite simple. If you have a class that has several clients,
    rather than loading the class with all the methods that the clients need, create
    specific interfaces for each client and multiply inherit them into the class.


Object Oriented Design

  1. Inheritance
    Used for Abstraction
    Used to Encapsulate Types/Implementations
    Follows the Substitution Principle
    Follows the Single Responsibility Principle
  2. Polymorphism
  3. Encapsulation
    Follows the Coding by Intention Practice


  4. Strong Cohesion
  5. Loose coupling
  6. Reuse
  7. Easy to Maintain
  8. Easy to Test
  9. Easy to Understand Design (Low Viscosity)

Bad Design


  1. Needless Complexity
    Code that is not used, or code that does more than it needs to.
      1. Redundancy
        The same or similar code blocks scattered through out the product.
      2. Fragility
        Closely related to rigidity is fragility. Fragility is the tendency of the
        software to break in many places every time it is changed. Often the breakage occurs
        in areas that have no conceptual relationship with the area that was changed. Such
        errors fill the hearts of managers with foreboding. Every time they authorize a fix,
        they fear that the software will break in some unexpected way.
        As the fragility becomes worse, the probability of breakage increases with time,
        asymptotically approaching 1. Such software is impossible to maintain. Every fix
        makes it worse, introducing more problems than are solved.
        Such software causes managers and customers to suspect that the developers have lost
        control of their software. Distrust reigns, and credibility is lost.


      1. Rigidity
        Rigidity is the tendency for software to be difficult to change, even in
        simple ways. Every change causes a cascade of subsequent changes in dependent
        modules. What begins as a simple two day change to one module grows into a multiweek
        marathon of change in module after module as the engineers chase the thread of
        the change through the application.
        When software behaves this way, managers fear to allow engineers to fix non-critical
        problems. This reluctance derives from the fact that they don't know, with any reliability,
        when the engineers will be finished. If the managers turn the engineers loose
        on such problems, they may disappear for long periods of time. The software design
        begins to take on some characteristics of a roach motel -- engineers check in, but they
        don't check out.
        When the manager's fears become so acute that they refuse to allow changes to software,
        official rigidity sets in. Thus, what starts as a design deficiency, winds up being
        adverse management policy.


      1. Immobility
        Immobility is the inability to reuse software from other projects or
        from parts of the same project. It often happens that one engineer will discover that he
        needs a module that is similar to one that another engineer wrote. However, it also
        often happens that the module in question has too much baggage that it depends upon.
        After much work, the engineers discover that the work and risk required to separate
        the desirable parts of the software from the undesirable parts are too great to tolerate.
        And so the software is simply rewritten instead of reused.


      1. Viscosity
        Viscosity comes in two forms: viscosity of the design, and viscosity of
        the environment. When faced with a change, engineers usually find more than one
        way to make the change. Some of the ways preserve the design, others do not (i.e.
        they are hacks.) When the design preserving methods are harder to employ than the
        hacks, then the viscosity of the design is high. It is easy to do the wrong thing, but
        hard to do the right thing.
        Viscosity of environment comes about when the development environment is slow
        and inefficient. For example, if compile times are very long, engineers will be
        tempted to make changes that don't force large recompiles, even though those
        changes are not optiimal from a design point of view. If the source code control system
        requires hours to check in just a few files, then engineers will be tempted to make
        changes that require as few check-ins as possible, regardless of whether the design is
        These four symptoms are the tell-tale signs of poor architecture. Any application that
        exhibits them is suffering from a design that is rotting from the inside out. But what
        causes that rot to take place?


      1. Opacity
        Opacity is the tendency of a module to be difficult to understand. Code can be written in a clear and expressive manner, or it can be written in an opaque and convoluted manner. Code that evloves over time tends to become more and more opaque with age. A constant effort to keep the code clear and expressive is required in order to keep opacity to a minimum.
        When developers first write a module, the code may seem clear to them. That is because they have immersed themselves within it, and the understand it at an intimate level. Latter, after tha intamicy has worn off, they may return to the module and wonder how the could have written anything so awful. To prevent this, developers need to put themselves in thier readers shoes and make a concerted effort to refactor thier code so that thier readers can understand it. They also need to have code reviews by others.

This is a quote from "Agile Software Development" Robert Martin.

    1. Practices
      1. Programing for possibilities
        This is when you antisipate need for some functionality or capability in the future. You include code to support these antisipations in the code for the known needs. In the future these antispation may or may not manifest. When they do not there is unnessicary complexity to understand and maintain. When they do mainifest, they rarly do so in the way you expected.
      2. Test Last or Not at All
        Testing last is difficult. It is generally imposilble to write true unit tests. Integration tests are the most granular tests that can be written inmost cases. Even integration tests in environment are difficult to setup. Because they are so difficult to create and maintain excusses are made to not create them at all. This leads to not testing at all. In this case developement has no way to know the health of the code they deliver to the test team.
      3. Never Addressing Impact of Shortcuts
        As the code debt builds it is never payed of. This will increase the viscosity of the design. It will become increasingly difficult to event find what the design is. At that point maintenance is extreemly difficult. Making changes to the code have unknowable affects.
      4. No Formal Design
        Just start coding.
      5. Coding by Purpose
        Starts writting code after thinking about what the code needs to acomplish. The focus is on what the code does not on the interface, how to use it. This will cause the code to be difficult to test, and clents will have difficulty in understanding how to use it. The possibility of reuse is reduced as well, who wants to reuse something that has so many difficulties.
    2. Object Oriented Design
      1. Inheritance
        Used for Specialization
        I have a concrete class that is used by at least one client. It does not offer a capability that I need. I need some of if not all of the functionality that it does offer. I will subclass it adding the responsibility, creating a specialization. There are many problems with this. One the concrete class was not designed to be inherited from, that is why it is conrete. We have added a resposibility breaking the Single Responsibility Principle.

There is a page in Design Patterns Expalined second edition that talks about how and why to use inheritance.

      1. Encapsulation
        Hides information needed to understand how to use it.
        An example of this is an Enum the represents many descret sets of information. The name of the Enum can not convey all the sets of infromation it is resposible for.
    1. Indicators Of
      1. Inheritance
        Misuse of
        Adding new responsibilities
        Subclassing to add new responsibilities is a misuse of inheritance. Many times the solution to this is to implement the new responsility in a new class or even be hind a new abstraction.
        Client casting so as to treat this subclass differently
        This is a sign that the substitution principle has been broken. The clent needs to check what the actual type is so that it can be treated differently.
        Need for
      2. Polymorphism
        Misuse of
        Need for
        Type checking in a case statement
        This can be a sign that polymorphism should be used. The resposiblity for what needs to happen needs to shift from the class implementing the case statement to polymorphism.
      3. Encapsulation
        Misuse of
        Need for

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.1.4 Build:#410 Feb 15, 2006) - Bug/feature request - Contact Administrators