tags:

views:

1063

answers:

12

I have used OO programming languages and techniques years ago (primarily on C++) but in the intervening time haven't done much with OO.

I'm starting to make a small utility in C#. I could simply program it all without using good OO practice, but it would be a good refresher for me to apply OO techniques.

Like database normalization levels, I'm looking for a checklist that will remind me of the various rules of thumb for a 'good' object oriented program - a concise yes/no list that I can read through occasionally during design and implementation to prevent me from thinking and working procedurally. Would be even more useful if it contained the proper OO terms and concepts so that any check item is easily searchable for further information.

What should be on a checklist that would help someone develop good OO software?

Conversely, what 'tests' could be applied that would show software is not OO?

+1  A: 

I like this list, although it might be a little dense to be used as a checklist.

Hooray Im Helping
+7  A: 

Steve McConnell's Code Complete 2 contains a lot of ready to use checklists for good software construction.

Robert C. Martin's Agile Principles, Patterns, and Practices in C# contains a lot of principles for good OO desgin.

Both will give you a solid foundation to start with.

ivo
+1 for Code Complete. Each chapter has a items on its checklist, I suggest checking it out.
lemon
+3  A: 

Make sure you read up and understand the following

  • Encapsulation
    • (Making sure you only expose the minimal state and functionality to get the job done)
  • Polymorphism
    • (Ability for derived objects to behave like their parents)
  • The difference between and interface and an abstract class
    • (An abstract class allows functionality and state to be shared with it's descendants, an interface is only the promise that the functionality will be implemented)
Alex
+7  A: 
  • Data belongs with the code that operates on it (i.e. into the same class). This improves maintainability because many fields and methods can be private (encapsulation) and are thus to some degree removed from consideration when looking at the interaction between components.
  • Use polymorphism instead of conditions - whenever you have to do different things based on what class an object is, try to put that behaviour into a method that different classes implement differently so that all you have to do is call that method
  • Use inheritance sparingly, prefer composition - Inheritance is a distinctive feature of OO programming and often seen as the "essence" of OOP. It is in fact gravely overused and should be classified as the least important feature
Michael Borgwardt
+6  A: 

One of the best sources would be Martin Fowler's "Refactoring" book which contains a list (and supporting detail) of object oriented code smells that you might want to consider refactoring.

I would also recommend the checklists in Robert Martin's "Clean Code".

Rob Scott
Refactoring is actually an awesome book
Nuno Furtado
+1  A: 

UML - Unified Modeling Language, for object modeling and defining the structure of and relationships between classes

http://en.wikipedia.org/wiki/Unified_Modeling_Language

Then of course the programming techniques for OO (most already mentioned)

  • Information Hiding
  • Abstraction
  • Interfaces
  • Encapsulation
  • Inheritance / Polymorphism
CRice
+32  A: 
  • Objects do things. (Most important point in the whole of OOP!) Don't think about them as "data holders" - send them a message to do something. What verbs should my class have? The "Responsibility-Driven Design" school of thinking is brilliant for this. (See Object Design: Roles, Responsibilities, and Collaborations, Rebecca Wirfs-Brock and Alan McKean, Addison-Wesley 2003, ISBN 0201379430.)
  • For each thing the system must do, come up with a bunch of concrete scenarios describing how the objects talk to each other to get the job done. This means thinking in terms of interaction diagrams and acting out the method calls. - Don't start with a class diagram - that's SQL-thinking not OO-thinking.
  • Learn Test-Driven Development. Nobody gets their object model right up front but if you do TDD you're putting in the groundwork to make sure your object model does what it needs to and making it safe to refactor when things change later.
  • Only build for the requirements you have now - don't obsess about "re-use" or stuff that will be "useful later". If you only build what you need right now, you're keeping the design space of things you could do later much more open.
  • Forget about inheritance when you're modelling objects. It's just one way of implementing common code. When you're modelling objects just pretend you're looking at each object through an interface that describes what it can be asked to do.
  • If a method takes loads of parameters or if you need to repeatedly call a bunch of objects to get lots of data, the method might be in the wrong class. The best place for a method is right next to most of the fields it uses in the same class (or superclass ...)
  • Read a Design Patterns book for your language. If it's C#, try "Design Patterns in C#" by Steve Metsker. This will teach you a series of tricks you can use to divide work up between objects.
  • Don't test an object to see what type it is and then take action based on that type - that's a code smell that the object should probably be doing the work. It's a hint that you should call the object and ask it to do the work. (If only some kinds of objects do the work, you can simply have "do nothing" implementations in some objects... That's legitimate OOP.)
  • Putting the methods and data in the right classes makes OO code run faster (and gives virtual machines a chance to optimise better) - it's not just aesthetic or theoretical. The Sharble and Cohen study points this out - see http://portal.acm.org/citation.cfm?doid=159420.155839 (See the graph of metrics on "number of instructions executed per scenario")
cartoonfox
+1. All good especially: "Don't start with a class diagram - that's SQL-thinking not OO-thinking", "Forget about inheritance when you're modelling objects" and "Only build for the requirements you have now".
Conor
TDD can lead to bad design, test everything you write, but first design and write it.
Danny Varod
Plan for everything (design for flexibility, implement by priority of requirements (and for performance)).
Danny Varod
**"Plan for everything" is the fastest route to failure.** You can't do it all - get the basics right and learn to build on it. That's what refactoring is for. Also, remember Knuth said "premature optimization is the root of all evil".
cartoonfox
**There's nothing wrong with TDD if you learn to do it properly.** It takes study and practice like anything else to do well.
cartoonfox
Nice checklist - I agree that the most important thing is to think in terms of objects and the messages they deal with. A book that has helped me enormously with OOP: Arthur Riel's "Object Oriented Design Heuristics" http://www.davegardner.me.uk/reading/oo-design-heuristics/
Dave
+1 for Responsibility Driven Design at the top of the list. Would give another +1 for all around good advice if I could.
Steve Gilham
+9  A: 

Gathered from various books, famous C# programmers, and general advice (not much if any of this is mine; It is in the sense that these are various questions i ask myself during development, but that's it):

  • Structs or Classes? Is the item you're creating a value of it's own, make it a struct. If it's an "object" with attributes and sub-values, methods, and possibly state then make it an object.
  • Sealed Classes: If you're going to be creating a class and you don't explicitly need to be able to inherit from it make it sealed. (I do it for the supposed performance gain)
  • Don't Repeat Yourself: if you find yourself copy-past(a/e)ing code around then you should probably (but not always) rethink your design to minimize code duplication.
  • If you don't need to provide a base implementation for a given abstract class turn it into an interface.
  • The specialization principle: Each object you have should only do one thing. This helps avoid the "God-object".
  • Use properties for public access: This has been debated over and over again, but it's really the best thing to do. Properties allow you to do things you normally can't with fields, and it also allows you more control over how the object is gotten and set.
  • Singletons: another controversial topic, and here's the idea: only use them when you Absolutely Need To. Most of the time a bunch of static methods can serve the purpose of a singleton. (Though if you absolutely need a singleton pattern use Jon Skeet's excellent one)
  • Loose coupling: Make sure that your classes depend on each other as little as possible; make sure that it's easy for your library users to swap out parts of your library with others (or custom built portions). This includes using interfaces where necessary, Encapsulation (others have mentioned it), and most of the rest of the principles in this answer.
  • Design with simplicity in mind: Unlike cake frosting, it's easier to design something simple now and add later than it is to design complex now and remove later.

I might chuck some or all of this out the door if i'm:

  • Writing a personal project
  • really in a hurry to get something done (but i'll come back to it later.... sometime..... ;) )

These principles help guide my everyday coding and have improved my coding quality vastly in some areas! hope that helps!

RCIX
+1  A: 
Danny Varod
I was actually in the room when Jeff Sutherland (creator of scrum) said that you need to implement TDD, pairing and all the XP practices for scrum to work at full efficiency. **Sorry but you're totally wrong about Scrum and XP.** TDD is a great way to learn OOP. You don't have to TDD - but it will be much harder without it.
cartoonfox
**I really wouldn't teach AOP or reflection or metaprogramming to beginners.** These are easy things to learn, but good design is about simpliciy and restraint. Unless you have enough practice of responsibility-driven design they'll just allow people to make a bigger mess. This what they were try to explain (in a humorous way in the http://www.failmanifesto.org/ )
cartoonfox
I was in the room with one of the other creators when he said the opposite :-)
Danny Varod
You mean Ken Schwaber right? You must have misunderstood him. He just took the XP practices out of the compulsory scrum stuff. What Sutherland is saying is that it was there to start with and you need it to make scrum work well.
cartoonfox
I didn't misunderstand him, he said it leads to a lack of design and he also said XP doesn't work.
Danny Varod
+5  A: 
  • Have I clearly defined the requirements? Formal requirements documentation may not be necessary, but you should have a clear vision before you begin coding. Mind-mapping tools and prototypes or design sketches may be good alternatives if you don't need formal documentation. Work with end-users and stakeholders as early as possible in the software process, to make sure you are implementing what they need.

  • Am I reinventing the wheel? If you are coding to solve a common problem, look for a robust library that already solves this problem. If you think you might already have solved the problem elsewhere in your code (or a co-worker might have), look first for an existing solution.

  • Does my object have a clear, single purpose? Following the principle of Encapsulation, an object should have behavior together with the data that it operates on. An object should only have one major responsibility.

  • Can I code to an interface? Design By Contract is a great way to enable unit testing, document detailed, class-level requirements, and encourage code reuse.

  • Can I put my code under test? Test-Driven Development (TDD) is not always easy; but unit tests are invaluable for refactoring, and verifying regression behavior after making changes. Goes hand-in-hand with Design By Contract.

  • Am I overdesigning? Don't try to code a reusable component. Don't try to anticipate future requirements. These things may seem counterintuitive, but they lead to better design. The first time you code something, just implement it as straightforwardly as possible, and make it work. The second time you use the same logic, copy and paste. Once you have two working sections of code with common logic, you can easily refactor without trying to anticipate future requirements.

  • Am I introducing redudant code? Don't Repeat Yourself (DRY) is the biggest driving principal of refactoring. Use copy-and-paste only as a first step to refactoring. Don't code the same thing in different places, it's a maintenance nightmare.

  • Is this a common design pattern, anti-pattern, or code smell? Be familiar with common solutions to OO design problems, and look for them as you code - but don't try to force a problem to fit a certain pattern. Watch out for code that falls into a common "bad practice" pattern.

  • Is my code too tightly coupled? Loose Coupling is a principle that tries to reduce the inter-dependencies between two or more classes. Some dependencies are necessary; but the more you are dependent on another class, the more you have to fix when that class changes. Don't let code in one class depend on the implementation details of another class - use an object only according to its contract.

  • Am I exposing too much information? Practice information hiding. If a method or field doesn't need to be public, make it private. Expose only the minimum API necessary for an object to fulfill its contract. Don't make implementation details accessible to client objects.

  • Am I coding defensively? Check for error conditions, and Fail Fast. Don't be afraid to use exceptions, and let them propagate. In the event your program reaches an unexpected state, it's much, much better to abort an operation, log a stack trace for you to work with, and avoid unpredictable behavior in your downstream code. Follow best practices for cleaning up resources, such as the using() {} statement.

  • Will I be able to read this code in six months? Good code is readable with minimal documentation. Put comments where necessary; but also write code that's intuitive, and use meaningful class, method, and variable names. Practice good coding style; if you're working on a team project, each member of the team should write code that looks the same.

  • Does it still work? Test early, test often. After introducing new functionality, go back and touch any existing behavior that might have been affected. Get other team members to peer review and test your code. Rerun unit tests after making changes, and keep them up to date.

RMorrisey
+5  A: 
  • SOLID
  • DRY
  • TDD
  • Composition over inheritance
eglasius
+6  A: 

Sounds like you want some basic yes/no questions to ask yourself along your way. Everyone has given some great "do this" and "think like that" lists, so here is my crack at some simple yes/no's.

Can I answer yes to all of these?

  • Do my classes represent the nouns I am concerned with?
  • Do my classes provide methods for actions/verbs that it can perform?

Can I answer no to all of these?

  • Do I have global state data that could either be put into a singleton or stored in class implementations that work with it?
  • Can I remove any public methods on a class and add them to an interface or make them private/protected to better encapsulate the behavior?
  • Should I use an interface to separate a behavior away from other interfaces or the implementing class?
  • Do I have code that is repeated between related classes that I can move into a base class for better code reuse and abstraction?
  • Am I testing for the type of something to decide what action to do? If so can this behavior be included on the base type or interface that the code in question is using to allow more effect use of the abstraction or should the code in question be refactored to use a better base class or interface?
  • Am I repeatedly checking some context data to decided what type to instantiate? If so can this be abstracted out into a factory design pattern for better abstraction of logic and code reuse?
  • Is a class very large with multiple focuses of functionality? If so can I divide it up into multiple classes, each with their own single purpose?
  • Do I have unrelated classes inheriting from the same base class? If so can I divide the base class into better abstractions or can I use composition to gain access to functionality?
  • Has my inheritance hierarchy become fearfully deep? If so can I flatten it or separate things via interfaces or splitting functionality?
  • I have worried way too much about my inheritance hierarchy?
  • When I explain the design to a rubber ducky do I feel stupid about the design or stupid about talking to a duck?

Just some quick ones off the top of my head. I hope it helps, OOP can get pretty crazy. I didn't include any yes/no's for more advanced stuff that's usually a concern with larger apps, like dependency injection or if you should split something out into different service/logic layers/assemblies....of course I hope you at least separate your UI from your logic.

Sean Copenhaver