Hallmarks of Good System Design

I’ve had a few conversations over the last week or two with some co-workers and friends about what constitutes ‘good system design’. I thought I’d post up my ideas with some short descriptions and get feedback from others. Here’s my list:

 

1. The system is easily understood by developers of all skill levels.

I really think this is imperative. If people look at how your system is put together, look at how it functions and understand it without a lot of explanation about how it works technically, you’ve got a good design (explaining business domain is different). If people look at your system and are immediately lost on how it all ties together, then chances are you’ve either over-complicated it, written something only you can maintain, or written something that people are going to have to be trained on in order to make meaningful contributions.

There are usually more mid level and junior level people on most teams than senior levels and architects. If you have a system that can only really be maintained by senior level developers (and higher), then there’s a problem. Unfortunately, many people look at these sorts of systems and the developers that write them and assume they are very intelligent/talented/gifted/what have you. It’s not much different than people who converse with someone that uses a lot of “big words” (or in this industry, buzz words) that they can’t understand — they think they must be intelligent.

Being hard to understand isn’t a sign of intelligence or good design. An intelligent person is someone that can explain concepts and ideas to people that don’t know anything about a subject matter so that they can easily understand it. In the same way, an intelligently designed system is one that can be easily understood by developers that didn’t design and build it.

 

2. Refactoring is not an exercise in total redesign

A well designed system is one that accomplishes exactly what the business needs today but is put together in a way that can easily be modified to support the business needs of tomorrow. Part of this is making your system loosely coupled, part of it is understanding concepts like abstraction, encapsulation and dependency inversion work. If you need to switch from your own home-grown subsystem to a 3rd party service, you should be able to create new classes and implement the interfaces defined in your system.

You should not have to tear apart how the entire system works.

Forethought must be reasonable; it is too easy to conceive an overwhelming set of situations that you may have to contend with. YAGNI is a key principle here. Use wisdom; design for today with the ability to easily refactor support for tomorrow, but don’t go overboard.

3. Functionality is easily consumed

A well-designed system is one that is written with the consumer in mind; Functionality and intent should be easily understood by the person consuming it without having to dig through the implementation to make sure that it actually does what is expected. I shouldn’t need some secret tribal knowledge to know what your component does; it should tell me what it does and it should do what it says. “No-ops” do not adhere to this philosophy. If your class has a function on it, that function should do what it claims to do. As a consumer, I shouldn’t have to know whether or not a function I need to call will actually perform the operation that it advertises.

Functionality should be designed with ease-of-use in mind. I should be able to instantiate your object and give it whatever dependencies it requires in order to function and then execute whatever methods I need to execute in order to accomplish the objective at hand. I should not need any secret tribal knowledge on the side effects of what I pass in to these functions. If your class requires a dependency and I pass it null, don’t treat it as a secret directive to start instantiating and using some predefined implementation. That is not intuitive and will not produce the results that I would expect.

 

4. Functionality is easily maintained

A well-designed system is one that can be maintained by anyone. Intent should be clear and easy to understand and the internals of your component should be self-descriptive along the lines of functionality provided. Methods should do one thing and one thing only. If you’ve written something that no one else feels comfortable trying to enhance, you’ve got a bad design. If your design does not make sense to other people, you have a bad design. One of the most important things about design is that it must be maintainable — and for it to be maintainable, other people have to feel comfortable with it and be able to understand it.

Complex problems are usually a bunch of smaller, simple problems. Build solutions that are built from smaller, simple solutions.

 

5. Tools and frameworks should be included because the benefits outweigh the costs

There are a lot of tools and frameworks that do a lot of different things. Each of them come with a set of benefits and a set of costs. Take a great care when you choose them — each one comes with a learning curve and will require time for developers unfamiliar with the tool to become productive with it. The more tools you throw into something, the more time it will take. Pick the tools that make the most sense for the problem you are trying to solve.

Including a tool or a framework simply because it seems ‘cool’ is not a valid reason. Doing it because you want to learn something new is not either. And if you need a tool or framework, be intelligent about how you introduce it as a dependency into your system. In most cases, you do not need a reference to it in every assembly. One would suffice, if you encapsulate it and interact with it through your own interfaces.

 

Those are a handful of things that I consider necessary for good system design. There are a lot of others, and in any design, there will always be a balancing act between many of these ideas and reality. I’d like to hear what other people think contributes to good system design.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Tom

    Great article. On point 3; we are using DI heavily in our solution now and the benefits are easily understood. All classes are pluggable since they are build by the container. This does however pose a large dependency on the container which might prevent reuse; for instance because of the complexity in building up some of the objects one would still have to copy a lot of classes when trying to reuse. Any thoughts on that?

  • http://realfiction.net Frank Quednau

    Hm….concepts like “abstraction, encapsulation and dependency inversion work” are in my experience not readily accessible to junior developers. A system that makes use of those concepts can impose difficulties for such novices to understand the system. Just now I am experiencing that some A-HA effects (Why certain things are done the way they are done) took between 1-2 months to really settle in.

  • http://www.lostechies.com/members/mbratton/default.aspx mbratton

    @Tom

    I think that could make a whole new blog post, but to give you a brief idea, you might look at this guy’s blog post: http://weblogs.asp.net/sfeldman/archive/2009/02/08/switching-ioc-container-with-linq-expressions.aspx

    Basically abstract and encapsulate, keep your dependencies on you 3rd party IoC to a minimum and use your own abstraction as much as possible.

    @Frank

    I draw a distinction between entry level and junior (not all places do) and consider junior developers capable of at least reading and grasping code — of course it’s always a trade-off between complexity and intuitive design, but I believe you should only make it as complex as it has to be, and as intuitive as possible.

  • Ajai

    Good stuff Marcus – lots of food (sprees) for thought…

    Everything is easily understandable & maintainable till we write that first line of code!

    I still like ‘cool’ though :-)

  • Hippy

    I would add:

    “The domain model of the design should be easily recognizable to a domain expert.”

    The idea I am trying to get across is similar in tone to “Functionality is easily consumed,” but this time from a more abstract domain expert’s point of view. If an expert in the problem domain can’t understand your design (i.e. the basic concepts represented in your code), then how can you be sure you’re solving their problem? How do you communicate between each other? At some level your design should also feel natural when used in providing a solution to any necessary use case as defined by the the domain expert.

    I think you could write a piece of software that meets or exceeds everything you listed above that still doesn’t satisfactorily address a problem your customers care about; thus, this last piece of the puzzle.

  • Gtsbunty

    Intilop has several groups who can take on projects that range from a small 100k Gate FPGA design/integration to 10 M gate SOC Design/integration/Verification project or a small 2 inch X 2 inch board for an embedded design application to 22 inch X 26 inch, 24 layer multi-Giga bit blade server Board with multiple 1000+ pin BGA devices. fNIC with Full TCP Offload