Saturday, May 14, 2011

On Framework Management

Most OSS communities that accept plugins from all contributors run into problems of conflict at some point or another. So many plugins, all doing the same thing...which one should I use? How much time do I as an end developer spend evaluating other people's plugins, before I find issues that cause me to write my own? Without community feedback and active management of the plugin repository, the situation gets quite complicated. Especially if the communities evolve quickly, and no structured review effort is in place to remove legacy artifacts.

I like to analogize the situation to a 12 year old boy who eats a lot everyday, from healthy salads to Twinkies and Mountain Dew, without exercising. He just knows that the food tastes good and he enjoys it. Then, before he knows it, he explodes to 300 lbs. Suddenly doctors are warning him about heart conditions. Worse, in a few years he'll want the attention of the most beautiful girl in school, but may very easily scare her away with his excessive breadth.The boy will be in trouble until he changes his habits and begins to exercise away the accumulation that can literally crush him under all its weight.



Similarly, even useful frameworks can traumatize developers with features that promise easy use but then fail to provide the tools necessary (documentation, functionality, intuitiveness) to make good on those promises. For example, take most CMS systems. They can sometimes give wonderful "quick-win" solutions for simple tasks, but often lose favor with developers who have to do anything "more complicated."



The problem is, most OSS communities are focused on pushing new features and functionality. They are also by nature inclusive. Normatively, one can argue that the end goal should not be to erect significant barriers to contribution, as that goes against the spirit of the movement.



At the same time, without active review of contributions and acceptance of good ones, rejection of bad ones, and elimination of old ones that are super-ceded by "new and improved" ones, "plug-ins" become very analogous to what happens in the human body when cancer afflicts. Cancer is, biologically, unrestrained growth. A condition that occurs when cells "refuse to die" as a result of mutation. Just as the system that is the human body is destroyed by this unchecked process (a "biological memory leak"), so too can such processes dramatically affect the usability and adoption of OSS systems.



The solutions in the biological case for the maladies of unchecked growth are: change in process, actions (fat kid= eat less, exercise more) and introduction of external agents to eliminate problematic elements (cancer patient= chemotherapy and radiation). One can make the case that many OSS projects are already or becoming "fat." While it's probably too early to say there are any "cancerous" OSS projects, the potentialities are already there.



The problem is fundamentally one of governance. Abstractly, one could consider it to be the same problem the United States Federal Government has today: unlimited feature creep leads to unchecked expansion of the system base. Soon the system becomes so hefty that velocity of new development slows down or each new feature has massive unintended consequences.



This is the same lesson Windows has been learning for years ("My system doesn't work! Windows sucks! [Actually, it's because I'm using software X with hardware Y in configuration Z and one of the drivers was incorrectly written.]" Now other systems are experiencing it as well, such as Drupal 7 which had dozens of modules that claimed they "would be ready on launch day" but were still in Alpha/Beta stages, and which regularly have bugs reported such as "Drupal 7 doesn't work because of ___ [broken module]"



Resolution is seemingly impossible for the problem of unlimited feature creep. It is, after all, human nature according to Economics (see: scarcity). Mitigation comes from recognizing the problem and creating an according infrastructure to address it. We already have an example for this, if we once again turn to the human body.



The human body does not only contain red blood cells, or have organs for the sole purpose of "achieving functionality" (arms, legs, etc). It also has white blood cells, and organs like the liver whose duties include the elimination of impurities. There is an immune system in place that actively handles maintaining the health of the body. Most software systems have no such components. We consider a system "healthy" if it doesn't have a lot of outstanding bugs and meets most user needs. But such a view can be analogized to surmising that because a person doesn't have a disease, they are healthy. Absence/minimization of maladies can be one useful indicator, but as many in the medical industry are coming to believe cannot by itself give the "whole medical picture."



In small software systems, as in children, an under-developed immune system results in manageable fragility. Healthy children have just enough immunity not to perish from pathogens and develop resistance with respect to time. It can be expected that small software systems can mimic that cycle of maturation through build->test->release->patch cycles. If the "immune system" of the software develops throughout its maturation cycle, i.e.) as new features are added, active refactoring eliminates waste and inefficiency, regression testing verifies system health, etc, the system can be said to be healthy and likely resilient to a variety of maladies.



Larger software systems face a different set of constraints. Frameworks for application development, for example, can be analogized to natural ecosystems. In natural ecosystems, there are predator/prey relationships. These relationships have to be in harmony in order for the system to remain sustainable. Software frameworks often advertise themselves as "ecosystems"...but what are the predator/prey relationships? Are other frameworks "predators?" Only in higher-order ecosystems.



Within a framework itself, the "modules" or "plugins" must comprise the "predators/prey." They compete for community mindshare and usage, as such they sometimes conflict and sometimes work complimentarily. In order for the overall system to remain healthy, sometimes new modules must eat old modules or old modules must consume specialized features offered by new modules. This consumption can only happen if modules are actively removed from the "availability pool." Instead, many OSS frameworks currently function like deserts with no vultures. The rotting remains of ancient and broken modules litter the landscape. Many times it is up to the module developers themselves to clean up their mess, which can be analogous to asking a corpse to dispose of itself.



Whereas nature has itself created a "governance" whereby specialized organisms exist to "remove the dross"/"recycle old elements", software frameworks have no natural mechanisms. We have to "write our own garbage collection." Consequently, it is of fundamental importance for framework architects to take such considerations into account throughout the evolution of a framework, lest the system becomes ultimately unstable and breaks down. If nothing else, policy must be established to determine how the ecosystem will be managed.



There are a variety of approaches available to handle stablization of the ecosystem. Some automated, some not. For example, we could imagine a tool that periodically purges old modules based on a variety of metrics. Perhaps if a module is released as a "beta" for more than 1 year and has no activity in the form of source control repository updates or releases, it should be removed. When other users "fork" or create sub modules that leverage that module, which themselves have activity, the old module's activity log can be updated (hence, the module is active, in that its lineage is). This requires the ability to store relationships between modules, which most centralized repository tools such as Apache Maven already have.



Once we have this ability, we can do more. We can tag "active" modules with functionality descriptors which allow us to scope out the "range" of the module. Simple tagging can allow us to create "feature matrices" whereby modules can be queried, such as "give me all the modules that can draw 3D bar charts using Javascript." Grouping modules of related functionality allows for comparison between the modules. For example, Grails has developed a very basic tagging and user rating system where the community can participate and rate modules. This works for a simple metric. It is of course fairly subjective, and gives no insight into the actual implementations of the modules, only the perceptions of their functionality from end users. A more advanced approach would use that rating as part of a metric that combines other factors. For example, basic test applications can be written as "benchmarks" for the modules. These benchmarks can be used to compare the functional efficiacy/equivalence of modules (e.g. For this given Animal domain object, generate a PDF. Assert that each module can do it, and that the output files all contain the animal's name) and their relative performance (in terms of processing time, memory allocation, and other low level metrics). These benchmarks can even integrate code analysis tools to evaluate the modules and produce comparisons on LoC, convention issues, potential bugs/memory leaks, etc.



Benchmark analyses, in conjunction with user ratings and other metrics, allow us to define a common "domain" for the grouping of modules. Combined with the range, we can develop axes for evaluation and comparison. Plotting these modules in the community derived "suitability space" would enable framework architects and module developers to graphically compare modules. This can lead to pooling of efforts, elimination of unfit modules, and even refactoring of large modules into sub-modules. It can also provide clues as to what "third party components" should be investigated for integration into the framework core. If certain modules are so widely adopted they're almost considered part of the core, they can be integrated. Alternately, the core can remain relatively stable and small but "flavors" of the framework with popular modules (that encapsulate a set of pre-defined common functionality requirements) can be developed.



Such use cases, automated pruning of dead modules and benchmarking of live ones, lend themselves to automation. Such automation, however, still requires manual involvement. The metrics for pruning must be established and benchmarks must be developed by the community. This leads to a greater point. It's not enough for framework architects to focus solely on extension of framework capabilities and elimination of bugs and issues within it. Just as software projects require testing, refactoring, and overall quality assurance staff, so do software frameworks. Perhaps on a larger scale. Most frameworks up to this point have taken the approach of a human body with all red blood cells, and no white blood cells. They grow in scope and size without dedicated teams that seek to contextualize that change and modify the architecture accordingly.



For example, ASP.NET was eager to add lambdas and LINQ in revision 4. Yet the ASP.NET web application component still offers almost a myriad of ways to get the base path of a server, with very little guidance as to the merits/demerits of each or even investigation as to whether some of those should be refactored/removed. This suggests that it has not been a priority within the framework architect's enclave to maintain architectural unity and strive for simplicity. These are fundamental requirements espoused by Brooks in The Mythical Man Month for successful software endeavors, and Booch has also explored the need to combat increasing entropy and inertia by concentrated effort in his series, On Architecture. .NET is certainly not the only example, of course. Some have taken to calling the Java Programming Language an "everything and the kitchen sink" endeavor, which some hypothesize delayed the release of Java 7.



It's not uncommon for any widely used and successful software framework to run into these types of issues. Successful mitigation strategies are rare. Mainly because scale was "unplanned for", and no established quality assurance planning was integrated in the beginning.



Successful framework architects must establish a "governing board" of minds who oversee and debate the evolution of the framework. Part of that board must include a review board that actively manages the framework ecosystem. In fact, one can argue that as a framework grows in user base and module scope, the amount of effort to maintain the ecosystem should begin to parallel and in some ways even outstrip the efforts to improve the core. This quality assurance involves more than just filing bug reports on core features and write test plans. Rather, these users should help develop the benchmarks, taxonomize the overall ecosystem, and provide guidance in the form of documentation and refactoring support to module developers within that ecosystem. For example, within the Java framework matrix multiplication is offered by many modules. A "mathematics functionality coordinator" could contact the lead developers of the different modules and work with them to establish benchmarks to compare them against, as well as common interfaces that would allow for intercommunication between that piece of functionality with others. Java has already done this successfully with the Java Persistence API.



Framework evolution, especially in the Agile age, is a chaotic and rapidly changing process. This is part of the joy of software. But that joy is not without its hidden pitfalls. The point is not to slow down the rate of progress to avoid those pitfalls, but rather understand that energy must be expended to overcome them through active management of ecosystem. Frameworks need to lose some fat too, lest they find their lunch eaten by smaller, quicker frameworks that gain more mindshare for their simplicity.

No comments:

Post a Comment