The fundamental rule of laziness is: Don't do it unless you have to.
June 30, 2006
URL:http://www.drdobbs.com/cpp/in-defense-of-laziness/189401941
Pete is a consultant specializing in library design and implementation. He has been a member of the C++ Standards Committee since its inception, and is Project Editor for the C++ Standard. He is writing a book on the newly approved Technical Report on C++ Library Extensions to be published by Addison-Wesley. Pete can be contacted at [email protected].
Overloading has been a theme in my life for the past year. Those of you who remember my first column for Dr. Dobb's, back in the April issue, have heard it before: Writing a book, moving from Massachusetts to Indiana, buying a house, selling a house, getting married, changing job relationships, going to Standards Committee meetings, editing the draft C++ Standard, writing this column. But I think the worst is nearly over. A month ago I was in Berlin for a C++ Standards Committee meeting; the next one is now a long five months away. Two weeks ago I sold my house. Yesterday I sent the penultimate draft of the book off to the publisher [1]. Later today I leave for Bostonthe wedding is the day after tomorrow. How did I get through it all, you ask. It was easy: I'm lazy.
The fundamental rule of laziness is: Don't do it unless you have to. But that's not a justification for your teenagers refusing to clean up their rooms until you yell at them. There's a more principled meaning to "have to" in that ruleit means setting priorities with an eye toward long-term goals. One of the hardest things to learn, in programming and in life, is that some things are less important than others. Do the things that are most important, and let the things that are less important go. Be lazy.
There was a discussion recently on comp.std.c++ about the best way to write code that loops through the contents of a vector using an index. The natural way to write that loop is:
for (int i = 0; i < vect.size(); ++i) // do whatever you need to do with i
But vect.size() might be a little slow, and if you change to a different kind of container, it might not even be constant time. So some people do this:
const int limit = vect.size(); for (int i = 0; i < limit; ++i) // do whatever you need to do with i
But that makes limit visible to the rest of the code, and someone later on might misuse it. So try this:
{ const int limit = vect.size(); for (int i = 0; i < limit; ++i) // do whatever you need to do with i }
Now limit goes away at the end of the block, and there's no need to worry about excessive scope.
There were several other variants, all dealing with equally unimportant issues. For a vector, size() is trivial. And it's an inline function. Just write the code and get on with the important part, which is making the application work.
That wasn't a particularly popular position. It drew some rather condescending comments from people who think that the goal of programming is perfection, and who are willing to spend inordinate amounts of time debating how best to achieve it. If you write code for a living, you can't afford to do that. You have to decide what's important and what isn't, and focus on what's important. Don't try to do everything. Only do the things that matter. Be lazy [2].
One of the best aids to laziness that I've come across is incremental development. Instead of trying to grasp the full complexity of a project, start with a simple part. Begin by writing test code for it, then implement it. Run the tests. And as you add complexity, keep running the tests. As you learn more about the intricacies of your project, you'll probably have to go back and rewrite the code several times. Running the tests helps assure you that you haven't broken anything. Or, if you're less fortunate, it helps you find the things you broke.
Incremental development means not writing more code than you have to. For example, one of the biggest problems that intermediate programmers run into is deciphering the error messages that compilers give them when they write template code [3]. The best way to write template code is to not do it until you have to. That way you don't spend time parsing obscure error messages before you know what you're trying to do. Get the code working with one simple type, so you know that you've got the fundamentals down. Then, if necessary, turn it into a template. Separating development of the computation itself from its expression as a template simplifies your job. Especially if it turns out that you don't need a template, after all.
Don't give in to the temptation to write templates. I know, templates are exciting. They're the hot new technology, and you're nobody if you're not writing templates. But the next time you're tempted to write a template, ask yourself how you're going to use it in your current project. If you're only going to instantiate it for one type, don't make it a template. Resist the temptation to generalize. You can do that later, if you need to. One type plus a suspicion that you might use it with another type later should not turn your code into a template [4]. Be lazy.
Think of yourself as a resource-starved CPU. If you were writing code for that CPU, you'd make sure that you didn't waste cycles or memory [5]. You've seen many of the basic techniques:
char buffer[BUF_SIZE]; void expensive_function() { // code using buffer } void cheaper_version() { static char *buffer = new char[BUF_SIZE]; // code using buffer }
The second version of this function is cheaper if it's never called: It doesn't tie up memory for a buffer that's never used. [6]
double really_slow_thing(double); double cached_version(double arg) { static double cached_argument; static double cached_result; static bool cache_valid = false; if (!cache_valid || cached_argument != arg) { cache_valid = true; cached_argument = arg; cached_result = really_slow_thing(arg); } return cached_result; }
If you end up calling really_slow_thing with the same value several times in succession and it doesn't have any side effects, caching the result avoids having to recompute the same value, freeing the CPU for other things, at the cost of additional code and data.
double r0 = really_slow_thing(0.0); double r1 = really_slow_thing(1.0); double r2 = really_slow_thing(2.0); double precomputed_version(double arg) { return arg == 0.0 ? r0 : arg == 1.0 ? r1 : arg == 2.0 ? r2 : really_slow_thing(arg); }
When you know in advance that you're going to call really_slow_thing several times with values from some well-defined set, you can speed things up by precomputing the result for each of those values [7]. However, this increases the application's startup time, which could be a problem [8].
Of course, the danger in being lazy is that you might not get the priorities right, and you might not have as much time as you'd like for something that slipped off of your schedule. If you're a resource-starved CPU, this is a disaster. If you're a programmer (or a writer), and you're lucky, you'll have a forgiving boss (or editor) who will let you slide, just this once.
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.