There have been a number of interesting debates going on lately in quite a number of different areas that all have a certain thread running through them. A recent article asked whether PHP has taught too many bad habits and that it is slowly dying in its nest of bad code (and worse coders). The Java side seems to be facing the same existential angst - whether Java is in fact becoming an obsolete language, especially on the Web - and the arguments even range in areas as diverse as Perl, C#, Ruby and C++.
Object oriented programming began as the logical extension to the datatype that was pervasive in the procedural age. A datatype is a compound structure of either other datatypes or primitive types, or both. In certain languages, such as C, there is a fairly strong degree of correspondance between the most basic datatypes (integers, floating point numbers, characters, and so forth) and physical memory structures — an integer, for instance, would consist of between two and four bytes, and would have one representation for positive numbers and another for negative numbers (typically two’s complement notation) … this would correspond directly with the way that a machine represented the integer internally. Thus, while simple type was an abstraction, it didn’t abstract very far.
However, most data structures require more than one atomic type. A coordinate needs two, or perhaps three such types, along with some means of defining units. A string is a sequence of characters maintained in a linked sequence. An address or something more complex could in fact be made up of multiple levels of such simple types, bound together in a certain manner.
Data structures are purely declarative entities. You are declaring their existence, rather than assigning them intent. In a procedural world intent in turn is provided via functions defined within local libraries that accepted these data structures as inbound arguments, and returned other data structures as outbound arguments. A data type is the first level abstraction of a data structure, listing the associated component types but not specifically allocating values to those types.
The problem with this mode of operation is one of scaling. A linked library is a collection of individual “intents”, functions, that are grouped together by an overarching commonality - a graphics library, a file I/O library, a stack library, and so forth. This works reasonably well in certain domains … indeed, its what made such things as Windows 3.0 a reality, as that code was still procedural rather than objective, but that was probably about the largest (arguably larger than the largest) space that you could in fact use pure procedural code. Code management simply becomes too complex otherwise, and the possibility that two data structures or functions from different libraries would have the same name and signature but different functionality grew considerably, resulting in hideously long names and extremely complex parameter signatures.
The other problem with this approach (and the one perceived to be the problem to solve) was the fact that you needed to maintain a very comprehensive scope for data structures that persisted beyond an immediate functional need, and the likelihood of collisions of those variables reached a near certainty, especially if you had more than one team working in the application at any given time. The heart of polymorphism comes not in the fact that you can have multiple classes perform a similar operation in a common manner - it is that you can have multiple classes utilize the same name for potentially similar but not necessarily identical operations. This is a subtle but very important distinction, one that holds just as true within the namespaces of XML as it does in C++. Polymorphism allows for the reuse of names without having to resort on arbitrary (and often byzantine) naming conventions.
The next characteristic to arrive in the notion of an “object-centric” rather than “procedure-centric” approach is the concept of encapsulation. An “object” is a combination of an internal data model that has scope only within the object declaration and a collection of functions (now called methods) and properties that provide a means for the reading and manipulation of that internal state. Of course, that internal state may also have a direct connection to objects that provide “side-effects” to this state change, but from a programmatic standpoint these side-effects are in fact irrelevant - this object definition is an abstraction that hides the complexity of directly manipulating these side-effects.
One rule that I’ve observed over the years is a simple one - “Abstractions require energy, whether in the form of increased processing power, more memory (including hard drive storage), greater bandwidth or faster hardware capabilities. An abstraction will not be adopted until a minimum energy threshold is reached, at which point it will seem to coallesce ‘overnight’.”
This concept is an important one, and serves to highlight why computer technology seems to have definite periods of intensive innovation and activity followed by periods where there seems to be very little such innovation. The periods of intense innovation (one of which I believe we are in now) are brought about because the threshold has been reached to allow for a higher level of abstraction.
Notice that I’ve talked about polymorphism and encapsulation as being key components, but the one I haven’t addressed yet, and the one I think is most controversial, is that of inheritance. For many, many programmers, object oriented programming (OOP) is mainly about the principle of inheritance. I want to state here something that will likely get the dander of many people up - I think that inheritance is in fact a red herring.
Inheritance is one of those concepts that looks eminently good on paper - think about the code reuse you can get by creating a class from another class, adding one or two properties to those defined in the parent class and redefining the definition of one or two other methods or properties. This has been a selling point for OOP adoption to literally a generation of software developers, and in a small enough domain, it would seem to be a pretty good idea.
However, one immediate effect of inheritance is that it means that even the most mundane change to a class must result in the creation of another class, and the changes introduced into a given class close to the top of the inheritance tree intorduces the possibilities of changes to all descendent classes as well, creating a strong incentive to not change such core classes. This means that you must guess very early on about the interfaces that a given class closer to the root class must have in order to work more effectively with descendent classes. This is an extraordinarily difficult task, made even harder because programmers are not generally trained (or given the time) to create the best code, but rather to create code in the quickest fashion possible.
Because of these factors, such code frameworks periodically have to be written from the ground up, usually at tremendous costs in migration, in getting the updated libraries disseminated, and in time spent regaining currency in an API that a programmer had already invested considerable time and energy in learning. Occasionally the cruft accumulates so badly that it forces software developers to go off and write a new language altogether, one that has a different syntax so that its not quite so difficult to work with, but that in turn very quickly succumbs to the same faults that the previous language had.
However, its worth realizing that most languages may very significantly syntactically yet otherwise be similar enough that they can be mapped cleanly one to the other. Indeed, this is one of the principle tenets of Microsoft’s CLR, that it recognizes this basic fact and consequently makes it possible to create sanitized versions of languages that share the underlying structure. Admittedly, there are any number of languages that do not in fact correspond even remotely to the CLR model, but as these don’t tend to be well known to most developers (Ocaml, anyone?) the premise of a common runtime is seductive. Ultimately, however, I think it still misses the point - it attempts to impose a pure OOP bias upon all languages, even those that are “object-like” but that generally tend to utilize weak typing and minimize the role of inheritance.
Curiously enough, object-like languages tend to encourage the development of components rather than classes. This might seem like a recipe for anarchy, but curiously enough, such component based architecture seems, in many, many cases, to work better than the “pure” approach of building complex frameworks. Why? I think a part of the reason stems from the fact that such components are designed not to be inherited from. If you can transport the component libraries to your users, you don’t need a complete runtime for the entire class framework infrastructure. It separates out the need to have highly skilled OOP developers at all levels of the application, instead breaking the problem domain into highly skilled component developers and much lower skilled component integrators, and, so long as some consistency is provided in establishing presentation standards such components can be both more robust in the face of incoming datatype formats and more “skinnable” at the integration level.
Indeed, looked at in this light, component-centric languages exist at a higher level of abstraction than OOP languages do. Far from being throwbacks to a pre-OOP time, such component languages could only exist once computers reached a sufficient level of complexity to enable the jump from OOP to component systems.
The next obvious jump in abstraction is in the establishment of XML and the document object model. Indeed, I’ve often thought for some time that the document object model has been misnamed - it is more properly a document component model. People do not inherit a <P> paragraph object generally. Rather, that paragraph contains other content - text nodes, bold or italic containers, spans, etc. Inheritance in fact plays very little role here, while encapsulation is implied in HTML (and SGML) but made explicit within XHTML. and XML.
The XML abstraction has, curiously, seemed to shed much of the baggage of intent in its way to becoming the dominant mechanism for expressing data structures. The DOM mechanisms generally do not provide any intent within their semantics - they exist only to provide means of navigating and extracting elements from the data structure itself, without any preconceived notion about the meaning of that data structure. Certainly, XML schemas do have implicit semantics, and hence there do exist sets of operations that can be performed upon them that are semantic specific, but unlike with object-oriented code, the intent as expressed by such methods can be thought of more as an overlay upon the XML that can be switched with other overlays when the need for switching the intent changes.
The same thing holds true for the notion of type, which, as previously mentioned, is itself is an abstraction. The XML structure does not, in fact, have any explicit requirements upon it to represent type-valid content; this is radically different from most traditional OOP models where type emerges as an artifact of the need to maintain some correspondance with the physical hardware limitations of a given computer. In C++, a string is a class that contains a pointer to multiple characters of a specific character width connected via a linked list structure. In XML, a string is simply a label that will let the XML parsing mechanism determine what the best representation of that data needs to be. What’s more, the constraints and containership that a schema implies can be expressed in many different ways, from DTDs and XSD to RNG and Schematron, each of which can specify very different constraints. Thus, as with intent, type itself is an abstraction over an XML document that is imposed from without.
While XML is certainly the most self-evident of these “post-OOP” languages, it isn’t the only one. Dynamically typed languages, where the type is resolved based upon context, illustrates again this notion of the fluidity of type. They depend upon OOP typed systems, just as OOP depends in great part upon the notion of procedural code, but they represent an abstraction layer above that provided by such languages as C++ and Java. This in turn tends to push these latter languages further down the stack, away from the building of “workaday” applications and into the realm of constructing the more abstract languages. The higher abstraction languages are also, of course, more inefficient - any abstraction layer will decrease the immediate efficiency of execution, though usually this is more than counteracted by the flexibility that the abstraction layer provides in creating simpler, more manageable code. This is another restatement of the energy principle espoused above, by the way.
Thus the rush to “objectify” languages such as Perl and PHP has caused problems, because a move too far into the realm of pure-inheritance based OOP opens up the door to the full foundation class conundrums that languages such as C++ and Java now face. This is not to say that organizing content using encapsulation and method intent is a bad idea - there are times where it is preferable to maintain the intent with the data. However, taking this to the next step of requiring extensive inheritance models, especially in settings (such as web processing) where the need to maintain elaborate class structures is considerably lessened, usually ends up only adding to the overhead of development and places requirements that developers be considerably more skilled in their programming ability.
On the flipside, the distributed processing models that we are now heading towards means that state needs to be more fully managed upon the client. Here, again, though, such state management works more effectively in a component based environment, which can be seen as being a considerably simpler domain (and which, in general, tend toward being model/view/controller oriented). At the moment, in the AJAX space there is a rush to put out libraries of “helper functions” to perform specialized tasks, replicating in the main the procedural efforts from before OOP became established. I suspect that AJAX will probably not fully “stabilize” until you see a compelling set of components in this space, though good components that work well across the range of browsers currently out there are hard to come by.
The final piece of the puzzle comes in finding the balance that works best for developers. A significant number of web developers on both the client and server side (the domain where scripting and similar high abstraction languages express themselves most clearly) are surprisingly maladept at dealing with object-oriented code … they still employ the crudest form of code reuse - cutting and pasting content - generally have only marginal ideas about the value of the OOP paradigm, and utilize tools that keep the percieved space of development in a procedural model, even if the tools themselves may encapsulate this within a rudimentary OOP framework.
Recently there have been discussions about the increasing role of architects in the web sphere, but the more I look at it the more I’m convinced that the reason for such architects is not the need to be able to handle the construction process directly but because such architects naturally tend to gravitate to the realm of higher level abstraction necessary for dealing with heterogeneous, asynchronous, XML-based distributed applications. This points to another corrollary - each level of abstraction encompasses an order of magnitude more physical systems than the one before it. AJAX systems make relatively little sense within a bounded, single computer system, but make perfect sense in distributed systems involving dozens to thousands of operating systems, servers, and related nodes. Of course, this implies that the next level of abstraction involves the manipulation of entire networks, and consequently the evolution of virtual networks that exist for temporary purposes and then disappear - what I see as the “real Web 3.0″. There are signs that such are forming at rudimentary levels now, but as with the emergence of Web 2.0 tech, it is likely that we’ll go through a rocky period (purely as a guess, in about eight years) before that next level emerges with the requisite technologies to support it.
I hadn’t planned on this post being quite so long, but it points to some thinking that I’ve been noodling over for a while. I think that most of the discussions about language fitness ultimately are reflections over this same awareness that we’ve entered into a new domain, a new level of abstraction (indeed, perhaps this is a good working definition for what a “paradigm”, one of the most overused terms of the dot-com era, really is). I’m curious as to your thoughts on this.
What do you think about OOP and its place in web and application development?