Some Good Unit Testing
Ed Gibbs writes of some code and unit tests he reviewed:
- Clear test names.
- Only 2-4 lines of code for most of the tests.
- Thorough testing of negative conditions like testDuplicateRefundsOnlyCountOnce().
- 99% unit test coverage according to Clover.
[/docs]
permanent link
Donate to Solar Cookers International
Refugees from Darfur face dangers daily, when they leave their camps to gather wood to cook their food. Donate to Solar Cookers International and make a concrete improvement in their daily lives.
Solar cookers are a win-win technology in sun-rich, fuel-scarce areas: they reduce smoke and lung diseases, pasteurize unsafe drinking water, and spare women and children the burdens and hazards of collecting ever-scarcer firewood for cooking.
You can also order Solar Cookers, or plans to build one for yourself, as a summer-time project for you and your children, or just for fun.
[/docs]
permanent link
Sticky Ideas
There's a new book about why some ideas survive and others die, by Chip and Dan Heath. I've been reading the excerpts... pretty interesting. Did you know that no children were ever harmed by tampered-with Halloween candy, or razor blades in apples, etc.? There were two incidents involving Halloween candy where children were harmed, but they harmed by their own parents, not by strangers. Yet this urban legend is changing how Halloween as celebrated in USA, and caused laws about tampering to be passed. (Hmm... did this start before or after the Tylenol scare of 1982? Apparently before.)
Six Principles of Sticky Ideas
... unexpected danger in a common activity...
Both stories called for simple action...
Both made use of vivid, concrete images...
... tapped into emotion: fear ... disgust...
PRINCIPLE 1: SIMPLICITY...
PRINCIPLE 2: UNEXPECTEDNESS...
PRINCIPLE 3: CONCRETENESS...
PRINCIPLE 4: CREDIBILITY...
PRINCIPLE 5: EMOTIONS... it's difficult to get teenagers to quit smoking by instilling in them a fear of the consequences, but it's easier to get them to quit by tapping into their resentment of the duplicity of Big Tobacco.
PRINCIPLE 6: STORIES...
[/docs]
permanent link
Mac Market Share up to 22% ?
Some Web server statistics summarized from the entertaining "interesting stuff" web site Boing Boing:
| Browser operating systems |
| Windows | 68.4% |
| Macintosh | 22.3% |
| unknown | 4.9% |
| Linux/Unix/Other | 4.1% |
| Browsers |
| Firefox | 50.7% |
| MS IE | 26.2% |
| Safari | 12.1% |
| others | 10.5% |
[/docs]
permanent link
"Inc. on Responding to Change"
In the article "Go Ahead, Make A Mess" in the December 2006 issue of Inc. magazine, we read:
Studies suggest that strategic planning is not only a waste of time, it's actually at least likely to hurt as help. [...]
Strategic planning essentially requires casting a blind eye to certain key facts: The world is an unpredictable place [...], markets are complex, competitors will counter your every move, and most managers fail to recognize the ways in which their own organizations deviate from their neat conceptions. [...]
According to the source cited in the article, companies that emphasize strategic planning did not perform better, on the average, than companies that do less planning.
The article also suggests that keeping things tidy has a cost: "people who claim to have 'very neat' desks report spending 36 percent more time looking for things than people who say they have 'fairly messy' desks."
[/docs]
permanent link
Industrial Logic
I'm happy to announce that I have joined Industrial Logic, a training and coaching company headed by Joshua Kerievsky. Please contact Industrial Logic if you want training or coaching on Design Patterns, Refactoring, Test-Driven Development, and/or agile software development with Extreme Programming or I.L.'s superset of XP, IXP.
Check out I.L's Smells to Refactoring Cheat Sheet (pdf) and the demo of one of I.L's eLearning products "Refactoring to Patterns Interactive".
[/docs]
permanent link
Symptoms of Design Debt
Dave W. Smith has posted some symptoms that indicate your team isn't refactoring enough to pay down accumulating design debt. Check it out.
Estimation Spread
Jockeying for Stories
Depending on a Drawing
Puzzling Tests
[/docs]
permanent link
Done?
How do you know it's done? (Speaking of some amount of code) That's a good question to ask, but perhaps the better one is "How can you show that it's working correctly, and that it is still working correctly after someone has changed something when you are not present?"
[/docs]
permanent link
BayXP Meeting on jBehave and rSpec
ShaneDuan will be speaking on jBehave, rSpec, and Test Driven Development, 6:00 PM, November 29, 2006, at Digital Chocolate, 1855 South Grant Street, San Mateo, CA 94402
Abstract: As TDD becoming more accepted development practices and we
gaining good experience on what it makes a good TDD, some have taken a
second look at what we can do to improve the framework that we are
using, so that it fits into our style of programming, rather than the
other way around. Two good examples are jBehave, and rSpec. Here we
are going to take a brief look at how they are trying to achieve this,
and share our ideas of TDD without the restriction of the tools.
[/docs]
permanent link
Comparison of Web App Frameworks
I thought this was interesting: Dave Pollak on Java, Smalltalk, Objective-C, and Ruby web-app frameworks.
It's too long for me to summarize, but he does mention some weaknesses in Ruby on Rails that I haven't seen elsewhere: security, stability, lack of multiprocessor support, etc. (And slowness and lack of unicode support, which I've already heard about.)
This guy says good things about NextStep (too bad he hasn't tried WebObjects), and mentions both strengths and weaknesses in Seaside on Squeak Smalltalk, as well as describing some features of Seaside that I haven't heard of before.
[...] even though haven't gotten to my "hate" point with Seaside (I've gotten to that point with most technologies, although with NextStep and Java, I came back to loving them... with Ruby, the jury's still out) I think it's got the right pieces parts to make it a quantum shift in web development. I think the kind of apps that could be commonplace with Seaside are totally not doable with any other web technology.
[/docs]
permanent link
Jane's Rule for Loading Dishwashers
I've been married for nearly 14 years, and I've only recently come to understand my wife's rule for loading silverware into our dishwasher. (Married women reading this blog may roll their eyes now.) It's only recently that she explained her rule and after trying it, I came to understand why it makes the washing-and-putting away process faster.
In order to try it, of course, I had to get over the idea that what I was doing was "the right way" and open myself to the idea that there might be a better way.
As you can see in the picture above, there is a sub-divided bin for silverware in the dishwasher's lower rack. Jane's rule for putting silverware into a dishwasher is to put all the forks in one sub-bin, all the spoons in a different sub-bin, all the steak knives in one sub-bin, and so on. This doesn't happen all at once, but only as silverware gets used and then put into the dishwasher for cleaning. Or taken from the sink where it's been soaking.
The true benefit of this comes not in the "loading the dishwasher" time, since this is slightly more effort than just sticking silverware into random sub-bins. The benefit comes when it is time to unload the dishwasher, putting the clean silverware into the drawer. Now I can just grab bunches of forks from the fork sub-bin, and put them into the fork-compartment of the silverware drawer. And I can grab bunches of spoons from the spoon sub-bin, and drop them into the spoon-compartment of the drawer. Unloading the dishwasher is much more faster than it would be if I had to take individual forks out of random sub-bins.
Compare this to test-driven development. There may be a little more effort when writing code, because you are writing programmer-tests to drive writing that code. It's an "unnatural" process, like sorting silverware into sub-bins when loading the dishwasher. But the true benefit comes later in the project (possibly just minutes or hours later), when you can rely on those programmer-tests to make refactoring safer. And since you fixed bugs during TDD, you have much less work fixing bugs later when it comes time to ship your product.
I heard in an Agile Toolkit podcast of a speech by Tim Lister, that a "process" or "method" is something other than the natural way we do things. After learning and practice, it can feel natural, but it isn't the way an untrained person would do something. The speaker's example was the process of swimming. Untrained persons, thrown into deep water, either drown or dog-paddle. They try to keep their head above the water, and their feet end up deep under the water. Dog-paddling is not an efficient way to swim, but it is natural. Olympic swimmers, on the other hand, have learned an unnatural way of swimming that is very efficient. Their heads are in the water, not above it, sipping air now and then from the side, and their feet are near the surface. In the "freestyle" event, the competitors can swim in any way they like, but they all swim in very similar way because that's the most efficient way currently known. Software development and management methods like Extreme Programming or Scrum are also "unnatural" processes, but can be very efficient.
[/docs]
permanent link
What is Agile Testing?
What is Agile Testing? The question came up on the Agile Testing mailing list, and among others, Lisa Crispin attempted to define it:
When I use the term 'agile testing' I'm talking about a set of good practices which has helped me and my teams deliver higher quality software, whether or not we were part of an agile team. We know that people, not tools or methodologies, are what makes projects succeed. Anything we can do to allow ourselves to do our best work fits my definition of 'agile'.
'Agile testing' practices come about by applying agile values and principles to testing. For example, communication is essential, and by collaborating with our customers up front to write test cases, we can improve communication. By driving coding with customer-facing tests, we're more likely to deliver what the customer wanted.
Some other examples: Automating our regression tests allows us time to do exploratory testing, so we strive for 100% regression test automation (I've never achieved anything close to this on a team who isn't automating unit tests, but we can still automate some testing). Exploratory testing is inherently agile so we work at getting better at that. Collaborating helps communication, so testers should get together with programmers and with customers from the start of a project, as well as pairing with each other.
Simple design is important in designing and automating tests. Refactoring applies to any type of coding including test scripts. Retrospectives are the single most important practice I can think of for any team, because you can't improve without identifying and focusing on what you need to improve. Dividing work into small chunks, having a stable build at all times (because we have continuous integration and collective code ownership), releasing business value frequently, all that allows us to do a better job of testing.
None of this is really new, and a lot of it I was doing years before I heard of XP, Scrum, agile, etc.
I haven't come up with a good elevator speech for agile testing so I have to resort to examples. I've heard people come up with some great one-liners but then I can't remember them. I think the Agile Manifesto works from a testing point of view as well as from a coding point of view.
Michael Bolton came up with a one-floor elevator speech:
Testers assist the developers and the rest of the project community
in identifying what is desired, what we have, and the differences between
them, with the four points of the Agile Manifesto as guiding principles.
Michael's slides (pdf) on User Acceptance Testing are also interesting. Check them out.
[/docs]
permanent link
A Leadership Warning by Dick Richards
Beware leadership that requires an enemy in order to hold power. Such leadership must create enemies. [...]
Beware leadership that requires fear in order to hold power. Such leadership must extinguish courage. [...]
It's short but meaningful. Check it out
[/docs]
permanent link
Don't Over-Use Mock Objects
Mock Objects used in Test-Driven-Development are "fake" objects that return specific values, and assert that calls are made with correct arguments and in the correct order. Avoid them. Mocks make your tests more fragile and more tightly-coupled. And at the same time, they reduce the integration-test aspects of TDD. They make your tests larger and more complicated and less readable.
Mocks reduce your tests robustness. If you change an object's API, a mock object's API may not change, making your tests out-of-date with respect to your non-test code. This depends on whether you are using a Mock-generating framework, or not, and how your mock object is implemented or how the Mock-generating framework is implemented.
In TDD, you normally write a test, write some code in a target class/method that passes the test. The third step is refactoring. That's when you can do "Extract Method"/"Extract Class" Refactorings to start creating other classes that cooperate with your target class (those extract classes may be "stub" classes at first, see below). Test-Driving with Mocks inverts that order: you create your target class and a mock class up-front, and plan on how they interact, instead of evolving that interaction in TDD's refactoring steps. You pre-judge the class design rather than evolve it.
Mocks are tool for decoupling, and I do sometimes use them. I limit my use of Mocks to those situations where the real object will not reliably do what I want. Examples: simulating errors in writing a file; simulating connections with a remote server; simulating errors from remote server. Often with libraries that were not designed with TDD in mind, such as the TWAIN libraries used for controlling scanners and implementing scanner-drivers.
Stubs are objects with hard-coded return values, but without the assertion-behavior that mocks have. Sometimes I do create stub objects during TDD, and later I focus on test-driving the stub-object class into being a "real" class. The existing tests (for code that is using those stub objects) help insure that I don't break anything that depends on that stub-becoming-real class. If a Mock generated from an interface was used instead of Stub, there would be a disconnect. My "real" class's return values (and implied behavior) could diverge from the mocked return values and nothing but code inspection or failing acceptance tests would tell me. I prefer to have failing tests tell me when behavior is changing. Remember change-detectors?
Mocks are tool, and a tool can be over-used or misused. I think for many people new to TDD, mocks are their new hammer, and every problem is treated like a nail, even when a saw or a screwdriver would be more appropriate for the situation.
[/docs]
permanent link
Commonly Heard, Uncommonly Thought
A commonly heard Myth: "A project with good developers will succeed no matter what their process."
So, no matter what their process, success is caused by "good developers" and failures are caused by "poor developers"? That isn't the case. A project with a bad process, will likely fail, no matter how good the developers are, unless the developers circumvent the process. The irony is that a "good developer" breaks the rules in this circumstance, while the "bad developer" follows the process rules. Too often developers are classified into "bad developers" and "good developers" because of systemic failures outside of their skills as programmers.
For example, if the domain is complex, and the process is "One month of analysis, followed by two months of design, followed by four months of programming, followed by three months of testing". (Dates fixed no matter how complete each of those activities is.) Well, that is only going to work if the team members actually do all of the activities (analysis/design/coding/testing) all the time. Testing during the design phase (perhaps via coding or by programmers "playing computer"). Thinking about coding, or actually coding, during the analysis phase. Revisiting analysis, design, and coding during the testing phase.
Very likely in such a 10-month "phased" project, they will find they are in trouble after the second month of "testing" (which often really means "bug-fixing"). At that time they will be forced to fix the flaws in their analysis, design and coding, with much grumbling and angst, and that "testing/bug-fixing" phase will end up lasting six months or longer instead of the planned three months. Sixteen months instead of 10 months.
Another myth is: "A project with bad developers will fail no matter how good their process is." (or "A good process won't make good developers out of bad ones.") This is also false in some situations, depending on how one defines a "bad developers". If it merely means "inexperienced", then depending on how well their process encourages detection and correction of mistakes, the simplicity of the domain, and how often and how well the developers get feedback from customer-proxies, domain experts, or end-users, the project could well succeed. If "bad" means "refuses to learn" and "refuses to cooperate", a good process will at least reveal those problems, but a bad process will let those problems stay concealed until disaster strikes.
This is why some poorly skilled developers are so against agile methods: their deficits in coding/testing skills or people-skills will be revealed by an agile method, and they would rather hide in a high-ceremony or chaotic process. Skill-deficits can usually be remedied by training, practice, and mentoring but only if people involved are motivated, and situation permits learning from mistakes.
In a chaotic "no process" situation, the advantage lies with the highly skilled/very-experienced developers who can write code with few defects, and who know enough to adopt a good informal process for their own work and for the work that's essential for the project's success. The other success factor I've seen in the "no process" situation is done by less-skilled developers: they work extra-long hours fixing mistakes that a good process would have prevented in the first place. I consider such overtime to be management failure.
"Success" can be very arbitrary, of course. In the history of software development, it's been fairly common for a horrible product to be declared a success and shipped. If that situation were uncommon, we wouldn't have license agreements that declares the software company is not liable for defects or even fitness for use.
More and more, software surrounds everything we see, touch, ride or fly in. My car has maybe a dozen CPUs running software for anti-lock brakes, digital dashboard, monitoring and controlling the engine, and so on. My "sonic" toothbrush maybe has a CPU as well. Clocks. iPods. Email. Phones. Chats and IM. Spell-check. Word-processing, image processing. Search engines. DNS. If your work is entirely digital, you're at the mercy of software all the time. Let's hope that the software we use is good enough; or we can do more than hope — we can learn good practices and good processes, seek feedback, and make it better.
[/docs]
permanent link
A Scrum Success Story
Richard Banks describes the benefits from having adopted Scrum in the last nine months. Check it out.
The biggest success factors were a willingness from the team to change and a structure with scrum that was focused on that change. The fact that an agile process is built around an understanding that it takes people to make something work and that we're not automatons is a great enabler for improvements. All I did was facilitate the change, lead a little bit by example to get things started and then get out of their way. If it wasn't for my great team then none of this would have happened and I'm indebted to them for the willingness and adaptability that they have shown over the last 12 months.
Compare to Richard's earlier attempt to introduce Scrum incrementally.
In a nutshell it was a disaster.
If you are planning on implementing Scrum you need to do it treat it as an all-or-nothing change. I talked with my CEO about organisational change and the mess that I made and he told me that if I wanted things to change I had to "go to war with the company. State what I wanted to do and take no prisoners in implementing the change." The only real benefit of the anarchic state things were now in was that any change would be an improvement and likely to be received well by the organisation.
[/docs]
permanent link
License to Scrum
Earlier this month, I became a licensed Certified ScrumMaster™. I should show up in the official rosters pretty soon; here is a screen-shot of my info in www.scrumalliance.org. (I need to correct some of that information.)
By the way, as this cartoon asserts, Scrum isn't a Silver Bullet. Neither is XP. However, they both do many of the things that Brooks recommends in his article "No Silver Bullet". (See previous posting).
[/docs]
permanent link
No Silver Bullet
So many people say "No Silver Bullet" mindlessly, without having read the article that made that phrase a cliché in the software profession. I'm speaking of the article by Frederick P. Brooks "No Silver Bullet: Essence and Accidents of Software Engineering," Computer, Vol. 20, No. 4 (April 1987). Here's a copy.
Lately, I've even seen the phrase "No Silver Bullet" used to argue against adopting agile methods. Here's what that article really says; you might find its emphasis on agile concepts surprising. Concepts like: "Working software over comprehensive documentation", "Customer collaboration over contract negotiation", and "Responding to change over following a plan". Quotes:
The familiar software project, at least as seen by the nontechnical
manager, has something of this character [of the werewolf]; it is
usually innocent and straightforward, but is capable of becoming a
monster of missed schedules, blown budgets, and flawed products. So we
hear desperate cries for a silver bullet--something to make software
costs drop as rapidly as computer hardware costs do.
But, as we look to the horizon of a decade hence, we see no silver
bullet. [...]
He's referring to fact that hardware costs get cut regularly and why software costs does not have a similar perpetual future of cost reductions. But he also says: (Boldfacing is my emphasis)
Skepticism is not pessimism, however. Although we see no startling
breakthroughs--and indeed, I believe such to be inconsistent with the
nature of software--many encouraging innovations are under way. A
disciplined, consistent effort to develop, propagate, and exploit
these innovations should indeed yield an order-of-magnitude
improvement. [...]
He goes on to describe the essential hard parts of software development (where the costs can't be compressed):
The essence of a software entity is a construct of interlocking
concepts: data sets, relationships among data items, algorithms, and
invocations of functions. This essence is abstract in that such a
conceptual construct is the same under many different representations.
It is nonetheless highly precise and richly detailed.
I believe the hard part of building software to be the specification,
design, and testing of this conceptual construct, not the labor of
representing it and testing the fidelity of the representation. We
still make syntax errors, to be sure; but they are fuzz compared with
the conceptual errors in most systems.
He then goes on to say why software specification,
design, and testing can't be just once:
All successful software gets changed. Two processes are at work.
First, as a software product is found to be useful, people try it in
new cases at the edge of or beyond the original domain. The pressures
for extended function come chiefly from users who like the basic
function and invent new uses for it.
After some discourse in various areas, Brooks gets back to requirements: (Boldfacing is my emphasis)
Therefore, the most important function that the software builder
performs for the client is the iterative extraction and refinement of
the product requirements. [...] Complex software systems are,
moreover, things that act, that move, that work. The dynamics of that
action are hard to imagine. So in planning any software-design
activity, it is necessary to allow for an extensive iteration between
the client and the designer as part of the system definition.
I would go a step further and assert that it is really impossible for a client, even working with a software engineer, to specify
completely, precisely, and correctly the exact requirements of a
modern software product before trying some versions of the product.
Sounds like he's advocating agile software development with frequent releases and close customer involvement. He mentions Rapid Application Development(RAD) and protyping. today we have experience in iteratively and incrementally producing software without throw-away-prototypes, and without some of the difficulties of RAD.
He also mentions the consequences of this style of development to morale: (Individuals and interactions over processes and tools.)
The morale effects are startling. Enthusiasm jumps when there is a running system, even a simple one. Efforts redouble when the first picture from a new graphics software system appears on the screen, even if it is only a rectangle. One always has, at every stage in the process, a working system. I find that teams can grow much more complex entities in four months than they can build.
Brooks says:
Incremental development--grow, don't build, software. I still remember the jolt I felt in 1958 when I first heard a friend talk about
building a program, as opposed to writing one. In a flash he broadened
my whole view of the software process.[...]
The building metaphor has outlived its usefulness. It is time to
change again. If, as I believe, the conceptual structures we construct
today are too complicated to be specified accurately in advance, and
too complex to be built faultlessly, then we must take a radically
different approach.
[...]
Some years ago Harlan Mills proposed that any software system should
be grown by incremental development. That is, the system should
first be made to run, even if it does nothing useful except call the
proper set of dummy subprograms. Then, bit by bit, it should be
fleshed out, with the subprograms in turn being developed--into
actions or calls to empty stubs in the level below.
I have seen most dramatic results since I began urging this technique
on the project builders in my Software Engineering Laboratory class.
Nothing in the past decade has so radically changed my own practice,
or its effectiveness.
He mentions the necessity of writing code top-down, which isn't entirely a necessity today. We can write programs in vertical slices, but not everything in that slice has to be "final" code. Since (as Brooks says in his article) coding isn't the hardest work of software development, we can code simple things in earlier iterations and grow those later into more complex things as needed, making time available for the customer to see some functionality early.
[/docs]
permanent link
Specification Documents as Tests
I was in a conversation with someone whose project is using Scrum. I was, if I remember correctly, trying to talk about the idea of how agile projects try to minimize the creation of artifacts not directly needed by the end-user or the developers.
She responded by talking about how essential the specification documents are for her team. I asked her "Who reads these documents?" and she said that the testers do. (NOTE: not primarily the programmers!) I then suggested to her that maybe in this agile development process, the testers should be writing these documents instead of reading them. And not writing them as static documents, but as executable tests in a form understandable by the Product Owner and programmers. Her face lit up with understanding.
I hope I helped. If she recognizes my attempt at recounting this conversion, I hope she will let me know if this helped her team in their adoption of agile practices.
[/docs]
permanent link
Various Bits
Review of a cool-looking Subversion App for MacOS X. I'm looking forward to using it. Maybe using it with code.google.com.
Lots of interesting stuff in Richard Thaler's Mental Accounting (pdf) paper:
[...] (1) The value function is defined over gains and losses relative to some reference point. The focus on changes, rather than wealth levels as in expected utility theory, reflects the piecemeal nature of mental accounting. Transactions are often evaluated one at a time, rather than in conjunction with everything else.
(2) Both the gain and loss functions display diminishing sensitivity. That is, the gain function is concave and the loss function is convex. This feature reflects the basic psychophysical principle (the Weber-Fechner law) that the difference between $10 and $20 seems bigger than the difference between
$1000 and $1010, irrespective of the sign.
(3) Loss aversion. Losing $100 hurts more than gaining $100 yields pleasure: v(x)< -v(-x). The influence of loss aversion on mental accounting is enormous, as will become evident very quickly. [...]
[...] For example, a prix fixe dinner, especially an expensive multi-course meal, avoids the unsavory prospect of matching a very high price with the very small quantity of food offered in each course.* Along the same lines, many urban car owners would be financially better off selling their car and using a combination of taxis and car rentals. However, paying $10 to take a taxi to the supermarket or a movie is both salient and linked to the consumption act; it seems to raise the price of groceries and movies in a way that monthly car payments ( or even better, a paid-off car) do not.
More generally, consumers don't like the experience of 'having the meter running'. This contributes to what has been called the 'flat rate bias' in telecommunications. Most telephone customers elect a flat rate service even though paying by the call would cost them less. [...]
Lots of stuff to think about there. This "meter running" aversion is the real reason online micro-payment systems, or other web-apps that try to charge by each use, haven't really taken off yet. Shopping sites that aggregate your purchases, and then offer "free shipping" if you buy enough stuff, are taking advantage of these "mental accounting" biases. How many times have you bought a book on Amazon that you really didn't need, but which put you over the free shipping price.
[/docs]
permanent link
Code Smell: Long Parameter Plus + "Undocumented Language"
Wil Shipley describes the evil of Long Parameter List in some of Apple's APIS. But he is describing a variation on that code smell. It's kind of a combination of "Long Parameter List" with "Undocumented Language". I will bold-face that aspect of the problem in my quotes from that blog:
[...] There is usually one, giant, all-encompassing "setAttributes" function and one "getAttributes" function for setting and getting every value associated with a Carbon "object", which requires to you laboriously build up and then laboriously unparse huge, special parameter structures for even the simplest call. For example, see one of the newer (v6.4) QuickTime calls, for setting parameters on a FireWire video camera: VDIIDCSetFeatures(VideoDigitizerComponent ci, QTAtomContainer *container). That one call is used to set a HUGE number of parameters, which is "simple," except, oh, it takes eighty lines of code to prepare for that one call, and the parameters you can set are not actually documented in the documentation. [...]
[...] Carbon uses the same type, OSTypes [4-character constants] (eg, 'sit!'), extensively for everything: return codes, building parameter blocks (specifying what you want to do, how you want to do it), loading QuickTime components, etc., which means reading a function description doesn't actually give you enough information to know what to actually pass it. (Eg: "Pass it some combination of four-character constants. Good luck!") [...]
[...] (On a side note, sadly, the AppKit and Foundation team just announced they are moving AWAY from using enumerated types ... because they are worried that enumerated types could have unspecified sizes. But, seriously, Ali, this is totally broken -- enumerated types make reading and writing code MUCH easier, and they enforce sanity in switch() statements and if() comparisons. Please don't break this!) [...]
[...] For some reason some Carbon programmers think it's easier for them to add and remove APIs if they never commit to any particular functionality, so they use those giant anonymous set-get functions and then just have you build up a list of parameters yourself, with the idea being that it's a lot more flexible if they change the parameters out from under you than if they change the functions. [...]
[...] The fact that Apple had to write EIGHTY lines of example code to show how to set A SINGLE PARAMETER on SOME TYPES of cameras to its maximum value shows that THESE APIS ARE SERIOUSLY BROKEN. [...]
It goes on with examples from the KeyChain API. Check it out
[/docs]
permanent link
Test-Driven Database Development
Scott Ambler announces:
My article "Test Driven Database Development" appears in the current issue
of TASSQuarterly magazine. The issue itself is available at
http://www.tassq.org/quarterly/docs/tassq_magazine-0609.pdf. Just like you
can take a TDD-based approach to developing application code, you can do the
same thing with your database schema. I personally think that it's time
that we start adopting quality practices for database development. If you
work in an organization which considers data to be a corporate asset, and/or
you're implementing functionality in your database via stored procedures,
stored functions, or even OO code, then shouldn't you also have a regression
test suite in place?
Quotes:
There are several advantages to TDD:
1. It promotes a significantly higher-level of unit testing [....]
2. It enables you to take small steps when [....] For example,
assume you add some new functional code, compile,
and test it. Chances are pretty good that your tests
will be broken by defects that exist in the new code.
It is much easier to find, and then fix, those defects if
you've written two new lines of code than two
thousand.[...]
3. It promotes detailed design. Bob Martin [5] says it
well “The act of writing a unit test is more an act of
design than of verification. It is also more an act of
documentation than of verification. The act of
writing a unit test closes a remarkable number of
feedback loops, [...]"
A test-driven database development (TDDD) approach provides the
benefits of TDD, plus a few others which are database related.
First, it enables you to ensure the quality of your data. [...] Second, it enables you to validate the functionality implemented within the database (as stored procedures for example) that in the past you might have
assumed “just worked”. Third, it enables data professionals to
work in an evolutionary manner, just like application
programmers. [...]
Part of test-driven-development is refactoring, and Scott gives a high level description of a database-refactoring in this article. There is more at www.agiledata.org. He also lists several database testing tools. Interesting magazine - it also has articles by James Bach, Rex Black, Michael Bolton, Duncan Card, Fiona Charles, Cem Kaner, Joe Larizza and Richard Bornet. Check it out.
[/docs]
permanent link
Super-Coder
If you are a super-programmer, incapable of error, do you need to do Test-Driven-Development (and Story-Test-Driven-Development (pdf))? Do you need programmer/unit tests and automated acceptance tests?
Maybe not for a small, solo project, but what about working in a team? What will happen if one of those other programmers on the team (maybe one who hasn't been hired yet) has to change something that your code depends on? Or even changes your code? Perhaps you've moved to another project, so you're not there to change your code perfectly.
Of course, you are not perfect. You will inject defects into your code sometimes. Your teammates will too. And then there are changing requirements, and changing interpretations of requirements, and operating system updates, and library updates... lots of opportunities to write imperfect code or introduce defects when modifying the code.
One way to find those imperfections is manual testing. But this is the 21st century - automate that testing! The first time you run well-written automated tests on "legacy code" (defined as code without automated tests, according to Michael Feathers), you are very likely to find bugs. Often just writing such a test helps me find code-defects, and then I run the test to confirm it. And they are so cheap to run (if written right) that using them to find less-frequent "regression" bugs will pay off.
Story-Test-Driven-Development starts off with the Customer (Product Manager/Domain Expert) defining the expected behavior of a feature using automated acceptance tests. Defining these tests clarify the requirements, which even a perfect programmer can benefit from. Without fully understanding the requirements, any programmer can write the wrong code: a "bug" even though code itself is flawless.
When an expert pair of programmers, familiar with TDD, decided to skip Test-Driven Development, they paid a price. Quoting James Shore:
What we should have done at this point is archive our spike and start over using proper test-driven development. But we didn't. Instead, we kept going, trying more and more ideas, [...] until we didn't have a spike any more. We had a nascent application. One without tests. In other words, we already had a fairly sizable amount of technical debt and the application was only a week or two old.
[...] It's now a month later and progress has slowed to a crawl. In the first month, we were adding significant new features every week, sometimes every day. What happened?
In two words: technical debt. To make progress so quickly, we cut a lot of corners. We didn't implement any tests. We only programmed for the best-case networking scenarios. We let bugs creep in.
[...] we took on technical debt and now we're paying the cost [...]
THAT was a six week project. They planned on paying off that technical debt after initial demo and alpha pre-release. (Which has happened. Check out CardMeeting.) Imagine how bug-ridden and fragile the source code of a 10-year-old product with few or no automated tests can be. If you work for a big company with "version 9.x" product, it's very likely you already know. It doesn't have to be that way. It doesn't have to stay that way.
[/docs]
permanent link
QuickBooks 2007 for Mac
My team has finished Mac QuickBooks 2007 and it is now available for pre-order on Intuit's website. (Actual availability is October 2.)
I think this is the best version we've done so far. Check out the Demos!
This is the second year we've used a Scrum/custom-Agile process. This year that process helped us make the development, and testing processes even more smooth and high-quality. No major surprises, no major fire-fighting.
[/docs]
permanent link
Application Layering
In large applications, just to be able find classes and functions, and to be able to comprehend the structure "in the large" (to see the forest and the trees), you want "layers". Or "modules". Or "packages". Some division of responsibilities larger than the individual classes. In the image below, I show a hypothetical ideal application with eight modules or layers, compared to a near-worst-case legacy application.
The order of the layers/modules in this diagram is arbitrary and not meant to imply the amount of connections between layers, nor the directions of those connection. (But keeping the connections between modules going one-way, even if that requires adding additional "interface" modules, is a Good Thing.)
When you are doing iterative/incremental development, as recommended by all agile methods, how do you keep to the ideal layering and avoid that legacy code scattering of responsibilities? By paying careful attention to the Single-Responsibility Principle and other principles of good design, and by cleaning up any the code smells that you notice which indicate violations of SRP and other good design principles. Decide on which layer a class belongs to, each time you create a class. (Create new layers as needed.) Each feature that you implement will likely involve several layers, so pay attention to where those layers are.
If you don't pay attention during agile development, your application structure could look like a Tetris game gone badly. Refactor every day to keep the structure clean and well-organized.
[/docs]
permanent link
Barriers to Self-Organized Problem Solving
Jeffrey Phillips has identified three reasons why self-organized problem solving doesn't happen at work. Quote:
Why is it when we notice a problem, especially a hole in a process or an inefficent process, that we don't get a group together to immediately solve the problem? I guess there are three relevant reasons: 1) ownership 2) importance 3) politics.
Ownership... someone else appears to own the problem, and if you attempt to take it on you may be working outside your area of focus... Even if no one owns the problem, it is very likely that you'll receive negative feedback for working on a problem rather than encouragement, since working on the problem is probably not "your job"....
Importance... Often, everyone recognizes a problem exists yet it is small enough or happens so infrequently that it seems that the problem is not important and not worth solving. Everyone assumes someone else will solve it, or the team that "owns" the problem does not recognize how important that problem is to another function or work team....
Politics... a manager in a different group may feel angry that you've identified what you think is a problem in his area....
This is why Agile and Lean methods have the workers "owning" the process they use - so they can improve it at will. I believe Lean (as practiced by Toyota) also has the emphasis on improving any and every part of the process, no matter how "unimportant" it might be.
Patrick Lencioni's Overcoming the Five Dysfunctions of a Team and his subsequent Field Guide identifies how to "cure" the political problems by Building Trust, Mastering Conflict, Achieving Commitment, Embracing Accountability, and Focusing on Results. This is easier said than done, though I've seen some of this team-building stuff really work in Jerry Weinberg's workshops.
[/docs]
permanent link
Two Videos on Agile
Via Ed Gibbs, Beyond Test Driven Development: Behaviour Driven Development by Dave Astels, author of "Test-driven Development: A Practical Guide". And, Scrum by Ken Schwaber, a founder of the Agile Alliance and the Scrum Alliance.
Check it out
[/docs]
permanent link
Loops and OO and Parallelism
A person in a dream I had this morning said that whenever you have code that has loops operating on lists, your code is not "object-oriented". Now I don't necessarily agree or disagree, but this was the example I thought of (in my dream):
app tells book 'change margins'
In some implementations, the cascade of method calls to implement that might be:
book loops through its chapters, tells each chapter to 'change margins'.
chapter loops through its pages, tells each page to 'change margins'.
But it could be different. What if the chapters share margin objects from the book, and the pages share margin objects from the chapters? If the data is shared, and not duplicated, then we don't need to loop everywhere changing it. (Though we probably still need to loop somewhere to inform all these objects that the data they depend on changed, and they need to re-layout themselves.)
app tells book to 'change margins'
book tells margins to 'change'.
margins tells all of its dependents (pages) to update themselves. (implied loop)
each page tells all of its dependents (chapters) to update themselves. (implied loop)
each chapter tells all of its dependents (the book) to update themselves.
[but what if pages are dependents of chapters? infinite loop? hmm.]
This dream was partly inspired by an article in MacTech magazine that I read, about parallelizing loops in Fortran-90 and C/C++ using some options available in Intel's compilers for MacOS X. With a couple of C pragmas (or the Fortran equivalent), the compiler takes care of splitting loop processing over multiple CPUs -- doing that yourself using Posix threads would take nearly a page of code, or somewhat smaller if you have threading classes to use.
(It also struck me that Fortran-90 is quite a different language than the Fortran I learned in the 1980's.)
Could you parallelize the looping message cascades of this book / margins example? Maybe. The loops that re-lays out each page and chapter needs one or two pieces of information from the previous page: the previous page number (so we can compute current page number = previous page number + 1). But in fact, there may be more pages than before, if the margins are made smaller. Or fewer pages if the margins are made larger. Maybe "page" is a just a flyweight object and we really only have "book" and "chapter" (or "section") where formatting is concerned. But a chapter still needs to know the last page number of the previous chapter, to get its own page numbering right.
[/docs]
permanent link
Objective C 2.0
Andy Matuschak has documented the new features of Objective-C by examining the code that Apple checked into their gcc branch in the gnu.org repositories. Check it out. (If you find his gray text on black background annoying, read the RSS feed instead.)
[/docs]
permanent link
Build Enlightenment
Check out Jason Sankey's article on Build Enlightenment (via a blog byDaniel Ostermeier and Jason Sankey)
This article describes the properties of an effective build system, starting from the most basic requirements and working towards more advanced features. Follow the article step-by-step to drag your build system out of the darkness and attain build enlightenment!
[/docs]
permanent link
BDD
Check out Dave Astel's paper (pdf) on TDD/BDD. Quotes:
[...] There are a score of books available on TDD, mine even won a Jolt
award. So it seems that everything is rosy? Everyone who's doing TDD is fully understanding it and getting the full benefit, right?
Fat Chance!
Too few people I talk to really understand what it's really about. That means that many people who practice TDD are not getting the full benefit from it. What's wrong?
The focus on testing
Well... one thing is that people think it's about testing. That's just not the case.
Sure, there are similarities, and you end up with a nice low level regression suite... but those are coincidental or happy side effects. So why have things come to this unhappy state of affairs? Why do so many not get it?[...]
So with people thinking about testing, it's easy to come up with all sorts of negative reactions and reasons not to do it... especially when time gets short and the pressure's on.
By the way, I HATE saying TDD is "not about the testing". I have to say it now and then because of people not realizing it's about designing, rather than testing. The fact that the word "test" is in the name just helps confuse the issue. My preference for naming this design technique, is to call it "Behavior Driven Design". Or "Behavior-Spec Driven Design". With BSDD, I could say BSDD is about the behavior-specs, but mostly about driving design.
Alistair Cockburn's "Executable Example Driven Design" (XXDD) is OK, but a bit long-winded. And I don't want to have say "Dos-equis Driven Design is not about beer." :-)
The problem is mental frameworks. People reject ideas that don't fit into their mental framework. You say "test" and the listener's mental framework comes up with these and other assumptions:
- Testing can be done manually.
- I can do implementing, someone else can do testing.
- Testing looks for bugs.
- Tests need something to test.
- Testing is hard.
- We can skip testing if we are good enough programmers, or time is short.
None of those assumptions directly applies to TDD. TDD can't be separated into tests written by one person and code written by another because the TDD cycle of test-code-refactor is a design process that one person (or a pair) goes through in cycles, where each cycle takes only minutes. TDD/BDD helps me find bugs, but that's not the purpose of TDD. TDD isn't TDD is you do the "test" part manually.
If you say "Test-Driven Development is not about the tests," that usually doesn't provide the zen-like kick in the brain-pan that the speaker intends. Instead, the phrase gets rejected because it doesn't fit into the listener's mental framework for "tests". The listener probably thinks you're nuts. He or she might just flip the "bozo bit" and stop listening to you.
If instead you say "Behavior Spec", the listener's mental framework will have fewer contradictory assumptions. For example:
- Specs are written before coding.
- Specs can provide examples.
- Specs tell you what you need to implement.
- Someone else can write specs and I can write the code.
Not all of those assumptions are true for BDD, but there are fewer contradictions to this idea of BBD that you're trying to introduce into someone's mental framework. Behavior Specs are written before the code exists (though only minutes before). Behavior Specs are executable examples. Behavior Specs tell you, more or less, what you need to implement next. There is still the contradictory idea that someone can write the Spec and you can write the code, but that isn't as difficult to overcome as all the other assumptions that come up with the word "test".
[/docs]
permanent link
On Puzzles in Interviews
Johanna Rothman, author of Hiring The Best Knowledge Workers, Techies & Nerds: The Secrets & Science Of Hiring Technical People, writes about a few examples where puzzles or riddles in interviews were not providing help in appropriate hiring.
At another client, the interviewing team liked word riddles. [...] they missed bunch of people who were hired by other managers in the company [...] Several of the new hires misunderstood the logic puzzle because English wasn't their first language, and they didn't hear the puzzle correctly. Two of the new hires decided that if that's how the team evaluated potential candidates, they didn't want to work with that team. And one of the new hires had attempted to explain why there was more than one solution to the riddle, but the interviewer couldn't hear that.
Our choices of questions that are not directly related to the job (and no matter how you slice it, puzzles and riddles are only indirectly related to the job) reflect our culture. It's quite clear that the puzzles and riddles interviewers choose reflect their individual culture. And without meaning to, that culture primarily selects for people just like themselves.
Previously she wrote on why puzzles discriminate:
Using puzzles and riddles discriminate against anyone who isn't a (middle-upper class) white American suburban male.
Girls, for example, do not have access or the alone time to spend doing books of puzzles and riddles. It is socially unacceptable for even the geekiest girl you know to do this. A girl who spends time pursuing puzzles and riddles for her own pleasure runs the colossal risk of being ostracized from all the other girls. Boys tend to discover puzzles and riddles during middle school and continue to pursue them through high school. Middle and high school for girls is much more about social ability and social connections.
Check it out
[/docs]
permanent link
Go For High Benefit
Pete Abilla, blogged about picking high benefit / low effort / "vital few" customer-focused features at Amazon.
He also advises to not get into a feature competition with competitors, since that takes your eyes off the customer and leads your product or service into featuritis. Too bad for users who want a good user-experience instead of featuritis that Microsoft doesn't take that advice.
Last year, Apple's Developer Conference had tongue-in-cheek posters advising Microsoft to "start your copiers". This year's keynote address, showed that in not-yet-released Vista, Microsoft did just that. Check out the keynote video to see side-by-side comparisons of Apple's email UI and Vista's email UI, and Apple's Calendar UI and Vista's Calendar UI and remember that the Apple UI came first.
Of course, Apple has been shipping its email and calander apps for a quite a while now, but Vista is still in beta... so if you want that UI today, you better get a Mac.
This WWDC year's posters refer to Microsoft/Vista as being a "copycat". (FYI: Apple has been using names of big cat species for its OS releases - the previous release was Tiger, the next one will be Leopard.)
[/docs]
permanent link
Uncommon Education Necessary for Scrum?
Ron Jeffries versus the idea of "Scrum is common sense"... "Einstein said: 'Example isn't another way to teach: it is the only way to teach.'" Check it out. (Thanks to adapdev for the link.)
[/docs]
permanent link
Incremental Development
Imagine that you are developing version N+1 of a software application that has a document format that have to be upgraded to handle the new data that version N did not have. Pretend it's a relational database; you will have to add tables and columns, etc.
One way to handle this is to imagine all the possible transformations you will need to make to the database, and implement them. Here's a list partially taken from Refactoring Databases: Evolutionary Design by Scott W. Ambler and Pramod J. Sadalage.
- Add Column
- Add Table
- Add View
- Drop Column
- Drop Table
- Drop View
- Change Column Type (converting values)
- Change Column Values
- Change Column Format
- Introduce Calculated Column
- Introduce Surrogate Key
- Merge Columns
- Merge Tables
- Move Column
- Rename Column
- Rename Table
- Rename View
- Replace Column
- Replace One-to-Many With Associative Table
- Replace Surrogate Key with Natural Key
- Split Column
- Split Table
Implementing every feature in that list (and the other features I didn't mention) will take a while. It gets you a nice, complete file-upgrade library. But we're not in the business of developing libraries, we're in the application business. If you implement every feature, many of them will probably not be used by your application. Waste.
The other way to handle this is to proceed with the development of version N+1 of the application. Whenever you need to do one of the above transformations, you implement it, but only what you need. You get the application delivered sooner, because you're concentrating on customer value. You also have a file-upgrade library that does just what you need.
One of the teams that I worked did precisely this, and choose the incremental approach. It turned out we only needed a few of these features in our file-upgrade code. We did brain-storm a list of all the transformations might need, but we only implemented the ones we needed when the need arose. No waste.
[/docs]
permanent link
Peter Drucker Quote
"My greatest strength as a consultant is to be ignorant and ask a few questions" - Peter Drucker
[/docs]
permanent link
NSpecify Example
On 2006 Jul 24, at 8:29 PM, Maruis Marais emailed to me:
Hi Keith,
Your posts around BDD is interesting. I'm in the .NET space and
have followed the BDD movement since Dave Astels talked about BDD
at XP2004 (I think).
I've developed a C# version of a BDD framework called NSpecify and
thought you might find it interesting.
Here is a quick code example:
using System;
using NSpecify.Framework;
namespace SampleBehaviour
{
[ Functionality() ]
public class Calculation
{
int one = 1;
int two = 2;
[ Specification() ]
public void OnePlusOneMustEqualTwo()
{
Specify.That( one + one ).Must.Equal( two );
Specify.That( two ).Must.Be.GreaterThan( one );
Specify.That( two ).Must.Not.Equal( one );
}
[ Specification(),
ExpectedException(typeof(DivideByZeroException) ) ]
public void OneDevidedByZeroMustThrowException()
{
int zero = 0;
Specify.That( one / zero ).Must.Equal( one );
Specify.Failure( "The expected exception was not
thrown" );
}
}
}
I have a NUnit integration working and at the moment I'm working on
creating a VS2005 test tip to integrate NSpecify into VSTS Testing
Framework.
[/docs]
permanent link
Behaviour Spec Organization
DavidChelimsky writing on Behaviour Spec Organization. Quotes:
One of the things that really interests me about the Behaviour Driven Development discussion is the effect it has on how you organize your specs. [...]
The reason that clarity is important is that specs serve as documentation for future development. If you want to understand how to use a class, look at the tests. Right?
# Disclaimer - this is also an example, not a recommendation
# -- but closer to a recommendation than the example above!
A stack
- should add pushed item to the top of the stack
- should return the top element on peek
- should not remove the top element on peek
- should return the top element on pop
- should remove the top element on pop
An empty stack
- should be empty
- should complain on peek
- should complain on pop
An almost empty stack (with one element)
- should not be empty
- should not be empty after receiving peek
- should be empty after receiving pop
[...]
[...] The generic "A stack" context describes the normal case for how a stack behaves, while the other contexts deal specifically with how a stack at or near a boundary behaves differently from the normal case. This is a far more clear specification [...]
Check It Out
[/docs]
permanent link
Behavior Driven Design
I am now convinced that the word "test" in Test-Driven Development is a barrier to understanding that TDD is a design process. Next time I give a presentation on that subject, I will use "Behavior Driven Design" and "Behavior Specs" rather than "Test-Driven Development" and "Programmer Tests".
I have slides on TDD and BDD for your perusal. Compare them and see what you think. (Better, show the BDD slides to people not familiar with TDD/BDD and see what they think.)
TDD Slides (pdf)
BDD Slides (pdf).
Under Creative Commons copyrights, you may use these slides, unmodified, for non-commercial purposes.
[/docs]
permanent link
12 Benefits of Test Driven Development
J. Timothy King blogs about 12 benefits of TDD. Check it out.
[/docs]
permanent link
Command-Query Separation
When I was programming in Pascal a long time ago, we had Functions and Procedures. Functions returned a value, Procedures did not. But there was more to it than that. Procedures had side-effects, but functions generally did not. This idea is called “Command-Query Separation” and it is useful today when writing object methods. Quoting from the Wikipedia:
Command-query separation (CQS) is a principle of object-oriented computer programming. It was devised by Bertrand Meyer a part of his pioneering work on the Eiffel programming language.
It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.
[/docs]
permanent link
On Behaviour-Driven Development
Two Quotes from http://behaviour-driven.org/GettingTheWordsRight. (It's a wiki, this page seems to be authored by Dan North and Dave Farley.)
"Behaviour-Driven Development grew out of a thought experiment based on Neuro-Linguistic Programming techniques. The idea is that the words you use influence the way you think about something."
"Concentrating on finding the right words led us to think about the process differently, for example, the fact that the words we use in BDD are very much focussed on the behaviour of the system led us to better understand the very close relationship between the stories we use to specify behaviour and the specifications we implement in BDD in place of tests. It also helped us gain further insight into some of the failure modes we have experienced in TDD projects."
[/docs]
permanent link
Measuring Practices
Responding to Bruce Eckel's blog post Programming as Typing, where Bruce tries to get across the idea that programming is more about the programmers, rather than the act of hitting keys on a computer's keyboard. And that reasoning about programming practices is often based on fundamental, unproven (or even wrong) assumptions. Two quotes from Bruce: (emphasis is mine)
It may not even be possible to prove things logically when it comes to programming. So many of the conclusions that we draw this way appear to be wrong. This is what I like about the book "Peopleware," [by Tom Demarco or Larry Constantine?] and also "Software Conflict 2.0" [by Robert Glass] that I'm now reading. These books point out places where we operate based on what seems perfectly logical, and yet is wrong (one of my favorite studies in "Peopleware" shows that, of all forms of estimation, the most productive approach is when no estimate at all is made).
I think the essence of what the agilists are doing is a perfect analogy to the discovery of the scientific method. Instead of making stuff up -- and if you look back at all the "solutions" we've invented to solve software complexity problems, that's primarily what they are -- you do an experiment and see what happens. And if the experiment denies the arguments you've used in the past, you can't discard the results of the experiment. You have to change something about your argument.
Isaac Gouy mentioned two papers that provide statistical support for the effectiveness of some of the common practices of agile methods.
Quoting the blurb here about a study of 29 projects at 17 companies: (emphasis is mine)
Recent research from Harvard Business School professor Alan MacCormack and colleagues proves a theory about software development that has been gaining adherents for some time: The best process is an evolutionary one. Focusing on the area of Internet-software development, the researchers uncovered four practices that lead to success: an early release of the evolving product design to customers, daily incorporation of new software code and rapid feedback on design changes, a team with broad-based experience of shipping multiple projects, and major investments in the design of the product architecture.
And this paper (pdf) analyzing 29 projects at HP. Quotes: (emphasis is mine)
In the final model predicting defect rate, three development process
measures and one control variable (for systems software projects) were significant. Together, these measures explain over 74 percent of the variation in defect rate. The significant development process measures are the use of
|