For example, the idea that you should make frequent stable builds fits naturally with such ideas as programming for people, keeping the user in charge, and making sure code works before
Trang 1Development Philosophy
When I started writing this chapter, I reviewed my list of developer tips, looked over some other lists posted on the Web, skimmed through my collection of development books, and put together a list of my 50 or so favorite pieces of advice Because I didn’t want this chapter to be a huge list of bullet points, I spent quite a while grouping and compressing the ideas into about a dozen key concepts
At that point, I realized that there’s really only one main theme that covers them all: avoiding unnecessary work When you develop an application, you really have three goals:
❑ Build an application that works
❑ Build it on time
❑ Build it within budget
If you assume that it is possible to build something that does the job correctly (admittedly, not always a valid assumption), the rest is largely a matter of avoiding unnecessary work If you per-form more work than necessary, you’ll finish late and over budget You’ll also end up spending more time maintaining the code and fixing bugs in the long term
At this point, I also realized that many of the techniques used by the agile programming methods described in Chapter 3, “Agile Methodologies,” address these issues For example, the idea that you should make frequent stable builds fits naturally with such ideas as programming for people, keeping the user in charge, and making sure code works before moving on to the next task
This chapter describes some of the most important ideas that you should keep in mind while you are working on a project Some ideas are most useful during specific stages of development For example, it is important to assign people to the tasks for which they are best suited early in the project Other ideas apply to the whole development process For example, whether you are build-ing a high-level design, writbuild-ing code, testbuild-ing changes, or writbuild-ing documentation, you should do the best job possible before moving to new things You will be more productive if you can move
Trang 2Program for People
Chapter 1, “Language Selection,” briefly mentions that programs are written for people and not for com-puters If you were writing only for the computer, you would use 0s and 1s, or at least some form of assembly language rather than a high-level language such as Visual Basic The computer doesn’t care what language you use It’s all 0s and 1s when the CPU executes it The reason you use a high-level
lan-guage is to make programming, understanding, debugging, and maintaining the code easier for people.
At some later date, you or someone else will try to read the code Unfortunately, reading code is harder than writing code When you are writing code, you have a mental model of what the code is supposed
to do and how it will do it You just need to type it into the IDE Depending on how good a programmer you are, the model may be a bit fuzzy or incomplete, and you may need to flesh it out as you go
However, when you try to read someone else’s code, or even your own code at a later date, you don’t yet have a mental model of the code You need to study the code to decide what it does As your model develops, you will find places where it conflicts with what the code actually does Then you need to revise your model to match the code, a process that can sometimes be difficult If you don’t update your model appropriately, you won’t properly understand the code, and any changes you make may lead
to bugs
I’ve worked on several existing applications where the developers clearly had no clue about how to make the code readable They used cryptic variable names, no comments, and stacks of very deep subroutine calls (some more than 30 calls deep) Some applications have included dead code that is no longer acces-sible from any other place in the system Some have been completely without design documentation.
Some of this code was so bad that even developers who had been working on the system for more than
10 years could spend more than a week trying to figure out how one part of the system worked or how to fix a relatively simple bug.
Once I was brought in to fix a large Excel application The developer previously maintaining the
pro-gram had quit unexpectedly, and the client thought the propro-gram needed about three days to repair.
Unfortunately, the application had been written over a period of a few years by several different develop-ers, and it contained no comments or other documentation After spending a day digging through the
code, I concluded that the code might only need a few days’ worth of work, but it would take a few
months to get to the point where anyone could do the work The code was so complicated and hard to
read that it would have needed a lot of study to fix any bugs without causing others.
Anything you do that makes it easier for a reader to build a mental model of your code makes it easier to understand, modify, and maintain Whereas code is relatively hard to read, prose is relatively easy You will probably read this section of the book in just a few seconds, and will understand most of it with lit-tle difficulty, although it will take me several minutes to write it
To take advantage of this fact, add enough comments to your code to describe in prose what the code is doing When you need to understand the code later, the comments can help you build a correct mental model of what the code does At that point, you only need to verify that the code does what you think it does, instead of trying to decipher the code’s purpose
To help the reader more, give your variables nice long meaningful names that follow naming conven-tions (Chapter 15, “Coding Standards,” has more to say about naming convenconven-tions.) Later, when you encounter a variable named selected_products, you can fit it neatly into your mental model without wasting extra thought figuring out what the variable does Each time you see the variable, it reinforces
Trang 3your understanding of it In contrast, if the variable were named selprods, each time you saw it you would need to re-evaluate your mental model to see if your idea of the variable’s purpose matches its latest use
As you write code, keep the idea in mind that you are writing code for people to read, not the com puter Anything that you can do to make the code easier for people to read makes it more stable and maintainable
Agile methods don’t emphasize the idea of programming for people but it’s absolutely critical for agile teams Because agile development relies on frequent small releases, the same code must be modified again and again That makes it essential that developers can understand the code as quickly as possible
so that they can make the necessary changes without introducing time-consuming bugs
Some agile approaches also promote the notion that the code is owned by the team, rather than by indi-vidual programmers You may work on a routine one day, but someone else may need to work on it the next In that case, it’s particularly important that the code be easy to understand You can’t rely on your memory of how the code works, because you may not be the next person who modifies it
Unfortunately, frequent releases may pressure developers into skimping on comments and other docu-mentation If you adopt agile methods, you must fight this at all costs If the code becomes unreadable, you may make the current release on time, but you’ll soon find that future releases take longer and longer as developers struggle to understand the code
Keep the User in Charge For some applications, the goal is to have the program do all of the work without any user intervention Unfortunately, writing a program that is perfectly autonomous is extremely difficult in practice Often, when you try to make a program run completely without help, you run into the 80/20 problem: 80 per-cent of the code is needed to handle the trickiest 20 perper-cent of the functionality
In addition to taking up a lot of room, that 80 percent of the code usually includes the code that’s the most complicated, confusing, bug-prone, and trouble to maintain In many cases, the situation is even worse and trying to exhaustively handle every conceivable situation that the program might encounter
is prohibitively difficult
Rather than trying to handle every possible situation without user intervention, it’s usually much easier
to build a program that handles the most common cases but lets the user deal with the really strange sit-uations If the program handles 80 percent of the load automatically, you may be able to cut out much of the 80 percent of the code that would otherwise be necessary to handle the remaining 20 percent
One project I worked on was a dispatch system that assigned jobs to repair people Twice during the pilot testing period, the system assigned someone to work at his ex-wife’s house In each case, the repair-men recognized the address and called the dispatcher to request a different job The system was flexible enough that the dispatcher was able to assign the job to someone else.
To handle this special case automatically, the program would have had to include some sort of table list-ing customers that each employee could not visit We would also have needed to ask the employees to list everyone whose house they couldn’t visit It would have been possible, but it would have been a large
Trang 4In deciding what tasks to leave to the user, you need to weigh the benefits and costs If a task is very easy for the user, but difficult for the program, let the user handle it If a task is time-consuming for the user, or there’s some reason why the computer can do a better job than the user, the program should handle it
This approach may not only save you a huge amount of work, but it can also help the user feel empow-ered and in charge Design the application as a tool that helps the user handle the majority of the work, instead of as an independent application replacing the user
In some applications, you can take these ideas even further and actually make the user do your work for you If you let the program execute scripts, Visual Basic code, and SQL scripts, the users can write some
of the code for you If you move database operations into scripts or stored procedures kept inside the database, you can move some of the work out of the application Chapter 9, “Scripting,” has more to say about some of these kinds of scripting
Scripting is particularly useful when the program must handle a large number of relatively simple tasks Instead of building all of the code in the application itself, you may be able to train the users to write the scripts for you
One indication that scripting may be helpful is if you find the users cannot agree on the application’s functionality, or if the required features change a lot during specification and early development If dif-ferent groups of users think they need difdif-ferent features, you may be able to satisfy them all by allowing them to write macros to suit their individual needs
Agile methods don’t require that you leave the user in control of the application You could use agile methods to develop a program that runs completely independently
However, agile methods do fit in well with this notion Agile development requires frequent small releases that implement only part of the application’s functionality If the program cannot handle abso-lutely every possible situation that it may encounter, you either need to release a program that is incom-plete, or you need to allow the user to deal with the cases that it cannot handle If you take the second approach, you can initially focus on the most common 80 percent of the cases In later releases, you can expand the program’s coverage to include less-typical cases if necessary At some point, if the difficulty
of handling special cases outweighs the benefits, you can stop development
Make the Program F ind Errors
For many years, programmers have been told to program defensively — to make the code bulletproof so
no matter what weird data is thrown at it, the program won’t crash One classic technique is to always include an Elseblock in a series of Ifstatements, or in a Select Casestatement so the program does
something no matter what data it sees.
For example, the following code calls various routines to set a program’s privileges based on the user’s type If the user’s type isn’t one of Supervisor, Clerk, or HelpDesk, the code’s Case Elsestatement calls subroutine SetHelpDeskPrivilegesto give the user the fewest privileges possible
Trang 5Select Case user_type Case Supervisor SetSupervisorPrivileges() Case Clerk
SetClerkPrivileges() Case HelpDesk
SetHelpDeskPrivileges() Case Else
SetHelpDeskPrivileges() End Select
The problem with this code is that it hides an unexpected condition Though this code will run and is reasonably safe, giving users of unknown type the least privileges possible, some other part of the pro-gram may later be confused by this Perhaps some other part of the propro-gram will try to perform an action only allowed to supervisors and will crash Perhaps the unknown user_typevalue came from a corrupted data file that contains other problems that are more difficult to correct
Instead of programming defensively, program offensively Make the code aggressively inspect its data and complain loudly if it finds something suspicious Then you can fix the problem and move on with-out worrying that the issue will cause trouble later
Some developers object to this approach by saying that it is better for the user if the program continues even with something wrong than it is to crash Fortunately, you can have the best of both worlds Inside the Elsecase, use a Debug.Assertstatement to validate the data and follow that by the defensive code When you make a debug build, the Debug.Assertstatement will catch the error so that you can fix it When you make a release build and give it to a customer, the Debug.Assertstatement is ignored, and the defensive code keeps the program running so the user doesn’t crash
The following code shows the previous Select Casestatement rewritten to catch problems with the
user_typevalue:
Select Case user_type Case Supervisor SetSupervisorPrivileges() Case Clerk
SetClerkPrivileges() Case HelpDesk
SetHelpDeskPrivileges() Case Else
Debug.Assert(False, “Unknown user_type”) SetHelpDeskPrivileges()
End Select
Make it Wor k F irst Many developers waste untold hours optimizing code that doesn’t need optimization Optimizing code
is challenging and interesting, and programmers are trained to write complex optimized code, so many developers try to implement the “perfect” solution for every problem
Trang 6However, completely optimal code is rarely necessary In the vast majority of code, a simple solution will provide good enough performance Modern computers are extremely fast, so it’s often not worth the time it takes to write the perfect piece of code Though a custom-built, balanced, red-black tree might provide an elegant solution, Visual Basic’s built-in Collectionand Hashtableclasses are more than fast enough for most applications Instead of spending a couple of weeks coding and debugging your tree structure, just try a Collectionor Hashtableand see if the performance is good enough You will not only save time now, but you’ll also greatly reduce maintenance later
After the program is working correctly, use a performance analysis tool to find the parts of the applica-tion that take the most time Then you can optimize those pieces and skip the code that is good enough
One project with around 60,000 lines of Visual Basic 6 code had some serious performance problems.
After a few hours with a performance profiler, I discovered that more than 90 percent of the time was
spent in a few routines While loading a few thousand pieces of data, some routines were being executed millions of times.
It took me about a week to tighten up the code that was causing the problems It was only a few hundred lines out of more than 60,000 that really needed optimization.
If the application doesn’t work properly, it doesn’t matter how fast it is No one is going to use a blind-ingly fast program that produces incorrect results Focus on making the application work correctly first Deferring optimization will not only save you time, but it will keep the code simpler so that making it work properly will be easier After the program is working, then you can look for performance bottle-necks and only fix the code that really needs fixing
The “defer optimization until proven necessary” rule has a few immediate corollaries:
❑ Don’t be clever — Use the simplest possible solution so that other developers can read the code
easily Later, if you do need to optimize code, use lots of comments to make the code easy to understand
❑ Don’t generalize until you know you must — When you write a routine that calculates taxes for
your state, don’t generalize it for other states, unless you are sure you will need it You can rewrite the routine later when it proves necessary
❑ Don’t internationalize if it’s not necessary — Internationalization is basically another form of
gener-alization If you are writing an application for use in your country, don’t provide support for every language in Europe and the Pacific Rim You can internationalize later when you find that you have a huge market in other countries At that point, you should have a working applica-tion, so you can add support for other locales relatively easily without worrying about a huge number of bugs at the same time
Note that none of this means you need to be intentionally stupid If you are pretty certain that a particu-lar routine will be a performance bottleneck or will need generalization later, go ahead and do it right the first time If you know that you’ll need to support multiple locales, then do some upfront planning for different date, time, and currency formats
Agile approaches require frequent builds (as many as several per day) and many small releases To make all of these small releases quickly, you cannot include every optimization and extravagant feature in the first build Instead you should use the simplest, most straightforward solution possible Later, if you dis-cover that the simplistic approach isn’t good enough, you can optimize the code and add extra features
in a later release
Trang 7Agile methods also require that you fix problems as soon as they are discovered, rather than plugging ahead with scheduled tasks even when bugs have been discovered Keeping the code as simple as possi-ble makes it less likely to contain bugs, and makes bugs easier to fix when they are found
Look Before You Leap Take the time to thoroughly plan each step of development before you actually start A multitude of studies have shown that bugs are more expensive to find and fix the longer they are present before they are discovered If you introduce a bug early in the development process, it is much easier to fix right away than it is at development’s end
I’ve worked on a couple of applications that were developed over a very long period of time One project had pieces of code written in Visual Basic 3 through Visual Basic 6 There may once have been a strat-egy, but as the years passed, changes were added incrementally without any real design and planning The end result was an application that was completely unmaintainable.
I have heard some proponents of extreme programming argue that excessive planning restricts develop-ment I have actually seen postings such as the following:
That is completely incorrect The point of agile programming is not to save time by skipping planning The point is to make many small incremental releases so that you can respond to changing and refined requirements quickly The idea is not to start throwing code around and see what pops out
After each small release cycle, an agile development team should take the time to plan the next cycle They must decide what new features to add and what bugs to fix in the next release Far from discourag-ing planndiscourag-ing, agile methods require very frequent planndiscourag-ing
Some agile methods also add extra planning to fine-tune the development process itself The Crystal Clear methodology adds a reflective improvement step where developers make a list of the practices that are working and those that are not, and adjust future development accordingly
The more design you do before starting development, the better you’ll understand what you need to do, and the better will be the resulting code You certainly shouldn’t spend four weeks planning for a release
if your extreme programming release cycle is only five weeks long, but neither should you just start slapping code together
The same principle also applies to other development tasks Before you start writing test code, think about what you need to test What are the unusual and unexpected cases that might give the code prob-lems? What are the typical cases that the application must handle every day? You must write random tests, too, but a few carefully thought-out tests can often uncover most of the code’s bugs
“‘Think hard before you code’ is called up-front design and that is against the spirit
of agile development.”
Trang 8Similarly, a little thought about a bug can make pinpointing the bug easier When you know something isn’t working, think about what the program is trying to do and what might lead to the results you see Step through the code to see exactly what it’s doing and figure out why things are going wrong I have seen developers jump to some conclusion about what’s wrong with the code and start making fixes right away Often, the first guesses are wrong, and they not only must figure out the cause of the original bug but they also need to figure out why their fixes don’t work
When you’re ready to make a change, think about the change’s consequences so that you don’t intro-duce new bugs
Take a few minutes to plan your actions before taking them A few minutes of thought usually saves you time in the long run As Augustus Caesar reportedly said, “Hasten slowly.”
Do a Good Job; Then Move On
It’s amazing how many developers release code without testing it even superficially You might expect untested code to appear on Internet discussion groups where the participants are not getting paid for their time, but all too often professional developers add code to an application without even stepping through the code in the debugger
It’s natural to assume that your code works After all, you just wrote it and the ideas are fresh in your mind If you knew the code didn’t work, you would have fixed it as you wrote it Unfortunately, your natural intuition is misleading at this point Bugs do appear in code and wishing it wasn’t so doesn’t make them disappear
After you write a piece of code, test it thoroughly Assume the code contains bugs that are hiding from you, and try to flush them out Don’t use any code that you have not tested thoroughly
Test the code with expected and unexpected inputs Test it with randomly generated data Step through the code in the debugger, and make sure all of the code is actually executed
Write test routines to exercise the code Then save those routines so you can test the code again later When you make changes to the code, run the test routines again It’s a lot easier (and thus a lot more likely) to thoroughly test the code after you make changes if you already have test routines built
After you test the code thoroughly, you can move on with some confidence that it works You should have the feeling that success is inevitable, not that you are adding additional layers to a house of cards
Test at all levels As soon as you write a routine, test it Later, as you assemble routines into larger sub-systems, test them Only after you have tested the application’s pieces separately should you test the system as a whole
Agile methods rely heavily on thoroughly tested code They require frequent releases of a stable pro-gram Regular builds help uncover bugs but thorough testing is even better
Test-driven development requires you to write the tests before writing the code That allows you to design the tests with fewer preconceptions about how the code works, so you are less likely to avoid cases that you “know” the code can handle After you write the code, however, add more tests to focus
on specific cases that you know may give the code problems
Trang 9Use Object-Oriented Principles Object-oriented techniques were originally billed as the final word in development that would enable ordinary programmers to quickly build fantastically complicated applications, increase productivity while reducing bug counts, effortlessly reuse code, guarantee world peace, and leap tall buildings in a single bound Like most development methodologies, object-oriented techniques were over-hyped, but they do contain some very important innovations if you take advantage of them
The three classic features of object-oriented programming are inheritance, encapsulation, and polymorphism
Inheritance means an object inherits the properties, methods, and events provided by its parent class For
example, an Employeeobject can do everything a Personobject can because Employeeinherits from
Person Inheritance lets you reuse code; you don’t need to rewrite the Personcode for the Employee
class because Employeeinherits that code The benefit is greater if several classes inherit from the same ancestor class If Employee, Supervisor, and Customerall inherit from Person, you get to use the same Personcode in all four classes while needing to write and maintain it once
Polymorphism means an object can behave as if it were an object from its parent class If a subroutine
takes a Personobject as a parameter, you can also pass it an Employeebecause an Employeecan do everything that a Personcan Like inheritance, polymorphism provides a form of code reuse It allows a single routine to handle both Personand Employeeobjects, so you don’t need to write, debug, and maintain two routines
Writing code at the highest level that makes sense gives you the best opportunity for reuse Adding code
to the Personclass lets you reuse the code in the derived classes However, don’t put code artificially high in the inheritance hierarchy if it doesn’t make sense If the Employeeand Supervisorclasses can share a ScheduledVacationproperty, but the Customerclass cannot, putting that routine in the
Personclass will cause unnecessary confusion Instead, put the code in the Employeeclass and derive
Supervisorfrom it, or create a new class that both Employeeand Supervisorcan inherit
Encapsulation means a class hides the details of its inner workings from code outside of the class as much
as possible If the program needs to generate an invoice for a customer, it calls a Customerobject’s
MakeInvoicemethod without needing to know how that routine works
Usually, the main benefit of encapsulation is taken to be flexibility You can change the way the
Customerclass implements the MakeInvoicemethod without changing any of the code that calls that method While that is a nice benefit, a potentially greater benefit lies in the fact that encapsulation reduces the programmer’s cognitive load A developer who calls the MakeInvoicemethod doesn’t need
to think about how the method works He doesn’t need to know how to get the customer’s account bal-ance, doesn’t need to know how to format the invoice, doesn’t need to know how to send the invoice to the right printer, and doesn’t care Instead, the developer can focus completely on the code he or she is currently writing: the code that calls the MakeInvoicemethod
Encapsulation breaks the program into smaller, manageable pieces It compartmentalizes the application
so that you can consider the pieces separately without worrying too much about how they interact, at least on a daily basis No one can keep all of the code in a large application in mind at all times, so it’s only by breaking it up that you have a chance of making it work
Trang 10Breaking an application into compartmentalized pieces isn’t a new idea It’s been around since long before object-oriented principles were formalized Object-oriented languages such as Visual Basic, C++, and Java just make encapsulation more formal and easier
Still, many developers don’t take encapsulation as far as they might They make properties and methods public when they don’t need to be They use global variables that let routines interact across modules in unpredictable ways They use module-level variables to store values between subroutine calls when a static variable inside the routine would work just as well
To get the greatest benefit from encapsulation, use local variables when you can, module-level variables only when you must, and global variables as a last resort Restrict access to everything as much as possi-ble by declaring variapossi-bles and subroutines Privateso that only code within the class can access it Later, when you discover that you really need to use a routine outside of a class, you can widen its scope
to Protected, Friend, or Publicto allow a wider audience of routines to have access
Take Advantage of V isual Studio
Visual Studio provides a lot of tools that many developers ignore IntelliSense makes using long, descriptive variable and routine names easy, but only if you let it Give your variables meaningful names and then use IntelliSense to avoid typing them
If you write some code and then decide that a variable’s name doesn’t match its purpose, rename it Right-click the variable and use the Rename command to give it a new name This is quick, easy, and safer than using a find-and-replace command that may change the text in the comments and other code
Use consistent code standards If you name variables consistently, IntelliSense can help you remember the variables’ names For example, if, like most Visual Basic developers, you always name text box con-trols with a txtprefix you can easily discover the name of any text box Simply type “txt” and then
press Ctrl+Space to make IntelliSense display a list of variables with names starting with “txt,” and pick-ing the right one should be easy
Use XML comments to describe routines and their parameters so that IntelliSense can remind you and other programmers about them You may have no trouble remembering a subroutine’s name and pur-pose now, but will you remember in a week? In a year? Many applications remain in use long after the original developers thought they would be (remember the Y2K bug?), so you or someone else may need
to read the code decades from now
Visual Studio automatically formats code, providing consistent indentation and capitalization You can
take advantage of these features by intentionally not indenting and capitalizing your code If the syntax
is correct, Visual Studio will update your code so it formats correctly If you make a syntax error, it will
be obvious because Visual Studio will not reformat it
Visual Basic also highlights syntax errors and suspicious pieces of code with wavy underlines Don’t ignore these If you see some code with a wavy underline, hover the mouse over it to see what Visual Basic thinks is wrong and fix it
When Visual Basic displays a warning that is not necessarily an error, rewrite the code to eliminate the warning For example, the following code builds a SQLSELECTstatement Because the second line uses the variable query before it is assigned a value, Visual Basic flags that line with a warning