views:

876

answers:

6

For those who haven't read Code Complete 2, the Pseudocode Programming Process is basically a way to design a routine by describing it in plain English first, then gradually revise it to more detailed pseudocode, and finally to code. The main benefit of this is to help you stay at the right level of abstraction by building systems top-down rather than bottom-up, thereby evolving a clean API in distinct layers. I find that TDD is less effective at this, because it focuses too much on doing the bare minimum to get a test to pass and encourages little up-front design. I also find that having to maintain a suite of unit tests for unstable code (code that's constantly being refactored) is quite difficult, because it's typically the case that you have a dozen unit tests for a routine that's only needed once or twice. When you do refactor - change a method signature, for example - most of the work you do is in updating the tests rather than the prod code. I prefer adding unit tests after a component's code has stabilized a bit.

My question is - of those who've tried both approaches, which do you prefer?

+4  A: 

With Test Driven Development you should still be doing some planning in the beginning. It should at first be a high level look at what you're trying to do. Don't come up with all the details, but get an idea in plain English of how to solve the problem.

Then start testing the problem. Once you've got the test in place, start to make it pass. If it isn't easy to do, you may need to revise your initial plan. If there are problems just revise. The test is not there to define the solution it is there to allow you to make changes so you can have a better solution while ensuring the stability.

I would say the best bet is to use TDD. The key is to realize that TDD doesn't mean "skip the planning". TDD means do a little bit of planning to get started well, and adjust as needed. You may not even need to adjust.

Brendan Enrick
Why is TDD better? Can you edit your response with some explanation?
j0rd4n
+3  A: 

In general, I find pseudocode only really becomes relevant when the code required to solve the problem is much more complicated that the code required to test the solution. If this is not the case, I do not run into the difficulties you describe as the simplest thing that could possibly work is usually an acceptable solution for the amount of time worth spending on the problem.

If, on the other hand, the problem is complicated, I need to think through how to approach it before I can write even an initial naive solution - I still need to plan before I code; therefore, I use a combination of both approaches: an English description of what I will initially write, then a test harness, then naive solution code, then refinement.

moonshadow
+1  A: 

I've used both along with Big Upfront Development, all three have their places depending on issues such as language, team dynamics and program size/complexity.

In dynamic languages (particularly ruby), I highly recommend TDD, it will help you catch errors that other languages would have caught at compile time.

In a large, complex system, the more design you do upfront the better off you will be. It seems like when I designed for a large project, every area that I hand-waved and said "this should be pretty straight forward" was a stumbling point later in the project.

If you are working alone on something small in a staticly-typed language, the list approach is reasonable and will save you a good deal of time over TDD (Test maintenance is NOT free, although writing the tests in the first place isn't too bad)--When there aren't any tests in the system you're working on, adding in tests isn't always admired and you might even draw some unwanted attention.

Bill K
+1  A: 

Just because the test passes, doesn't mean you're done.

TDD is best characterized by Red - Green - Refactor.

Having a test provides one (of two) goal lines. It's just the first, minimal set of requirements. The real goal is the same goal as "Pseudocode Programming Process" or any design discipline.

Also, the TDD is driven by testing, but that doesn't mean driven blindly by testing. You can iterate your testing the same way you iterate your code. There's no place for dogmatic adherence to a dumb plan here. This an an Agile technique -- that means adapt it to your team and your circumstances.

Design enough code to have a testable interface. Design enough tests to be sure the interface will work. Design some more tests and some more implementation until you see the need to refactor.

The real goal is Good Software. TDD can't exclude "goodness".

A technique is not a restrictive mandate. A techniques should be looked as a crutch to help you product good code. If I were smarter, richer and better-looking, I wouldn't need TDD. But since I'm as dumb as I am, I need a crutch to help me refactor.

S.Lott
A: 

For me TDD has an ace pseudocoding just can't compete with - both help you abstract and plan the development, but once you're finished development in TDD land you still have the unit tests.

AS useful an approach as CC2 described pseudocoding is, it just can't match that. TDD is only half about designing, it's also providing a rigorous scaffold you can evolve the project forward from. However I see no reason why you can't pseudocode to solve the problems TDD sets.

I must not develop organically.
Pseudocode is the mind-killer.
It is the little-death that brings project memory oblivion.
I will face my 90's methodology.
I will permit it to pass over me and through me.
And when it has gone past I will turn the inner eye to see its path.
Where the pseudocode has gone there will be TDD.
Only unit-tests will remain.

(please don't flame me for that, I'm only half serious :P )

annakata
+1  A: 

My team mixes both approaches and it's an awesome way to develop (at least for us). We need unit tests because we have a large and complex software system. But the Pseudocode Programming Process is hands-down the best approach to software design I've come across. To make them work together:

  • We start by writing our classes, and fill in with fully commented method stubs, with inputs and outputs.
  • We use pair coding and peer review as a dialogue to refine and validate the design, still only with the method stubs.
  • At this point we've now both designed our system and have some testable code. So we go ahead and write our unit tests.
  • We go back in and start filling in the methods with comments for the logic that needs to be written.
  • We write code; the tests pass.

The beauty of it is that by the time we actually write code, most of the work of implementation is already done, because so much of what we think of as implementation is actually code design. Also the early process replaces the need for UML - class and method stubs are just as descriptive, plus it'll actually be used. And we always stay at the appropriate level of abstraction.

Obviously the process is never really quite as linear as I've described - some quirk of implementation may mean that we need to revisit the high-level design. But in general, by the time we write unit tests the design is really quite stable (at the method level), so no need for lots of test rewriting.

Ash Eldritch