views:

2924

answers:

23

I'm working on a large project (for me) which will have many classes and will need to be extensible, but I'm not sure how to plan out my program and how the classes need to interact.

I took an OOD course a few semesters back and learned a lot from it; like writing UML, and translating requirements documents into objects and classes. We learned sequence diagrams too but somehow I missed the lecture or something, they didn't really stick with me.

With previous projects I've tried using methods I learned from the course but usually end up with code that as soon as I can say "yeah that looks something like what I had in mind" i have no desire to dig through the muck to add new features.

I've got a copy of Steve McConnell's Code Complete which I continually hear is amazing, here and elsewhere. I read the chapter on design and didn't seem to come out with the information I'm looking for. I know he says that it's not a cut and dried process, that it's mostly based on heuristics, but I can't seem to take all his information and apply it to my projects.

So what are things you do during the high level design phase (before you begin programming) to determine what are the classes you need (especially ones not based on any 'real world objects') and how will they interact with each other?

Specifically I'm interested in what are the methods you use? What is the process you follow that usually yeilds a good, clean design that will closely represent the final product?

+4  A: 

It's oft repeated but completely true - understand your data.

For OOP your classes should describe salient pieces of information and how they interact.

If you have a mental model that well-describes the behaviour and lifetime of the data, you'll have an easy ride laying out your classes.

This is simply an extension of: Know exactly what you're trying to do.

Dave Gamble
Object behaviour is more important than data. This is a direct result of encapsulation: the heart of object oriented programming. Data exposure (from languages like C and Pascal) leads to systems that are difficult to maintain (enhance and debug) simply because you never know what other place in the system changes the data.OOP isn't about data; OOP is about behaviour. It is an important distinction.
Dave Jarvis
+1  A: 
nmiranda
i've used blue J... it's definately useful but how does it help me design classes and their relations?
Victor
I think it made me clear to see the whole picture on how to identify the classes and how to relate them visually,in my first steps I experimented how was the code representation of the objects and how to understand to think in objects. I remember when I dedicated time to figure out the "is a" and "has a" and the UML was an excellente help. Since then I use this visual tools to design my objects and when I discovered ActiveWriter I was very pleased cause BlueJ doesn't have code generation and this tool does, just to enforce my argument I think visually you have a broader approach to a solution.
nmiranda
+1  A: 

Honestly, a good step would be going back and looking at flow charting and sequence diagramming. There are a ton of good sites that show you how to do it. I find it to be invaluable when looking at how I want to break down a program into classes as I know exactly what the program needs inputted, computed, and outputted and each step can be broken down into one part of the program.

yuck... I HATE flowcharts lol
Daniel Kindler
I like flowcharts for when I get stuck on an issue. It sometimes helps me think about the problem in a different way.
Instead of or as well as flow-charts, *"Data Flow Diagrams" (DFDs)* are much more high-level: they're more like a UML deployment diagram, and suitable for getting insight into *system* functionality (i.e. the system's data input and data output, internal and external data storage, and data processing), and architecture. Flow-charts are (IMHO) closer in scope to modelling a single function with if-then-else statements.
ChrisW
Yeah, I use both most of the time usually Flow charts are primarily for when I am trying to figure out a particular issue.
Using swimlanes solves a lot of the problems with flowcharts. I've found that using one sequence diagram per scenario works best. Each scenario covers one specific path through the decision tree so there are no IFs in the flow. If you want to have a single diagram with all the flow, you'd have to include the decision points but it gets cluttered quickly, especially if you want to include responsibility assignment.
Kelly French
+11  A: 

When I had the chance, I normally use what I call the "three iterations rule".

In the first iteration (or startup), I devise the general layout of the application according to the model objects, the algorithms, and the expected (really expected, not maybe expected) future directions. I don't write design documents, but if I have to coordinate multiple people, a rough sketch of the procedure is of course needed, together with an analysis of dependencies and guesstimate of the time needed. Try to keep this phase to a minimum if, like me, you prefer a more agile method. There are cases where a strong design phase is needed, in particular when everything is known and true about the logic of your program, and if you plan to have a lot of interactions between features in your code. In this case, use cases or user stories provide are a good high level idea, in particular for GUI apps. For command line apps, and in particular libraries, try to write "program stories" in which you code against the library you have to develop and check how it looks. These programs will become functional tests of your library when completed.

After this first iteration, you will have a better understanding on how things interact, got out the details and the rough spots, solved issues with a slapped duct tape patch. You are ready to make use of this experience to improve, clean, polish, divide what was too large, coalesce what was too fragmented, define and use design patterns, analyze performance bottlenecks and nontrivial security issues. In general, all these changes will have a huge impact on the unit tests you wrote, but not on the functional tests.

When you complete this second iteration, you will have a little jewel, well tested, well documented, and well designed. Now you have both the experience and the code to do the third iteration, extend. You will add new features and use cases to improve your application. You will find rough spots and you will eventually enter a fourth iteration which is analogous to the second one. Rinse and repeat.

This is my general approach to software design. It is similar to spiral design, with short, three months iterations, and elements of Agile development, that allows you to learn the issues and get to know your software and its field of application. Of course, it's a matter of scalability, so if the application is so large to involve hundreds of developers, things are a bit more complex than this, but in the end I guess the idea is always the same, divide et impera.

So summing up:

  1. In iteration one, you get a taste of it, and learn
  2. In iteration two, you clean up your product and prepare it for the future
  3. In iteration three, you add new features and learn more
  4. goto 2
Stefano Borini
+26  A: 

The steps that I use for initial design (getting to a class diagram), are:

  1. Requirements gathering. Talk to the client and factor out the use cases to define what functionality the software should have.

  2. Compose a narrative of the individual use cases.

  3. Go through the narrative and highlight nouns (person, place, thing), as candidate classes and verbs (actions), as methods / behaviors.

  4. Discard duplicate nouns and factor out common functionality.

  5. Create a class diagram. If you're a Java developer, NetBeans 6.7 from Sun has a UML module that allows for diagramming as well as round-trip engineering and it's FREE. Eclipse (an open source Java IDE), also has a modeling framework, but I have no experience with it. You may also want to try out ArgoUML, an open source tool.

  6. Apply OOD principles to organize your classes (factor out common functionality, build hierarchies, etc.)

Scott Davies
thanks for making this, it lays out very clear and precisely the actions taken.
Victor
This is indeed a useful technique, especially when you do not have a real handle on the problem domain. However it rarely produces an optimal architecture.
NomeN
+4  A: 

Try using behavior driven development. It'll be hard to break your old habits, but I've found that BDD really is your best bet when it comes to developing in the real world.

http://behaviour-driven.org/

Eric the Red
+1 Using test/behavior/domain driven development allows you to create classes as you go so you will avoid the problematic large up front design waterfall methodology.
Halvard
+1  A: 

One useful technique is to relate your unique problem description to something you can find in the real world. For example, you are modelling a complex health care system that will take the world by storm. Are there any examples you can readily call upon to model this?

Indeed. Observe how the side pharmacy would operate, or the doctor's room.

Bring your domain problem down to something understandable to you; something to which you can relate.

Then once the "players" within the domain start to appear obvious, and you start to model your code, opt for a "provider-consumer" modelling approach i.e. your code is the "provider" of the model, and you are the "consumer".

Relating to the domain and understanding it at a high level is key part of any design.

Mike J
+1  A: 

I think the answer here should be very different depending on the real world experience of the guy asking.

If you have just one or two years work experience then you must go to the point that is: how do you get to the point you really know your data and understand exactly what you're trying to do?

Yeah, if you have been working in the real world 5+ years then you would choose between any of the many software development processes models or techniques.

But you don't get experience by reading books only. You should learn by working in a good group under a good leadership.

If that's not possible then you should just do by yourself. Begin iterating by coding a probably very nasty piece of code, learning your errors, dumping it all, coding a better one and so on.

You'll learn a lot about your codebase. The tools are tools, they will teach you nothing.

IlDan
+3  A: 

You asked question that lots of authors use to write a book. There is number of methodologies and you should pick one that seems "prettiest" to you.
I can recommend book "Domain Driven Design" by Eric Evans. Also, check site domaindrivendesign.org.

zendar
+1 - DDD is the future!
Charles Bretana
+2  A: 
  1. study & master Design Patterns.
  2. Next, learn about the Domain Driven Design
  3. After that, learn the requirement gathering

I took an OOD course a few semesters back and learned a lot from it; like writing UML, and translating requirements documents into objects and classes. We learned sequence diagrams too but somehow I missed the lecture or something, they didn't really stick with me.

  1. You know about the step 3. You need to master it. I mean, via a lot of practice to make it become your second nature. That's because the method you learn, is simply against the way we used to have. So you need to really master it. Otherwise, you will always find yourself go back to your original way of doing thing. This is somehow like Test Driven Process, where a lot of java developer give it up after a few tries. Unless they fully master it, otherwise it's just a burden to them

  2. Write use cases, especially for alternate course. Alternate course occupy more than 50% of our development time. Normally when your PM assign you a task, for instance, create a login system, he will think it's straight forward, you can take 1 day to finish it off. But he never take into account that you need to consider, 1. what if user key in wrong password, 2. what if user key in wrong password for 3 times, 3. what if user doesn't type in user name and etc. You need to list them out, and show it to your PM, ask him to reschedule the deadline.

janetsmith
+2  A: 
NomeN
+2  A: 

The most interesting source I know of regarding this is Part D of Object Oriented Software Construction, 2nd Edition by Bertrand Meyer.

Part D: Object-oriented methodology: applying the method well

19: On methodology, 20: Design pattern: multi-panel interactive systems, 21: Inheritance case study: "undo" in an interactive system, 22: How to find the classes, 23: Principles of class design, 24: Using inheritance well, 25: Useful techniques, 26: A sense of style, 27: Object-oriented analysis, 28: The software construction process, 29: Teaching the method

Interestingly, the chapter 22. How to find the classes is available online.

Daniel Daranas
+2  A: 

The technique I've used in real projects with reasonable success is Responsibility Driven Design, inspired by Wirfs-Brock's book.

Start with the top level user stories, and with colleagues, at a whiteboard, sketch the high-level interactions they imply. This gets you the first idea of what the big modules are; and an iteration or two of high level CRC-card like play you should have stabilised a list of major components, what they do and how they interact.

Then, if any of the responsibilities are large or complex, refine those modules down until you have things that are small and simple enough to be objects, by playing out the interactions inside the module for each of the major operations identified by the higher level interactions.

Knowing when to stop is a matter of judgement (which only comes with experience).

Steve Gilham
+1 for whiteboard, outstanding thing :DI solve 80% of problems sitting in front of whiteboard, just looking at it and thinking 'what would be the best?'
usoban
+2  A: 

I am afraid that this is not an answer people like to hear. Anyway, let me state my opinion.

OOP should be viewed as one of the paradigms, not as the superior paradigm. OOP is good for solving certain kind of problems, like developing a GUI library. It also fits into the style of software development usually followed by large software companies - an elite team of designers or architects lays down the software design in UML diagrams or some other similar medium and a less enlightened team of developers translate that design into source code. OOP offer little benefit if you are working alone or with a small team of highly talented programmers. Then, it is better to use a language that supports multiple paradigms and will help you to come up with a prototype fast. Python, Ruby, Lisp/Scheme etc are good choices. The prototype is your design. Then you improve on that. Use the paradigm that is best to solve the problem at hand. If needed, optimize hot spots with extensions written in C or some other systems language. By using one of these languages, you also get extensibility for free, not just at the programmer level but also at the user level. Languages like Lisp can dynamically generate and execute code, which means your users can extend the application by writing small code snippets, in the language that the software itself is coded! Or if you choose to write the program in C or C++, consider embedding an interpreter for a small language like Lua. Expose functionalities as plugins written in that language.

I think that, most of the time OOP and OOD create software that are victims of over design.

To summarize, my preferred way to write software is:

  1. Use a dynamic language.
  2. Write the design (prototype) in that language itself.
  3. If necessary, optimize certain areas using C/C++.
  4. Provide extensibility by way of the interpreter of the implementation language itself.

The last feature enables the software to easily adapt to specific user (including myself!) requirements.

Vijay Mathew
This is hardly advise about how to design
NomeN
I use a similar approach. To avoid becoming overwhelmed by the complexity, start with a helicopter view. I like a sketch with 8-20 functions. If I start getting more then I look at how to partition into sub-systems. Once I have that high level view I will decompose each function into 8-20 sub-functions, etc. By looking at the what those functions manipulate I get the top level classes. That is when I start laying out the skeletal system in Python, aka executable pseudo code. That together with the blocks of comments is my 'executable specification' which then gets progressively refined.
CyberED
A: 

Design Patterns

Creational Design Patterns

Singleton - Ensure that only one instance of a class is created and Provide a global access point to the object.

Factory(Simplified version of Factory Method)- Creates objects without exposing the instantiation logic to the client and Refers to the newly created object through a common interface.

Factory Method - Defines an interface for creating objects, but let subclasses to decide which class to instantiate and Refers to the newly created object through a common interface.

Abstract Factory - Offers the interface for creating a family of related objects, without explicitly specifying their classes.

Builder - Defines an instance for creating an object but letting subclasses decide which class to instantiate and Allows a finer control over the construction process.

Prototype - Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

Behavioral Design Patterns

Chain of Responsibiliy - It avoids attaching the sender of a request to its receiver, giving this way other objects the possibility of handling the request too. - The objects become parts of a chain and the request is sent from one object to another across the chain until one of the objects will handle it.

Command - Encapsulate a request in an object, Allows the parameterization of clients with different requests and Allows saving the requests in a queue.

Interpreter - Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language / Map a domain to a language, the language to a grammar, and the grammar to a hierarchical object-oriented design

Iterator - Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Mediator - Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

Observer - Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Strategy - Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Template Method - Define the skeleton of an algorithm in an operation, deferring some steps to subclasses / Template Method lets subclasses redefine certain steps of an algorithm without letting them to change the algorithm's structure.

Visitor - Represents an operation to be performed on the elements of an object structure / Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Null Object - Provide an object as a surrogate for the lack of an object of a given type. / The Null Object Pattern provides intelligent do nothing behavior, hiding the details from its collaborators.

Structural Design Patterns

Adapter - Convert the interface of a class into another interface clients expect. / Adapter lets classes work together, that could not otherwise because of incompatible interfaces.

Bridge - Compose objects into tree structures to represent part-whole hierarchies. / Composite lets clients treat individual objects and compositions of objects uniformly.

Composite - Compose objects into tree structures to represent part-whole hierarchies. / Composite lets clients treat individual objects and compositions of objects uniformly.

Decorator - add additional responsibilities dynamically to an object.

Flyweight - use sharing to support a large number of objects that have part of their internal state in common where the other part of state can vary.

Memento - capture the internal state of an object without violating encapsulation and thus providing a mean for restoring the object into initial state when needed.

Proxy - provide a “Placeholder” for an object to control references to it.

Sauron
Patterns are useful for some people. I think it needs considerable experience to see the patterns in the requirements. And you probably need to document them. I'm inclined to think that patterns are nothing more than abstract component libraries.
CyberED
A: 

First of all - design should come from your soul. You must feel it by your every fibre. I usually walk it down for two or three months before I start doing anything, Just walking down the streets (really). And thinking. Walking is a good meditation, you know. So it lets you to concentrate well.

Secondly - use OOP and classes only where a natural object hierarchy exists. Don't 'screw-in' it to that artificially. If no strict hierarchy exists (like in most business applications) - go for procedural/functional, or, at least use objects only as data containers with isolated accessors.

And the last - try to read this: The Algorithm of Creative Thinking

Thevs
+2  A: 

I don't have enough reputation to make comments yet (joined today) or I'd just comment on Scott Davies' answer. Adding to what he had to say:

  1. Make absolutely sure you know what your program is all about before you start. What is your program? What will it not do? What problem is it trying to solve?

  2. Your first set of use cases shouldn't be a laundry list of everything the program will eventually do. Start with the smallest set of use cases you can come up with that still captures the essence of what your program is for. For this web site, for example, the core use cases might be log in, ask a question, answer a question, and view questions and answers. Nothing about reputation, voting, or the community wiki, just the raw essence of what you're shooting for.

  3. As you come up with potential classes, don't think of them only in terms of what noun they represent, but what responsibilities they have. I've found this to be the biggest aid in figuring out how classes relate to each other during program execution. It's easy to come up with relationships like "a dog is an animal" or "a puppy has one mother." It's usually harder to figure out relationships describing run-time interactions between objects. You're program's algorithms are at least as important as your objects, and they're much easier to design if you've spelled out what each class's job is.

  4. Once you've got that minimal set of use cases and objects, start coding. Get something that actually runs as soon as possible, even though it doesn't do much and probably looks like crap. It's a starting point, and will force you to answer questions you might gloss over on paper.

  5. Now go back and pick more use cases, write up how they'll work, modify your class model, and write more code. Just like your first cut, take on as little at a time as you can while still adding something meaningful. Rinse and repeat.

Just my two cents. Hopefully it's useful.

Darryl
I am glad you made an answer as it would've been a monster of a comment ;-)
NomeN
thanks for this very detailed answer. I've been talking to people in person about this as well and this is very close to what i've been hearing is ideal for personal projects.
Victor
A: 

During my adventures of designing class structures, I’ve noticed that it’s very helpful to start with writing some pseudo-code. That means: I start with “writing” some general fragments of application’s code on a highest level, play with it, and discover the elements that are appearing – in fact, the elements that I – as a programmer – would like to use. It’s a very good starting point for designing general structure of modules and their interactions. After few iterations the whole structure starts to look more like a full system of classes. It’s a very flexible way to design parts of code. You can call it a programmer-oriented design.

Darius
A: 

If you have domain expertise on the project you are going to work on like say banking. It's easy to structure your objects and you know how those enhancements come every other day.

If you don't have that expertise work with someone who has that expertise and convert those ideas into technical details.

If you are confused about how to structure your project design. BLINDLY follow "pragmatic programmer" book. I was in same situation before, try reading a chapter from that book. you will see the difference, It will change the way you think as a software developer.

Broken Link
A: 

Learn design patterns. It has been my personal revolution the past two years regarding OOP. Get a book. I would recommend you this one:

Head First Design Patterns

It is in c# but it can be extensible to any language.

despart
A: 

Just quoting http://www.fysh.org/~katie/computing/methodologies.txt

And at the core of RUP is a small area where you have to use OO design talents.... if you don't have them, it's like having a methodology for running the 100m.

"Step 1: write about running really fast. Step 2: Go and draw a plan of the racetrack. Step 3: go and buy really tight lycra shorts. Step 4: run really, really, really fast. Step 5: cross line first"

It's that step 4 that's the tough one. But if you put lots of emphasis on 1,2,3 and 5 it's possible no-one will notice and then you could probably make a lot of money selling the methodology to would be athletes who think there's some "secret" to being a 100m runner over

A: 

I use Test-Driven Design (TDD). Writing the test first actually helps lead you to a design that is clean and correct. See http://en.wikipedia.org/wiki/Test-driven_development.

David Allen