You are hereA New Chore for Sisyphus / Appendix B - Causal Relationship of Difficulty to Flaws
Appendix B - Causal Relationship of Difficulty to Flaws
Table 2 summarizes the causal relationship between a project's
inherent difficulty and the resulting flaws that inevitably result
from the ill-practices described in Chapter 2. This material was
originally presented in Chapter 5 which provides a more detailed
explanation of the relationship of inherent difficulty to the
problems described in Chapter 2. As with Chapter 5, an explanation
is also given as to why the same practice does not result in systemic
flaws when the project has a low inherent difficulty.
Low Inherent Difficulty
High Inherent Difficulty
Coding before functional requirements defined.
Early code represents uncoordinated fragments of the ultimate solution.
The worst consequences are minor differences in the user interface for somewhat related elements.
Interdependencies between program elements mean that the pieces must eventually be made to work together.
Not developing formal functional requirements.
Uncoordinated program development that results in a product that "only an engineer could love." No functional testing other than developer tests to their own understanding of the program.
Agile methodologies such as collecting user stories are sufficient for documenting the user requirements. User approved tests are sufficient to show the program "works."
Different functional areas of the program must work together to solve the problem. If these dependencies are not identified in functional requirements, they must be found by the developers, testers, or users.
No preliminary design.
Program interdependencies are not identified. There is no technical basis for project scheduling "guesses".
The program to be developed typically fits a known "design pattern." Code estimates can be accurately based on similar functions developed for other projects.
Functional and data dependencies are not understood during development. These dependencies must be resolved during development or testing.
Lack of common abstract objects.
Multiple program elements must deal with raw data or data that reflects the lowest level implementation.
Lack of functional and data coupling mean there aren't any significant common abstract objects.
Significant functional or data coupling means that multiple program elements must interact. If this interaction is not through common abstractions, each implementation must be separately debugged and then maintained in later releases.
Continual or frequent "refactoring"
Constantly debugging what is effectively new code. Iterative development methods should converge to a final finished product.
Refactorings converge as the developers add functionality and learn from previous iterations.
Complex, highly interdependent functionality cannot be resolved at the developer level. The developer ends up chasing the last problem with each new iteration. Fixing one problem aggravates another.
Near duplicate copies of code occur within the program.
Little or no common functionality so little likelihood that the same functionality will be needed in several different locations.
Each copy of the code must be separately tested and maintained. Bugs in the original are propagated to all copies. If the processing is visible to the end user, a continual effort must be maintained to ensure that all instances have the same "look and feel."
Unrealistic unit testing
The pieces work but the assembled pieces do not.
Few or no dependencies between program elements means that, at worst, the developer has to do additional testing later in the development cycle.
Eventually the pieces must be made to work together. Inputs and outputs must be made to match what is provided by upstream processing and required by downstream processing.
Hiding the problem
Programming errors are masked from the end user rather than fixed.
Masked errors are confined to a localized portion of the overall program. They must eventually be fixed but the impact is also local.
Instabilities in one part of the program affect other areas resulting in the overall program being unstable.
Lack of user interface consistency
Program is hard to document, hard to test and hard to use.
Lack of functional coupling means that only an overall "look and feel" coordination is possible for user interface elements.
In order to be acceptable to the user, functionally similar actions need to be be implemented similarly. Inexplicable difference make the program hard to test, document and use.
Extreme focus on meeting the minimal interpretation of the requirements
Code only works for very nominal actions and data. Boundaries and off-nominal conditions are ignored or not allowed regardless of how needed they are by the end user.
Iterative methodologies encourage this since any additional functionality that is needed can be completed in a later iteration.
Interdependencies mean that what appears to be an off-nominal case in one functional area may be very main-stream in another area. It may be possible to develop a core subset of the system functionality but this must be coordinated across the program elements.
Attempts to test quality into the code
Additional testing staff and the expenditure of lots of CPU cycles in an attempt to find all of the bugs. End-item testing is not effective in finding requirements and design level bugs which remain undetected.
Low functional complexity means that superficial testing of visible functionality is all that is required.
Functional and data dependencies mean that only well designed tests based on a functional analysis of the required system have any hope of validating the system's functionality. The quantity of the testing is relatively unimportant while the quality of the testing is paramount.
Attempts to automate testing of functionality still being developed
Substitution of automated build tests for thorough, valid white box tests by developers.
Minimal saving of developer time although tests that can be brought forward with each iteration are useful.
See long-term impact for previous item.
Proliferation of patch releases
Development resources needed for the "next release" are diverted to fixing problems in the last release. Customers must deal with a steady stream of new releases that may include functional changes.
Patches have a localized impact and generally fix truly broken functionality. Customers would have liked to have it work correctly to begin with but will probably put up with iterative fixes that converge to a stable, working system.
Patches to fix design level flaws will rarely work since a program redesign is what is really required. Interdependency of the program means that a fix to one function will frequently result in breakage or changes elsewhere.
This work is copyrighted by David G. Miller, and is licensed under the Creative Commons Attribution-NonCommercial-NoDerivs License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.