3.4 Requirements Prerequisite
If the requirements are explicit, the user can review them and agree to them. If they’re not, the programmer usually ends up making requirements decisions during programming. Explicit requirements keep you from guessing what the user wants.
The Myth of Stable Requirements
Specifying requirements adequately is a key to project success, perhaps even more important than effective construction techniques.
Handling Requirements Changes During Construction
Sure, it feels like you’re getting behind if you stop coding at this stage. But if you’re driving from Chicago to Los Angeles, is it a waste of time to stop and look at a road map when you see signs for New York? No. If you’re not heading in the right direction, stop and check your course.
Make sure everyone knows the cost of requirements changes:
"that sounds like a great idea. Since it’s not in the requirements document, I’ll work up a revised schedule and cost estimate so that you can decide whether you want to do it now or later.” The words “schedule” and “cost” are more sobering than coffee and a cold shower, and many “must haves” will quickly turn into “nice to haves.”
It’s all right for customers to change their minds and to realize that they need more capabilities. The problem is their suggesting changes so frequently that you can’t keep up. Having a built-in procedure for controlling changes makes everyone happy.
Evolutionary delivery is an approach that delivers the system in stages. You can build a little, get a little feedback from your users, adjust your design a little, make a few changes, and build a little more. The key is using short develop- ment cycles so that you can respond to your users quickly.
- Are all the inputs to the system specified, including their source, accuracy, range of values, and frequency?
- Are all the outputs from the system specified, including their destination, accuracy, range of values, frequency, and format?
- Are all output formats specified for Web pages, reports, and so on?
- Are all the external hardware and software interfaces specified?
- Are all the external communication interfaces specified, including hand- shaking, error-checking, and communication protocols?
- Are all the tasks the user wants to perform specified?
- Is the data used in each task and the data resulting from each task specified?
Non functional requirements
- Is the expected response time, from the user’s point of view, specified for all necessary operations?
- Are other timing considerations specified, such as processing time, data- transfer rate, and system throughput?
- Is the level of security specified?
- Is the reliability specified, including the consequences of software failure, the vital information that needs to be protected from failure, and the strategy for error detection and recovery?
- Are minimum machine memory and free disk space specified?
- Is the maintainability of the system specified, including its ability to adapt to changes in specific functionality, changes in the operating environment, and changes in its interfaces with other software?
- Is the definition of success included? Of failure?
- Are the requirements written in the user’s language? Do the users think so?
- Does each requirement avoid conflicts with other requirements?
- Are acceptable tradeoffs between competing attributes specified—for example, between robustness and correctness?
- Do the requirements avoid specifying the design?
- Are the requirements at a fairly consistent level of detail? Should any requirement be specified in more detail? Should any requirement be specified in less detail?
- Are the requirements clear enough to be turned over to an independent group for construction and still be understood? Do the developers think so?
- Is each item relevant to the problem and its solution? Can each item be traced to its origin in the problem environment?
- Is each requirement testable? Will it be possible for independent testing to determine whether each requirement has been satisfied?
- Are all possible changes to the requirements specified, including the like- lihood of each change?
- Where information isn’t available before development begins, are the areas of incompleteness specified?
- Are the requirements complete in the sense that if the product satisfies every requirement, it will be acceptable?
- Are you comfortable with all the requirements? Have you eliminated requirements that are impossible to implement and included just to appease your customer or your boss?
3.5 Architecture Prerequisite
the quality of the architecture deter- mines the conceptual integrity of the system. That in turn determines the ultimate quality of the system.
It partitions the work so that multiple develop- ers or multiple development teams can work independently.
Good architecture makes construction easy. Bad architecture makes construction almost impossible.
The architecture doesn’t need to specify every class in the system. Aim for the 80/20 rule: specify the 20 percent of the classes that make up 80 percent of the system’s behavior.
The architecture should explain why a database is preferable to flat files.
User Interface Design
Careful architecture of the user interface makes the difference between a well-liked program and one that’s never used.
The architecture should be modularized so that a new user interface can be substi- tuted without affecting the business rules and output parts of the program.
The architecture should describe how the system will address growth in number of users, number of servers, number of network nodes, number of database records, size of database records, transaction volume, and so on.
The architecture can decide to use strings in line in the code where they’re needed, keep the strings in a class and reference them through the class interface, or store the strings in a resource file. The architecture should explain which option was chosen and why.
Error processing is turning out to be one of the thorniest problems of modern computer science.
With so much code dedicated to handling errors, a strategy for handling them consistently should be spelled out in the architecture.
Error handling is often treated as a coding-convention-level issue, if it’s treated at all.
at what level are errors handled? You can handle them at the point of detection, pass them off to an error-handling class, or pass them up the call chain.
Is each class responsible for validating its own data, or is there a group of classes responsible for validating the system’s data? Can classes at any level assume that the data they’re receiving is clean?
You can buy GUI controls, database managers, image processors, graphics and charting components, Internet communications components, security and encryption components, spreadsheet tools, text- processing tools—the list is nearly endless. One of the greatest advantages of program- ming in modern GUI environments is the amount of functionality you get automatically: graphics classes, dialog box managers, keyboard and mouse handlers, code that works automatically with any printer or monitor, and so on.
The architecture should show that possible enhancements have been considered and that the enhancements most likely are also the easiest to implement.
General Architectural Quality
The architecture should be a polished conceptual whole with few ad hoc additions. The central thesis of the most popular software-engineering book ever, The Mythical Man-Month, is that the essential problem with large systems is maintaining their conceptual integrity.
When you look at the architecture, you should be pleased by how natural and easy the solution seems. It shouldn’t look as if the problem and the architecture have been forced together with duct tape.
Good software architecture is largely machine and language-independent.
The architecture should explicitly identify risky areas. It should explain why they’re risky and what steps have been taken to minimize the risk.
- Are the most critical classes described and justified?
- Is the data design described and justified?
- Is a strategy for the user interface design described?
- Is the user interface modularized so that changes in it won’t affect the rest
of the program?
- Is a strategy for handling I/O described and justified?
- Does the architecture describe how scalability will be achieved?
- Is a strategy for internationalization/localization described?
- Is a coherent error-handling strategy provided?
- Has technical feasibility of all parts of the system been established?
- Are necessary buy-vs.-build decisions included?
- Is the architecture designed to accommodate likely changes?
- Are you, as a programmer who will implement the system, comfortable with the architecture?
3.6 Amount of Time to Spend on Upstream Prerequisites
Your clients wouldn’t want you to show up with wood, hammer, and nails and start spending their money before the architect had finished the blueprints.
People tend to understand software development less than they understand two-by-fours and sheetrock.
why you want to plan requirements development as a separate project. You might need to explain your reasoning to them.
- Part of a programmer’s job is to educate bosses and coworkers about the soft- ware-development process, including the importance of adequate preparation before programming begins.
- If a good problem definition hasn’t been specified, you might be solving the wrong problem during construction.
- If good requirements work hasn’t been done, you might have missed important details of the problem. Requirements changes cost 20 to 100 times as much in the stages following construction as they do earlier, so be sure the requirements are right before you start programming.
- If a good architectural design hasn’t been done, you might be solving the right problem the wrong way during construction. The cost of architectural changes increases as more code is written for the wrong architecture, so be sure the architecture is right, too.
Chapter 4: Key Construction Decisions
4.1 Choice of Programming Language
By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and in effect increases the mental power of the race. Before the introduction of the Arabic notation, multiplication was difficult, and the division even of integers called into play the highest mathematical faculties. Probably nothing in the modern world would have more astonished a Greek mathematician than to learn that … a huge proportion of the population of Western Europe could perform the operation of division for the largest num- bers. This fact would have seemed to him a sheer impossibility…. Our modern power of easy reckoning with decimal fractions is the almost miraculous result of the gradual discovery of a perfect notation.
Alfred North Whitehead
An earlier study at IBM found that programmers who had extensive experience with a programming language were more than three times as productive as those with minimal experience.
Programmers working with high-level languages achieve better productivity and quality than those working with lower-level languages.
You save time when you don’t need to have an awards ceremony every time a C statement does what it’s supposed to.
More- over, higher-level languages are more expressive than lower-level languages. Each line of code says more.
The Sapir - Whorf hypothesis says that your ability to think a thought depends on knowing words capable of expressing the thought. If you don’t know the words, you can’t express the thought and you might not even be able to formulate it.
Programmers may be similarly influenced by their languages.
A typical story goes like this: “We were writing a new system in C++, but most of our programmers didn’t have much experience in C++. They came from Fortran back- grounds. They wrote code that compiled in C++, but they were really writing dis- guised Fortran. They stretched C++ to emulate Fortran’s bad features (such as gotos and global data) and ignored C++’s rich set of object-oriented capabilities.”
4.2 Programming Conventions
construction guidelines for variable names, class names, routine names, for- matting conventions, and commenting conventions.
One key to successful programming is avoiding arbitrary variations so that your brain can be free to focus on the variations that are really needed.
Coding-convention details are at such a level of precision that they’re nearly impossible to retrofit into software after it’s written.
4.3 Your Location on the Technology Wave
In early-wave environments—Web programming in the mid-1990s, for example, Programmers spend significant amounts of time simply trying to figure out how the language works instead of writing new code.
Programmers who program “in” a language limit their thoughts to constructs that the language directly supports. If the language tools are primitive, the programmer’s thoughts will also be primitive.
Programmers who program “into” a language first decide what thoughts they want to express, and then they determine how to express those thoughts using the tools provided by their specific language.
Example of Programming into a Language
In the early days of Visual Basic, I was frustrated because I wanted to keep the business logic, the UI, and the database separate, but there wasn’t any built-in way to do that in the language.
Consequently, I adopted a design convention that the .frm file (the form file) was allowed only to retrieve data from the database and store data back into the database.
Each form supported an IsFormCompleted() routine, which was used by the calling routine to determine whether the form that had been activated had saved its data.
If your language lacks constructs that you want to use or is prone to other kinds of prob- lems, try to compensate for them. Invent your own coding conventions, standards, class libraries, and other augmentations.
4.4 Selection of Major Construction Practices
Checklist: Major Construction Practices
- Will programmers program in pairs, or individually, or some combination of the two?
- Will programmers write test cases for their code before writing the code itself?
- Will programmers write unit tests for their code regardless of whether they write them first or last?
- Will programmers integration-test their code before they check it in?
- Will programmers review or inspect each other’s code?
- Every programming language has strengths and weaknesses. Be aware of the specific strengths and weaknesses of the language you’re using.
- Establish programming conventions before you begin programming. It’s nearly impossible to change code to match them later.
- More construction practices exist than you can use on any single project. Con- sciously choose the practices that are best suited to your project.
- Ask yourself whether the programming practices you’re using are a response to the programming language you’re using or controlled by it.
- Identify where you are on the technology wave, and adjust your plans and expectations accordingly.
Part II - Creating High-Quality Code
Chapter 5 Design in Construction
the design might be intended to be detailed enough for coding to be fairly mechanical, but design is rarely that complete—the programmer usually designs part of the program, officially or otherwise.
On small, informal projects, a lot of design is done while the programmer sits at the keyboard. “Design” might be just writing a class interface in pseudocode before writ- ing the details. It might be drawing diagrams of a few class relationships before coding them. It might be asking another programmer which design pattern seems like a bet- ter choice.
Regardless of how it’s done, small projects benefit from careful design just as larger projects do, and recognizing design as an explicit activity maximizes the ben- efit you will receive from it.
5.1 Design Challenges
Design Is a Wicked Problem
Horst Rittel and Melvin Webber defined a “wicked” problem as one that could be clearly defined only by solving it, or by solving part of it. This paradox implies, essentially, that you have to “solve” the problem once in order to clearly define it and then solve it again to create a solution that works. This process has been motherhood and apple pie in software development for decades.
a dramatic example of such a wicked problem was the design of the original Tacoma Narrows bridge.
wind created an unexpected, side-to- side harmonic ripple. One blustery day in 1940, the ripple grew uncontrollably until the bridge collapsed.
until the bridge collapsed, its engineers didn’t know that aerodynamics needed to be considered to such an extent. Only by building the bridge (solving the problem) could they learn about the additional consideration in the problem that allowed them to build another bridge that still stands.
Design Is a Sloppy Process (Even If it Produces a Tidy Result)
Design is sloppy because you take many false steps and go down many blind alleys— you make a lot of mistakes. Indeed, making mistakes is the point of design—it’s cheaper to make mistakes and correct designs than it would be to make the same mis- takes, recognize them after coding, and have to correct full-blown code.
design is “emergent.” Designs don’t spring fully formed directly from someone’s brain. They evolve and improve through design reviews, informal discussions, experience writing the code itself, and experience revising the code.
5.2 Key Design Concepts
Accidental and Essential Difficulties
In philosophy, the essential properties are the properties that a thing must have in order to be that thing. A car must have an engine, wheels, and doors to be a car. If it doesn’t have any of those essential properties, it isn’t really a car.
Accidental properties are optional.
Even if we could invent a programming language that used the same terminology as the real-world problem we’re trying to solve, programming would still be difficult because of the challenge in determining precisely how the real world works
Importance of Managing Complexity
Projects fail most often because of poor requirements, poor planning, or poor management. But when projects do fail for reasons that are primarily technical, the reason is often uncontrolled complexity. The software is allowed to grow so complex that no one really knows what it does.
When a project reaches the point at which no one completely understands the impact that code changes in one area will have on other areas, progress grinds to a halt.
Managing complexity is the most important technical topic in software development. In my view, it’s so important that Software’s Primary Technical Imperative has to be managing complexity.
Dijkstra pointed out that no one’s skull is really big enough to contain a modern computer program (Dijkstra 1972), which means that we as software developers shouldn’t try to cram whole programs into our skulls at once; we should try to organize our programs in such a way that we can safely focus on one part of it at a time.
The goal is to minimize the amount of a program you have to think about at any one time. You might think of this as mental juggling—the more mental balls the program requires you to keep in the air at once, the more likely you’ll drop one of the balls, leading to a design or coding error.
Humans have an easier time comprehending several simple pieces of information than one complicated piece. The goal of all software-design techniques is to break a complicated problem into simple pieces.
The more independent the subsystems are, the more you make it safe to focus on one bit of complexity at a time.
Packages provide the same benefit at a higher level of aggregation.
Keeping routines short helps reduce your mental workload.
working at the highest level of abstraction reduce the load on your brain.
Desirable Characteristics of a Design
A high-quality design has several general characteristics. If you could achieve all these goals, your design would be very good indeed. Some goals contradict other goals, but that’s the challenge of design—creating a good set of tradeoffs from competing objectives.
Avoid making “clever” designs. Clever designs are usually hard to understand. Instead make “simple” and “easy-to-understand” designs.
Ease of maintenance
Continually imagine the questions a maintenance programmer would ask about the code you’re writing. Think of the maintenance programmer as your audience, and then design the system to be self-explanatory.
you should hold connections among different parts of a program to a minimum.
Use the principles of good abstractions in class interfaces, encapsulation, and information hiding to design classes with as few interconnections as possible.
Minimal connectedness minimizes work during integration, testing, and maintenance.
To enhance a system without causing violence to the underlying structure.
You can change a piece of a system without affecting other pieces.
you can reuse pieces of it in other systems.
High fan-in refers to having a high number of classes that use a given class. High fan-in implies that a system has been designed to make good use of utility classes at the lower levels in the system.
means having a given class use a low-to-medium number of other classes.
High fan-out (more than about seven) indicates that a class uses a large number of other classes and may therefore be overly complex. Researchers have found that the principle of low fan-out is beneficial whether you’re considering the number of routines called from within a routine or the number of classes used within a class.
you can easily move it to another environment.
Leanness means designing the system so that it has no extra parts.
Voltaire said that a book is finished not when nothing more can be added but when nothing more can be taken away.
In software, this is especially true because extra code has to be developed, reviewed, tested, and considered when the other code is modified.
you can view the system at any single level and get a consistent view.
Design the system so that you can view it at one level without dipping into other levels.
The beneficial effects of stratified design in such a case are (1) it compartmentalizes the messiness of the bad code and (2) if you’re ever allowed to jettison the old code or refactor it, you won’t need to modify any new code except the interface layer.
Try to give the whole system a familiar feeling by using standardized, common approaches.