📄 chapter7.html
字号:
<html><head><title>Writing Bug-Free C Code: General Tips</title></head><body><center><font size="+3">Chapter 7: General Tips</font><br><a href="index.html">Writing Bug-Free C Code</a><br></center><br><center><table><tr><td valign=top><small><a href="#designright">7.1 Design It Right the First Time</a><br><a href="#gooddesign">7.2 Good Design Beats an Optimizing Compiler</a><br><a href="#evolutionary">7.3 Evolutionary Coding</a><br><a href="#setgoals">7.4 Set Goals</a><br><a href="#codewhatnothow">7.5 Code What to Do, not How to Do It</a><br><a href="#noglobalvars">7.6 Virtually No Global Variables</a><br><a href="#loopvars">7.7 Loop Variables</a><br><a href="#highwarnings">7.8 Use the Highest Compiler Warning Level</a><br><a href="#usestatic">7.9 Use "static" to Localize Knowledge</a><br><a href="#blockvars">7.10 Place Variables in the Block Needed</a><br><a href="#stackarrays">7.11 Arrays on the Stack</a><br><a href="#pointers">7.12 Pointers Contain Valid Addresses or NULL</a><br><a href="#avoidtypecasts">7.13 Avoid Type Casting Whenever Possible</a><br><a href="#usesizeof">7.14 Use sizeof() on Variables, not Types</a><br><small></td><td width=30> </td><td valign=top><small><a href="#avoiddeepnest">7.15 Avoid Deeply Nested Blocks</a><br><a href="#smallfunctions">7.16 Keep Functions Small</a><br><a href="#releasedebug">7.17 Releasing Debugging Code in the Product</a><br><a href="#stacktrace">7.18 Stack Trace Support</a><br><a href="#singleexit">7.19 Functions Have a Single Point of Exit</a><br><a href="#nogoto">7.20 Do Not Use the Goto Statement</a><br><a href="#bulletproff">7.21 Write Bulletproof Functions</a><br><a href="#portablecode">7.22 Writing Portable Code</a><br><a href="#memorymodels">7.23 Memory Models</a><br><a href="#automatedtesting">7.24 Automated Testing Procedures</a><br><a href="#doctools">7.25 Documentation Tools</a><br><a href="#sourcecontrol">7.26 Source-Code Control Systems</a><br><a href="#monoscreen">7.27 Monochrome Screen</a><br><a href="#debugtiming">7.28 Techniques for Debugging Timing Sensitive Code</a><br></small></td></tr></table></center><br><br>No matter how good your tools that help you detect bugs in your programs, the goal of every programmer should be to avoid bugs in the first place. Debugging tools serve a purpose, but relying on the tools to catch your programming mistakes does not make you a better programmer. What makes you a better programmer is learning from your mistakes. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> A key to writing bug-free code is learning from your mistakes. </tr></td></table></blockquote>How do you rate your own ability to produce bug-free code? Whatever your answer is, how did you rate yourself last year? How do you expect to rate yourself next year? <br><br>The point of this exercise is to stress to you the importance of improving your coding techniques year after year. The goal is to be able to write more code next year than last year and to write it with fewer bugs.<br><a name="designright"><br></a><big><b>7.1 Design It Right the First Time</b></big> <br><br>If there is one thing that I have learned over the years that I would emphasize to a programmer just starting out, it is that old code rarely dies because it just stays around and around. This is especially true in large projects. And all successful small projects end up turning into large projects. <br><br>As time goes on in a large project, more and more code layers are built upon existing code layers. It does not take long before coding is no longer being done to the operating system level or windowing environment layer but to the layers that were coded on top of these system layers. <br><br>Now suppose that a year later someone discovers that the implementation of a low-level layer is causing performance problems. All too often management decides to live with the performance problem in favor of using their programmer's time putting new features into the product. You are forced by management to live with a lot of the coding decisions you make over the years, so your decisions had better be good! <br><br>Management may allow you to re-engineer a module to make it better and faster, but consider the lost time. The time that is attributed to the module is the original design time plus the time to re-engineer the module. Designing a module right the first time saves time. <br><br>It is not feasible to continually re-engineer old modules. If this does happen due to poor design decisions made upfront, a project will come to a halt. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Designing a module right the first time saves time. </tr></td></table></blockquote>My advice is to design a module right the first time because you'll rarely get the chance to re-engineer the module.<br><a name="gooddesign"><br></a><big><b>7.2 Good Design Beats an Optimizing Compiler</b></big> <br><br>An optimizing compiler helps a program run faster, but in all cases, a good design makes a program run fast. A great design produces the fastest program. A little more time spent on a programming problem generally results in a better design, which can make a program run significantly faster. <br><br>Over the last few years, I think all of us at one time or another saw a well-known product upgrade getting hammered by the trade press for how sluggishly the upgrade performed. Do not let this happen to your product. <br><br>And by all means, evaluate your competition. If your product is two to three times slower than the competition and this comes out in a magazine review, do you think potential customers will buy your product?<br><a name="evolutionary"><br></a><big><b>7.3 Evolutionary Coding</b></big> <br><br>How do you code a module? Do you spend days working feverishly on a module and have it all come together that last half day? Do you code one piece of a module, moving on to the next piece only when you are certain that the piece you just wrote is working? <br><br>Over the years I've tried both techniques and I can tell you from experience that coding a module a piece at a time is much easier. It also helps isolate bugs. If you wait until the end to put all the pieces together, where is the problem? By coding one piece at a time, the problem is more than likely in the piece you are working on and not in some piece you have already finished and tested. <br><br><b>7.3.1 Build a Framework</b> <br><br>Start coding a module by building a framework that is plugged into a system almost immediately. The goal in creating this framework is to get the module completely stubbed out so that it compiles. <br><br>Let's use the Dos module as an example and assume that the module interface has already been designed. The first step in creating the framework is to create the global include file. Next, create a source file that contains all of the sections of a module, but none of the guts. Namely, create the module and APIENTRY comment blocks without any comments, declare the class without any members and write all the APIENTRY functions with no code in the function bodies. <br><br>At this point, I place return statements in all functions, so that each function can return an error value. For example, I have DosOpenFile() and DosCloseFile() return NULL and DosRead() and DosWrite() return zero. <br><br>The framework is now complete. Compile the module and correct any errors that show up. Although no real code has been written, the framework provides you with a clear goal. <br><br><b>7.3.2 Code the Create and Destroy Methods</b> <br><br>After the framework is compiling successfully, you are ready to start implementing the module. Where should you start? The functions that I always tackle first are the create and destroy methods. This allows me to write some simple code that uses and tests the module. <br><br>In the case of the Dos module, the DosOpenFile() and DosCloseFile() functions are implemented first, followed by some test code. This test code allows verification that (1) opening an existing file works, (2) trying to open a file that does not exist fails and (3) calling DosCloseFile() works properly and frees allocated memory. <br><br>Most modules are not as simple as the Dos module was in determining the data items to add to the class structure. This is fine. Simply fill in the class structure with whatever data items are required by the create and destroy method function. When implementing other method functions, add data items to the class structure as they are needed. <br><br><b>7.3.3 Code the Other Methods</b> <br><br>Once the create and destroy methods are working correctly, it is time to start implementing the other method functions. The best strategy is to implement a method function, compile it and then test it by writing test code. <br><br>In the case of the Dos module, you may decide to implement DosRead() first. After doing so, you could then create a test file with a known data set and attempt to have the test code read this data set. This helps validate the DosRead() code. Likewise for the DosWrite() function. Write out some information to a file and verify that it was indeed written. <br><br>The order in which you code the method functions is totally up to you. After you gain experience using this technique, you will end up picking an order that allows for test code to be written easily.<br><a name="setgoals"><br></a><big><b>7.4 Set Goals</b></big> <br><br>Goal setting is always important to accomplishing any task you set out to do, but it is especially important that programmers have clearly defined goals. The problem is that all too often a programmer gets sucked into a project that is 90 percent done and weeks or months later the project is still only 90 percent done. <br><br><b>7.4.1 The 90 Percent Done Syndrome</b> <br><br>The problem with programming without a clear well-defined goal is that a project is not completed. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Programming without a goal is like a sailboat without a sail. You drift. </tr></td></table></blockquote>I have fallen into this trap myself. You work on items that relate to the ninety percent that is already done. You may have found a new feature to add and so you work on it, but the feature does not help you complete the project. Or you have found a better way to implement an algorithm, so you spend time recoding the algorithm. While finding a better algorithm is great, it only delays the project from being completed. <br><br>The module framework helps you set a clearly defined goal. Once the framework is in place, all the APIENTRY functions are stubbed out, just waiting to be completed. Once these APIENTRY functions are completely written and tested, you have reached your goal and the module is finished.<br><a name="codewhatnothow"><br></a><big><b>7.5 Code What to Do, not How to Do It</b></big> <br><br>The goal of this technique is to avoid spreading knowledge about how something is done throughout the entire project. By moving this knowledge to one function, you are isolating the knowledge. Now any code that calls this function is specifying what must be done, leaving the how to the function. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Never reinvent the wheel. Always code what to do, not how to do it. </tr></td></table></blockquote>It is hard to discipline yourself to use this technique, but the payback is well worth the extra effort. <br><br>Code that uses this technique becomes shorter and more compact. What used to take ten lines now takes five. This leads to a program that is easier to code, maintain and change. <br><br>A good analogy is building blocks. You are building something starting from scratch so you start out by making a few blocks. You use these building blocks to make the something. Now let's say that you want to build something else. The key is that you don't start totally from scratch because you've already built some of the basic building blocks. <br><br>Always keep your eyes open for a new building block to implement.<br><a name="noglobalvars"><br></a><big><b>7.6 Virtually No Global Variables</b></big> <br><br>In an object-based system, global variables (variables known to more than one source file) are almost never needed. The reasoning is simple. Objects, by design, are created and destroyed by method functions, so the objects themselves are never static. An advantage of object-based systems that dynamically allocate objects is that they are never limited by some predefined number. Instead, they are limited only by the amount of memory that is available. <br><br>Global variables are rarely referenced by the method functions of an object because method functions act upon an object, not global data. Remember that an object's handle is passed in as the first argument to a method function. <br><br>So why are global variables ever needed? Sometimes to directly support a class of objects. A prime example is if all objects of a class require access to a read-only resource. When creating the first object for this class, the resource is read into memory and a handle to the resource is stored in a global variable. When
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -