Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I've read books on design, I troll the internet for articles on best practices and software design principles. However, I always seem to struggle to put into practice the kind of suggestions and techniques supplied.

My question is. What kind of things do you ask yourself when you are designing an application/class/method location/property location etc that helps ensure you come up with something that is, although not perhaps perfect, is good enough that you are happy with the result.

For example, when trying to decide between putting a property on an object how do you decide whether the code that might use that object should be part of the class that contains it or exposed so other classes can use it. i.e. how do you decide who owns and responsible for a certain logic/property.

I realise there are things like SOLID that you can apply, but I'm looking for techniques and thought processes people often go though in order to get to the end design that applies these principles.

NOTE: I'm not looking at getting links to books or internet articles necessarily as these I look at quite regularly. I'm really after peoples read world practices on how they go about day to day resolving class and application design issues they come across.

Thanks alot.

share|improve this question

closed as too broad by gnat, GlenH7, MichaelT, Corbin March, Kilian Foth Sep 10 '13 at 14:09

There are either too many possible answers, or good answers would be too long for this format. Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs.If this question can be reworded to fit the rules in the help center, please edit the question.

I usually personify the module/component/class, and try to put myself in its shoes. Then I think, is this really my responsibility? Is it fair that I have to do this? How can I offload this work to someone else? Most of my classes are responsibility shirking lazy little buggers :) – MattDavey Sep 8 '11 at 15:58
Love the shirkinge responsiblity idea – dreza Sep 10 '11 at 21:11
I guess it's similar to how website designers use "personas" when designing a website, it helps them imagine the requirements from the perspective of the persona. – MattDavey Sep 12 '11 at 8:06

11 Answers 11

up vote 7 down vote accepted

Brief summary

  1. Practice a lot, and analyse the (short and long term) consequences of your (and your peers') decisions.
  2. Start with the simplest thing which could possibly work.
  3. Let the software evolve over time - when you smell bad code, refactor.
  4. Don't hesitate to revert your decisions if they don't work.
  5. Ask for other's help and insights when stuck.
  6. "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."

Longer musings

I think there are no clean and objective rules on how to create a good (much less great) design. Software development is a craft, thus one needs to practice it a lot, over an extended time, to make a lot of decisions and also to see the later consequences of those decisions. I may decide to do one thing based on whatever reason (because of SOLID, DRY, or because my boss said so :-) but two years later, when the program has evolved and extended to unforeseen new directions, it may turn out to be a bad decision after all. Or a quick ad hoc decision, which at the moment looks a bit ugly, but I have no better idea on how to make it nicer, may open up the possibility for a major redesign a year later.

I more and more tend to see software as something which is not "designed", but "evolving" over time. So a lot of my decisions are based on feelings rather than clean architectural rules. (Many of those rules are IMHO attempts to define and formalize such base feelings to some extent - however, blindly following those rules without getting the intention behind them leads to no good.) In other words, instead of attempting to impose some predefined design upon the code, I try to understand the inherent inner structure of the code and express it more openly via refactoring.

One more thought on this view vs the quote about perfection above: refactoring strives towards perfection, but it is a moving target, as the code needs to be constantly adapted to an ever changing context. So we need to learn to be satisfied with an ever imperfect code base. (Biological evolution can teach us that in the long term, the best survivors are not the ones who perfectly adapt to one specific environment, rather the ones who imperfectly adapt, because these have more room to change when the environment changes.)

So usually when I am implementing something, first I strive to get it working - somehow, anyhow. Do the simplest thing which could possibly work. When I am confident that it is working (to some extent - it may not be fully implemented yet), I look and smell the code and if I smell anything bad, I refactor it.

E.g. I may have put a new method into an existing class which seems to be related to the new functionality, but working with that class feels awkward because it is just too big. So I start to look at what's in there, and discover duplication. After extracting the duplicated code, the class may become easier to handle. Or I may notice that the methods in the class are related to distinct responsibilities, and I am able to extract a new class to cleanly represent a single responsibility, thus making the rest of the old class cleaner and leaner.

Or I may also feel bad about my new method because it is hard to use: the code which actually uses it has difficulties getting hold of the right instance of the containing class. (Or - the other side of the coin - the method itself has difficulties reaching other objects/methods it depends on.) This makes me rethink where the method really belongs to. I may be able to find a better place for it where it is more naturally reachable for its clients / easily accesses its own dependencies.

This is by no means a one way street: it often happens that I revert some decision I made earlier - half an hour or half a year ago - when I realize it is not working well. Software systems evolve as the world around them changes, requirements come and go, the "hot spots" of a system move around. What seemed like a great solution half an hour or half a year ago may show its drawbacks now. Then I remove it, and try to find a better solution. I have learnt over the years to detach myself from my ideas and creations. No matter whose idea it was, it must stand the test of time on its own. If my idea turns out to be a bad one, I am happy to replace it with a better solution, even if it comes from someone else.

And this happens often, because I can't always have the right or best solution in mind. When I get stuck, I discuss the problem with my peers, either within the team, or on StackOverflow. Other people may have a better insight because they have different experiences, or simply because I am too close to the problem to see the solution. I may be stuck in a mental rut, thinking only about a certain class of solutions, while a more general approach would yield better results.

share|improve this answer
+1: 'Software development is a craft, thus one needs to practice it a lot, over an extended time, to make a lot of decisions and also to see the later consequences of those decisions.' ...absolutely. IMO, not enough programmers really see software development as a craft. I have always thought of myself as an artisan ...and code is my medium. – IAbstract Sep 9 '11 at 21:18
Good musings. I constantly refactor but I also thought it was because I am constantly making mistakes as to where I am putting the applicable properties and methods, hence this question on how to determine where those should be. Although I struggle still, glad at least my evolution process might be down the right track. – dreza Sep 10 '11 at 21:08

Something that I always ask myself (scoff if you will) before even thinking about a design or implementation (especially if it's part of personal projects) is: has this already been done by someone else and is it publicly available?

The more OS libraries I come across, the more I'm impressed with the overall quality (not saying, of course, that there aren't plenty of horrible ones too ;)).

Most books on software design don't seem to stress that point enough (or at all). There's no point in designing and building something when there's something written by someone with a lot more experience than you in a given domain when you can simply leverage their work.

For example (in a Python world):

Do I need database access? Cool.. SQLAlchemy is there already. Caching? Beaker. Awesome.

etc etc etc

share|improve this answer
This goes both ways, oftentimes you spend weeks to get into API, then stuff starts breaking and you see that some limitation of the framework simply won't let you do something. Think MFC if you don't believe me. I'd say there's 50-50 chance that the lib/framework will do more good than harm. – Coder Sep 8 '11 at 14:31
+1 for advocating "standing on the shoulders of giants!" – John Tobler Sep 8 '11 at 20:20
Yesterday I had to explain to a client why I used protobuf for serialization/deserialization (in C++) instead of writing it from scratch. – Chris Pitman Sep 9 '11 at 23:09
Good suggestion on looking for existing implementations. I guess my question was more focused around specific class design and where methods,properties exist and how to determine the ownership and accessibility of these. – dreza Sep 10 '11 at 21:10

1) Mandatory questions about design

  • Who will use my application?
  • What are the expected features?
  • How will they (ab)use my application?

2) Abstract data

  • Which language is best suited to describe and solve this project problems?
  • What structures best represent needed data?
  • What methods better describe and solve problems?

3) Prototype

  • Create basic UI with only needed functionalities
  • Create basic code to implement most important features

4) Try another design

  • Repeat steps 2 and 3 until you find the best solutions

5) Implement details

  • Divide the project in small pieces
  • Implement code and UI details
share|improve this answer
You get a +1 for starting with the user, also! – John Tobler Sep 8 '11 at 20:19

Just a few thoughts on this ...

  1. Understand what I am trying to code. If this is an application that interacts with a user, then I would need to brainstorm with my users and understand their need (usually a question would be like, what would you like for this application to do? what would you like for it to look it? (maybe throw in some suggestions).
  2. Understand the process flow. What is the flow of this program ? If a user interacts with this application what is the expected outcome? if this is a batch program what is the computational logic, etc.
  3. Data Model - what is the underlying data model, am i duplicating data?
  4. Existing Libraries - there might be other libraries (either external or internal) that might be doing what i need already, look for them and don't invent the wheel.
  5. Bring it all today and create an action plan - this could be a check list of things to do it order or in parallel and check it off as it is completed.
share|improve this answer
+1 for starting your approach with the user! – John Tobler Sep 8 '11 at 20:18

I just blogged about this the other day. Read post here.

Step 1: I usually start backwords and Identify the Output first. I have found wether it's for myself or a client that one thing is always certain... people know what the final outcome or product should be.

If the output is vague or not clearly defined then there's no need to even begin. If there is uncertaintity as to what "exactly" the output should be then have them draw a picture or show you a web page that details the specific desired output.

Step 2: Once the output has been established, then it's a matter of determining what inputs are required in order to produce that output. Inputs can be data elements, formulas decisions based on "Input A" versus "Input B". Stuff like that.

Step 3: The key to the whole process is step 3. Ask yourself a simple question... Do I have enough inputs to create that output? If the answer is no then stop right there and get those missing answers. I don't waste time writing code until I can answer Yes in step 3.

You might be thinking wow, thats great for small pieces what about the big picture. Okay, break the big picture into smaller pieces. You can always break big stuff into little chunks that are clearly defined.

Just my $0.02

share|improve this answer
couldn't find your blog on that link. – dreza Sep 10 '11 at 20:56

Understand what you are trying to do. Sketch out a logical progression using comments. Start filling out the comments with actual code. If it starts getting clunky, break out separate pieces of functionality into distinct classes. Keep breaking the problem into ever smaller parts. When you have a part that works well, split it out to a distinct class so it will continue to work well when everything left still changes.

For me, what typically happens is the structure will prove itself - i.e. There have been many times when after coding a nearly complete solution, the entire problem domain gets turned upside down. Same processing, but radically different progression - and yet all I have to do is re-arrange my classes, without having to touch any of their details.

So, I would say that you want to design your code in such a way that it provides the highest level of adaptability to change - this is going to equate to having a high level division of responsibility. Each class is performing one behavior and doing it well - the application performs its function by choreographing the interaction between these classes.

What I think really helps me the most is that I actually hate to type. This means I am doing things in the least amount of code possible and re-using everything I can. And by re-using, I do not mean copying and pasting.

By simply not allowing yourself to duplicate any (meaningful) code, you will arrive at a very strong object oriented structure. Try it - this is possibly the single most overlooked technique in creating robust software. If you follow it, it will force you to utilize various OO techniques so as not to have to copy and paste, and when you are done, you will have exactly one set of code supporting each distinct piece of functionality - and that is a very good place to be as your code will be highly adaptable to change.

Inherent in all of the above, in case it was not noticed, was that most of the changes and factoring are happening iteratively in a micro focus as the code is being developed. Start with a design as a guideline, but know that it won't survive reality.

share|improve this answer
Thanks for the feed backback rpbarbati. I do try to implement DRY as much as I can. I find though that it doesn't necessarilly help me with the overall design but I'm still working on it... – dreza Feb 12 '13 at 19:13
  1. What kind of data do I have to model?
  2. What are their relations?
  3. How big are the data? Could it fit into memory?
  4. What kind of data structures might be applicable? (There might be several.)
  5. What kind of operations do I need?
  6. Which kind of sub-systems will I need? (This could be a database, HTML generation, HTTP server, etc..)
  7. Which existing solutions can I use for these sub-systems? (Maybe there is even a framework that could solve several at once.)

After this, I start with a simple prototype that demonstrates that the technologies chosen work together as intended. It will most likely only support one or two kinds of data and one or two operations.

If this doesn't work out, I try to find better solutions. Otherwise, I flesh out the whole by adding new kinds of data and new operations.

share|improve this answer

In designing software, I focus on 1) data 2) computations and 3) logic, in roughly that order.

The first issue is, what data to I need to deal with? How is it structured, and how should I define it?

The second issue is, what computations do I need to have performed in order for the program to work.

The final thing is, how does it all come together. Where do I need my loops, links, and logical branches that result in "jumps?" This last group of processes control the first two, and the data flow is controlled by the computations and logic.

share|improve this answer

Although the discussion of this technique is often in the context of quality and improvement, the Five Whys technique can be applied to a number of situations, including requirements engineering, design, and testing. The idea is that you ask the question "why?" until you get to the root cause of a problem or understand the reasoning behind a particular solution.

share|improve this answer
+1 for Five whys. Still working on how I can apply that to my question but gives me something to think about – dreza Sep 10 '11 at 21:00
@dreza Specifically during design, if you have something that doesn't seem quite right or you are questioning the use of a particular pattern or structure, asking why enough times should lead you back to a requirement. Ultimately, the traceability back to a requirement is what matters, and applying Five Whys should get you back to that requirement. There are other methods, though. – Thomas Owens Sep 11 '11 at 4:30

Here's the stuff I use: (requires experienced programmers and good testing)

  1. What are the 7 words that fully describe the requirement?
  2. Where in the existing codebase it needs to be in?
  3. What is the complexity?
  4. How many separate changes in the codebase are needed?
  5. After doing the changes, will it work?
  6. How much it will cost?
  7. Is it really worth all that money?
  8. If we start doing it now, when will it be ready?
  9. Make it happen, now.

On more programmer level, the same stuff goes like this:

  1. What are those 7 words?
  2. Where's my notepad?
  3. Which file was it?
  4. What's the class name?
  5. Can you give me some coffee?
  6. How did it end up this complex?
  7. Can we make it compile?
  8. I already verified it by using correct function prototypes, why are they still testing it?
  9. Can we send them some screenshots so the managers don't panic

If you want the regular design idea list, it'd be something like this:

  1. language features required
  2. number of states and state transitions needed
  3. are all necessary api's available
  4. does the apis actually work for this situation?
  5. what stuff is needed from other teams?
  6. how many lines of code did it take to write it?
  7. how easy is it to use?
  8. what stuff is still missing?
  9. where is my deadline?
  10. what other things we need to get done before april 1st?
share|improve this answer
ha, lovin the coffee +1 – dreza Sep 13 '11 at 21:11

I always try to keep my code DRY. For me it's a single principle than's helped me more than anything else.

share|improve this answer
So, you have one method that contains logic that will be used in 3 different places. How do you come up with deciding whether to create a class, make that method public of an existing class, make it a static class? i believe I understand DRY, just often not the places where it's implementation is to go – dreza Sep 8 '11 at 6:51
@dreza: if you don't know which to choose, then it probably doesn't matter Pick one and if it turns out to be less that optimal for some future requirement, then change. Great software is not designed as much as it is carefully grown. – kevin cline Sep 8 '11 at 9:23
@dreza: for DRY to work, code has to be in one place, and programmers need to be able to find that place, so they don't duplicate it. So, I wouldn't put it in a static class unless either classes are first class objects, or else it wouldn't make sense for anyone to extend or substitute the logic, and then easy to find would be the next tie breaker. That could easily still leave it a tie, but kevin cline is right, if all tie breakers fail to resolve it then stop agonizing and pick randomly - it won't matter that much. – psr Sep 8 '11 at 18:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.