Re: Managing a project as it scales
- From: Joseph M. Newcomer <newcomer@xxxxxxxxxxxx>
- Date: Thu, 15 Dec 2005 11:36:40 -0500
On 14 Dec 2005 07:52:31 -0800, "Alexander" <the44secs@xxxxxxxxx> wrote:
>> Usually it is the interaction that is the problem, and this can be solved by minimizing
>> the number of interactions of the classes. For example, avoiding dumping everything into
>> the CWinApp class, avoiding public variables in classes, avoiding ever having a dialog
>> touch anything outside its own class, etc. Managment is largely dealing with complexity,
>> and good programming modularizes components and reduces complexity.
>
>So far so good.
>
>> Usually class-related problems deal with complex subclassing and inheritance, and one way
>> to avoid this is to not subclass deeply or have complex overrides.
>
>This will be useful. Thank you.
>
>> If something as small as 10 dialogs and 50 classes is starting to "get out of hand",
>> you've got some bad development methodologies.
>
>The reason of my posting.
>
>I figure you guys must have developed strategies based on your
>experience with large projects (or be familiar with established
>practices, if they exists) and that these would be of benefit even in
>amateur projects such as mine.
***
Its all in the patterns.
Look for books on design patterns. Once you adopt good design patterns, you will find
that most problems fall into a small number of categories, and you can apply and re-apply
the patterns again and again. As you grow familiar with the patterns, you find that you
have a fairly small toolchest of well-understood tools, rather than redesigning a new tool
each time you tackle a problem. And that means you end up writing a lot of code; there's
nothing like experience.
Mostly the key is to keep modularization very high, and module boundaries very tight. For
example, no global variables is a good starting point. And lumping everything into the
CWinApp class essentially violates module boundaries and puts syntactic sacharrine (not
even as good as syntactic sugar) on global variables.
I have a lot of patterns I use, but it is also important to recognize anti-patterns,
techniques which are huge red flags that you're doing something wrong. If you take
certain principles as guiding principles (.e.g, "no global variables, ever") then you are
forced to think carefully if an apparent need for a global variable arises. It might be
exactly the right answer to your problem, but you first have to say "This is probably a
mistake. Can I avoid making this mistake?" and most of the time there's a better way.
When you finally get to the point where you have to actually use a global variable, you
will know that at that point it really is, in spite of its reputation as an anti-pattern,
the best solution. Knowing anti-patterns will keep you from falling into bad practice.
Many programs have variables which represent the entire program state relative to some
module. These variables might be declared as static class members, although C++
methodology suggests singleton classes would be a better solution. But the technique of
void SomeClass::SomeMethod(int x0, int y0)
{
x = x0;
y = y0;
DoOtherStuff();
}
void SomeClass::DoOtherStuff()
{
... = f(x, y);
}
where you set a member variable such as x or y and then use it in other methods has got to
go. If DoOtherStuff needed x and y, you pass them as parameters. The classic argument
against this is "efficiency", and those who teach this as a principle should be deemed
incompetent to teach. I have spent years trying to break programmers of this bad habit.
Just maybe this technique once made sense on a PDP-11 (a machine whose clock cycle was
expressed in KHz, not GHz, and where performance was measured in the number of clock
cycles required per instruction, rather than the number of instructions per clock cycle),
but in a modern architecture, parameter passing is sub-nanosecond and the cost is largely
irrelevant, except in very rare and esoteric cases. Using this technique rapidly results
in unmanageable code.
Back when I programmed in assembly code, the most important tool we had was the
cross-reference listing, that told us who was referencing a variable. If you need a
cross-reference listing today, it means you need to rethink your design methodology,
because everything you need to see should be within one method, or within one class. After
that, you worry about who is using the interfaces, and that should be a small number of
sites in your code.
If you ever write ((CMyWinAppClass *)AfxGetApp())->anything as an expression, you have
probably made a fundamental design error. The only class that should be manipulating
member variables of your CWinApp class is that same CWinApp class (the exceptions to this
are very few). I have a program of 130K source lines with zero global variables and no
uses of CWinApp member variables outside the CWinApp class. It didn't even occur to me
that I'd done this until somebody was complaining about needed 400 variables moved to a
DLL but shared with the executable. That's probably 399 more global variables than should
be needed, and absolutely 400 more than should exist in a DLL interface.
Try to use set/get methods instead of manipulating member variables directly in a class.
When possible, avoid new/delete in favor of techniques that are more robust, particularly
in the presence of exceptions, e.g.,
int n = c_MyList.GetSelCount();
int * p = new int[n];
c_MyList.GetSelItems(n, p); // or p, n, I forget...but that's why there's docs
...
delete [ ] p;
should be avoided; instead do
int n = c_MyList.GetSelCount();
CArray<int, int>p;
p.SetSize(n);
c_MyList.GetSelItems(n, p.GetData()); // or vice-versa, see above
Note that there is no need to worry about the delete, because the destructor for the
CArray object will automatically delete the storage if an exception is thrown. Use
new/delete only when the lifetime of the value exceeds the scope of a function.
A dialog never, ever touches any variable that is not declared in its class. Exception:
static variables declared const.
No control of a dialog is ever manipulated by other than the dialog class. Ever.
No use of UpdateData. No exceptions. No use of GetDlgItem. Occasional rare exceptions
for arrays of identical controls, such as buttons (this exception can come up as often as
once every two years).
The only communication with a dialog is by setting and retrieving values from member
variables around the DoModal, e.g.,
CWhateverDialog dlg;
dlg.m_This = ...;
dlg.m_That = ...;
dlg.m_Other = ...;
if(dlg.DoModal() != IDOK)
return; // or suitable recovery
... = dlg.m_This;
... = dlg.m_That;
Some will argue that this is already poor style, and I have a lot of sympathy with that
position; they will argue that the initial assignments should be in the constructor, and
the retrieval should be via methods. Generally, my dialogs are sufficiently simple that
the naive code above is usually sufficient, but I would in no way argue against someone
who took the more conservative position of constructor/methods.
There are a lot of other techniques that can apply, such as how to manage CFormViews with
an attached CDocument to guarantee that the document is (sometimes virtually) in sync with
the contents of the control, or techniques for managing thread communication and
synchronization. You should always code Unicode-aware, and forget that LPSTR, LPCSTR,
char, char *, etc. exist as data types, except in rare and exotic situations.
Not everyone agrees with all of my principles. But I developed these over the years,
starting with raw Win32 API and proceeding into MFC, and they result in programs that are
intellectually manageable because I isolate behavior and have well-established
communications mechanisms for situations where information must be exchanged. There's a
collection of essays on my MVP Tips site that discuss many of these in great detail.
A typical project I write for a client will be around 100K source lines, a huge number of
classes (it varies, but 100 classes is not out of line), and take between 500 and 1000
hours of effort, depending on how good their specs were (poorer specs mean longer
development times). This translates into three to six months of realtime. I have to use
tehcniques that mean that at the end of the project I still have intellectual control over
the parts I wrote in the first week. One project came back to me in 2004 ("We have a new
line of controllers, can you enhance the program to handle the new features?") which I had
not seen since 1999. You need to write code that is not only intellectually manageable
when you are working on it, but that is clear enough (or well-documented enough) that you
can still understand it after having ignored it entirely for five years. (If I hadn't
done a picture of the key data structure using the traditional "ASCII Art" of -+|><^v
symbols, I'm not sure I could have reverse-engineered it in reasonable time). Code that
is easy to write is easy to maintain. Simple code is better than efficient code, unless
efficiency matters ("Efficiency never matters until it matters. Then it matters a lot",
which translates as "don't worry about it until you know it is a problem. Only then do
you start to worry about it")
Example of not worrying about efficiency: I had an application where we had to do an FFT
on some integer data. The FFT subroutine wanted arrays of doubles. As a
proof-of-concept, I allocated an array of doubles, converted the integer array to doubles,
passed it to the FFT subroutine, scanned the resutling spectral data for peak values,
scanned the data to normalize it so the peak was 100%, converted the data back to integers
in the range 0..100 for plotting, and plotted the result. Figured I worry about
efficiency if it mattered. The whole sequence ran so fast that I never had to change the
simple algorithm; we track audio so close to realtime (relative to the user's interaction
with the samples) that the simple approach was sufficient. Don't try to "optimize" unitl
you have substantiating data that tells you where to optimize; pre-optimization usually
results in code that is hard to write, hard to debug, and hard to maintain. Only code
that is solving hard problems (and sometimes efficiency is THE hard problem!) should be
difficult to deal with. Make everything else as simple and obvious as possible, while
maintaining class structure and interfaces.
On the other hand, plotting an audio waveform required that I compute the number of audio
samples per pixel, and for each x-coordinate in the display, display the *average* of all
the samples represented in that pixel; otherwise the plotting time for a 20-second sample
of 44.1KHz data made the display unusable. You could actually see the points being
plotted. Efficiency mattered there, and by reducing the number of GDI calls I had one
call per x-coordinate, not one call per sample point (to plot 1 second of audio data would
rqeuire 44,100 GDI calls to plot every sample, but if the user had sized the app to be,
say, 441 pixels wide, I only needed 441 GDI calls, a reduction by a factor of 100.
Reducing costs by 3% ususally doesn't matter. Reducing them by two orders of magnitude
matters). Spend your time getting orders of magnitude performance increase, and write the
rest of the code clearly and cleanly; the 3% efficiency loss will never be noticed, and
your resulting code will be easier to write, easier to debug, and easier to maintain.
Most structural errors I see are the result of applying obsolete design principles that
created efficient C code on PDP-11s to modern hardware architectures like 3GHz
highly-pipelined superscalar machines (such as the Pentium IV) and modern programming
languages (like C++), in modern software architectures (like the use of modularization and
multithreading).
joe
****
>
>Just trying to learn.
Joseph M. Newcomer [MVP]
email: newcomer@xxxxxxxxxxxx
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
.
- Follow-Ups:
- Re: Managing a project as it scales
- From: Gerry Quinn
- Re: Managing a project as it scales
- References:
- Managing a project as it scales
- From: Alexander
- Re: Managing a project as it scales
- From: Joseph M . Newcomer
- Re: Managing a project as it scales
- From: Alexander
- Managing a project as it scales
- Prev by Date: CRegkey and thread safety
- Next by Date: MDI app view problem
- Previous by thread: Re: Managing a project as it scales
- Next by thread: Re: Managing a project as it scales
- Index(es):