《Debugging Applications》读书笔记

来源:互联网 发布:淘宝里质量好的杂货铺 编辑:程序博客网 时间:2024/05/14 22:20

《Debugging Applications》读书笔记

 

按:此书更重视的是预防性编程的实践,例如建立源代码控制系统、Bug跟踪系统,进行assert、trace、comments,进行单元测试等。

 

Chapter 1

Bugs: Where They Come From and How You Solve Them

 

 

The reasons for bugs generally fall into the followingprocess categories:

?   Short orimpossible deadlines

?   The"code first, think later" approach

?   Misunderstoodrequirements

?   Engineerignorance or improper training

?   Lack ofcommitment to quality

 

 

Although many people start thinking about debuggingonly when they crash during the coding phase, you should think about it rightfrom the beginning, in the requirements phase. The more you plan your projectsup front, the less time—and money—you'll spend debuggingthem later.

As I mentioned earlier, feature creep can be a bane toyour project. More often than not, unplanned features introduce bugs and wreakhavoc on a product.

 

Sometimes you must change or add a feature to aproduct to be competitive or to better meet the user's needs. The key point toremember is that before you change your code, you need to determine—andplan for—exactly what will change. And keep in mindthat adding a feature doesn't affect just the code; it also affects testing,documentation, and sometimes even marketing messages. When revising your productionschedule, a general rule to follow is that the time it takes to add or remove afeature grows exponentially the further along the production cycle you are.

 

 

The idea is to try to avoid bugs in the first place.If you build sufficient debugging code into your applications, that code—notthe debugger—should tell you where the bugs are.

 

 

As you'll see, this debugging process doesn't take arocket scientist to implement. The hard part is making sure you start with thisprocess every time you debug. Here are the nine steps involved in the debuggingapproach that I recommend:

 

 

Step 1: Duplicate the bug

Step 2: Describe the bug

Step 3: Always assume that the bug is yours

Step 4: Divide and conquer

Step 5: Think creatively

Step 6: Leverage tools

Step 7: Start heavy debugging

Step 8: Verify that the bug is fixed

Step 9: Learn and share

 

Chapter 2

Getting Started Debugging

 

 

Track Changes Until You Throw Away the Project

Version control and bug tracking systems are two ofthe most important infrastructure tools you have because they give you thehistory of your project.

 

Because most teams don't adequately maintain theirrequirements and design documents throughout the life of a project, the onlyreal documentation becomes the audit trail in the version control and bugtracking systems.

 

 

I'm talking about these two tools in the same breathbecause they are inseparable. The bug tracking system captures all the eventsthat might have driven changes to your master sources. The version controlsystem captures every change.

 

One of the most important commands that you can learnto use in your version control system is its label command. Different versioncontrol systems might refer to the label command in different ways, butwhatever it's called, a label marks a particular set of master sources. A labelallows you to retrieve a specific version of your master sources in the future.

 

When deciding what to label, I've always followedthese three hard-and-fast rules:

1.  Label allinternal milestones.

2.  Label anybuild sent to someone outside the team.

3.  Label anytimea daily build occurs.

The third labeling rule is one that many peopleforget. Your quality engineers are usually working with the daily build, sowhen they report a problem, it's against a particular version of the mastersources. Because developers can change code quickly, you want to make it simplefor them to get back to the exact version of the files they need to reproducethe bug.

 

In addition to tracking your bugs, the bug trackingsystem makes an excellent vehicle for jotting down reminders and keeping ato-do list, especially when you're in the process of developing code.

 

As you're doing the design and initial scheduling foryour project, make sure to add in time for building your debugging systems. Youneed to decide up front how you're going to implement your crash handlers (atopic covered in Chapter 9), file data dumpers, and other tools you'll need tohelp you reproduce problems reported from the field. I've always liked to treatthe error handling systems as if they were a product feature. Handle bugsproactively.

 

 

As you're planning your debugging systems, you need toestablish your preventive debugging policies. The first and most difficult partsof this process involve determining how you're going to return error conditionsin the project.

 

Two of the most important pieces of yourinfrastructure are your build system and your smoke test suite. The buildsystem is what compiles and links your product, and the smoke test suitecomprises tests that run your program and verify that it works. im McCarthy, inhis book Dynamics of Software Development (Microsoft Press, 1995), called thedaily build and smoke test the heartbeat of the product. If these processesaren't healthy, the project is dead.

 

When building your product, you should be buildingboth release and debug versions at the same time.

 

To avoid problems with the build, everyone must havethe same versions of all build tools and parts. As I mentioned earlier, someteams like to keep the build system in version control to enforce thispractice. Once you've hit feature complete, also known as beta 1, you shoulddefinitely not upgrade any tools.

 

Another advantage of developers pulling frequently isthat it helps enforce the mantra of "no build breaks." By pullingfrequently, any problem with the master build automatically becomes a problemwith every developer's local build. With the knowledge that breaking the masterbuild means breaking the build for every individual developer, the pressure ison everyone to check only clean code into the master sources.

 

A smoke test is a test that checks your product'sbasic functionality.

 

In most software situations, a smoke test is simply arun-through of the product to see whether it runs and is therefore good enoughto start testing seriously. A smoke test is your gauge of the baseline healthof the code.

Your smoke test is just a checklist of items that yourprogram can handle.

 

Keep in mind that your smoke test doesn't need toexhaustively test every code path in your program, but you do want to use it tojudge whether you can handle the basics. Once your program passes the smoketest, the quality engineers can start doing the hard work of trying to breakthe program.

 

One vital component of your smoke test is some sort ofperformance benchmark.

 

Breaking the smoke test should be as serious a crimeas breaking the build. It takes more effort to create a smoke test, and nodeveloper should treat it lightly. Because the smoke test is what tells your QAteam that they have a build that's good enough to work on, keeping the smoketest running is mandatory.

 

 

Chapter 3

Debugging During Coding

 

Defensive programming is the error handling code thattells you an error occurred. Proactive programming tells you why the erroroccurred.

 

 I've alwaysreferred to this approach as "trust, but verify," which is RonaldReagan's famous

 

quote about how the United States was going to enforceone of the nuclear arms limitation treaties with the then Soviet Union. I trustthat I and my colleagues will use my code correctly. To avoid bugs, however, Iverify everything. I verify the data that others pass into my code, I verify mycode's internal manipulations, I verify every assumption I make in my code, Iverify data my code passes to others, and I verify data coming back from callsmy code makes. If there's something to verify, I verify it.

 

 

code quality is the sole responsibility of the developmentengineers, not the test engineers, technical writers, or managers. The bulk ofour responsibility for code quality starts with the coding and finishes withthe unit testing. When unit testing, you must strive to execute as much of yourcode as possible and ensure that it doesn't crash. By having strong unit tests,the test engineers can spend their time more effectively looking forintegration problems.

 

Assertions might be the best proactive programmingtrick you can learn, but trace statements, if used correctly with assertions,will truly allow you to debug your application without the debugger. Tracestatements are essentially printf-style debugging. You should neverunderestimate the power of printf-style debugging because that's how most applicationswere debugged before interactive debuggers were invented.

 

Although trace statements can solve almost all yourproblems, they have two drawbacks.

The first limitation is that trace statements usuallycause your application to serialize execution when you call them. This meansthat your high-speed multithreaded application can perform in a completelydifferent way when you use trace statements because the threads block and arescheduled around the trace statements.

The second limitation is that, because of theserialization problem, too many trace statements can make your debug build runvery slowly.

 

If everyone uses a similar format, finding informationwith grep or writing simple parsers to analyze the logs is easy.

 

Although you might think your code is the model ofclarity and completely obvious, without correct comments, your code is as badas raw assembly language to the maintenance developers.

 

Our job as engineers is twofold: develop a solutionfor the user and make that solution maintainable for the future. The only wayto make your code maintainable is to comment it. By "comment it," Idon't mean simply writing comments that duplicate what the code is doing; Imean documenting your assumptions, your approach, and your reasons for choosingthe approach you did. You also need to keep your comments coordinated with thecode.

 

 

I use the following approach to commenting:

?   Each functionor method needs a sentence or two that clarifies the following information:

o   What the routinedoes

o   Whatassumptions the routine makes

o   What eachinput parameter is expected to contain

o   What eachoutput parameter is expected to contain on success and failure

o   Each possiblereturn value

?   Each part ofthe function that isn't completely obvious from the code needs a sentence ortwo that explains what it's doing.

?   Anyinteresting algorithm deserves a complete description.

?   Anynontrivial bugs you've fixed in the code need to be commented with the bugnumber and a description of what you fixed.

?   Well-placedtrace statements, assertions, and good naming conventions can also serve asgood comments and provide excellent context to the code.

?   Comment as ifyou were going to be the one maintaining the code in five years.

?   If you findyourself saying, "This is a big hack" or "This is really trickystuff," you probably need to rewrite the function instead of commentingit.

 

 

Proper and complete documentation in the code marksthe difference between a serious, professional developer and someone who isplaying at it.

 

 

Assertions, tracing, and commenting are how I startverifying my fellow developers who are calling my code. Unit testing is how Iverify myself. Unit tests are the scaffolding that you put in place to call yourcode outside the normal program as a whole.

 

Once I figure out the interface for a module, I writethe stub functions for that module and immediately write a test program, orharness, to call those interfaces. As I add a piece of functionality, I add newtest cases to the test harness. Using this approach, I can test eachincremental change in isolation and spread out the test harness developmentover the development cycle. If you do all the regular development after you'veimplemented the main code, you generally don't have enough time to do a goodjob on the harness and therefore do a less thorough job implementing aneffective test.

The second way I verify myself is to think about howI'm going to test my code before I write it.

 

The key to the most effective unit tests comes down totwo words: code coverage. If you take nothing else away from this chapterexcept those two words, I'll consider it a success. Code coverage is simply thepercentage of lines you've executed in your module. The simple fact is that aline not executed is a line waiting to crash.

 

Personally, I don't check in any code to the mastersources until I've executed at least 85 to 90 percent of the lines in my code.

 

QA's job is to test the product as a whole and to signoff on the quality as a whole. Your job is to test a unit and to sign off onthe quality of that unit. When both sides do their jobs, the result is ahigh-quality product.

 

If you're not using one of the third-partycode-coverage tools, you're cheating yourself on quality.

In addition to the code coverage, I frequently runthird-party error detection and performance tools on my unit test projects.

 

 

(其余章节设计具体技术细节,故省略)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击