views:

291

answers:

9

When I start writing code from scratch, I have a bad habit of quickly writing everything in one function, the whole time thinking "I'll make it more modular later". Then when later comes along, I have a working product and any attempts to fix it would mean creating functions and having to figure out what I need to pass.

It gets worst because it becomes extremely difficult to redesign classes when your project is almost done. For example, I usually do some planning before I start writing code, then when my project is done, I realized I could have made the classes more modular and/or I could have used inheritance. Basically, I don't think I do enough planning and I don't get more than one-level of abstraction.

So in the end, I'm stuck with a program with a large main function, one class and a few helper functions. Needless to say, it is not very reusable.

Has anybody had the same problem and have any tips to overcome this? One thing I had in mind was to write the main function with pseduocode (without much detail but enough to see what objects and functions they need). Essentially a top-down approach.

Is this a good idea? Any other suggestions?

+4  A: 

"First we make our habits, then they make us."

This seems to apply for both good and bad habits. Sounds like a bad one has taken hold of you.

Practice being more modular up front until it's "just the way I do things."

duffymo
+3  A: 

Yes, the solution is easy, although it takes time to get used to it. Never claim there will be a "later", where you sit down and just do refactoring. Instead, continue adding functionality to your code (or tests) and during this phase perform small, incremental refactorings. The "later" will basically be "always", but hidden in the phase where you are actually doing something new every time.

Stefano Borini
+1  A: 

I find the TDD Red-Green-Refactor discipline works wonders.

Avdi
+12  A: 

I take the completely opposite approach when writing something on-the-fly without a big design phase beforehand. That is, I write the main function the way it would look "in a perfect world" using imaginary objects and methods, then create skeletons of everything so the program compiles, then go back and make it work. This ensures a modular design, and the high-level code is very easy to understand. If anything, the drawback is the code can become too modular, as it's tempting to write doFoo() instead of implementing foo inline.

James M.
Yes, I follow this same approach. I think at the end of the day you can never knock modularized code, but in my experience the real benefit is manageability and just having a good all round structure. The Reuse aspects are minimal, but sure they exist.
JL
Exactly my approach. The only possible danger is that the code ends looking like this: main() { doFoo(); } foo() { doFoo(); } doFoo() { reallyDoFoo(); } reallyDoFoo() { reallyReallyDoFoo(); }
ammoQ
+2  A: 

My rule of thumb is that anything longer than 20 LoC should be clean. IME every project stands on a few "just-a-proof-of-concept"s that were ever intended to end up in production code. Since this seems inevitable though, even 50 lines of proof-of-concept code should be clear -- they might end up being the foundation of a big project.

My approach is top-down. I write

while( obj = get_next_obj(data) ) {
  wibble(obj);
  fumble(obj);
  process( filter(obj) );
}

and only start to write all these functions later. (Usually they are inline ans go into the unnamed namespace. Sometimes they turn out to be one-liners and then I might eliminate them later.)

This way I also avoid to have to comment the code: The function names are explanation enough.

sbi
A: 

You pretty much identified the issue. Not having enough planning. Spend some time analyzing the solution you're going to develop, break it down into pieces of functionality, identify how it would be best to implement them and try to separate the layers of the application (UI, business logic, data access layer, etc).

Think in terms of OOP and refactor as early as it makes sense. It's a lot cheaper than doing it after everything is built.

Mircea Grelus
A: 

Write the main function minimally, with almost nothing in it. In most gui programs, sdl games programs, open gl, or anything with any kind of user interface at all, the main function should be nothing more than an event eating loop. It has to be, or there will always be long stretches of time where the computer seems unresponsive, and the operating system thinks considers maybe shutting it down because it's not responding to messages.

Once you get your main loop, quickly lock that down, only to be modified for bug fixes, not new functionality. This may just end up displacing the problem to another function, but having a monilithic function is rather difficult to do in an event based application anyway. You'll always need a million little event handlers.

Maybe you have a monolithic class. I've done that. Mainly the way to deal with it is to try and keep a mental or physical map of dependencies, and note where there's ... let's say, perforations, fissures where a group of functions doesn't explicitly depend on any shared state or variables with other functions in the class. There you can spin that cluster of functions off into a new class. If it's really a huge class, and really tangled up, I'd call that a code smell. Think about redesigning such a thing to be less huge and interdependant.

Another thing you can do is as you're coding, note that when a function grows to a size where it no longer fits on a single screen, it's probably too big, and at that point start thinking about how to break it down into multiple smaller functions.

Breton
A: 

Refactoring is a lot less scary if you have good tools to do it. I see you tagged your question as "C++" but the same goes for any language. Get an IDE where extracting and renaming methods, extracting variables, etc. is easy to do, and then learn how to use that IDE effectively. Then the "small, incremental refactorings" that Stefano Borini mentions will be less daunting.

MatrixFrog
A: 

Your approach isn't necessarily bad -- earlier more modular design might end up as over-engineering.

You do need to refactor -- this is a fact of life. The question is when? Too late, and the refactoring is too big a task and too risk-prone. Too early, and it might be over-engineering. And, as time goes on, you will need to refactor again .. and again. This is just part of the natural life-cycle of software.

The trick is to refactor soon, but not too soon. And frequently, but not too frequently. How soon and how frequently? That's why it's a art and not a science :)

Larry Watanabe