Buzzwords - Objects v. Tables

Buzz Words - OOP vs. TOP


OOP pushers love to shove their buzzwords into your face if you point out problems with OOP. They say things like, "sure data sharing is a little harder in OOP, but OOP has iheritance." Thus, real problems don't matter if the abstract theory is taken care of.

Well, us TOP (Table-Oriented Programming) people can play the same game. In addition to describing the faults in the OOP buzzwords, we have come up with TOP buzzwords of our own. You want a buzzword fight? Ready, Aim, F....

Re-use

Re-use is the alleged holy grail of Object Oriented Programming. The problem is that OOP seems to sacrifice everything else to make this goal more possible. Re-use is the attempt to reduce programming effort by re-using existing code. However, OOP has not lived up to expectations.

First, there is no evidence that OOP drastically increases re-use. Second, what use is re-using half of the code when it takes twice as long to make the code in the first place! It would be like building an engine that is twice as feul-efficient, but finding out that the engine makes the vehicle weigh twice as much! (net gain <= 0) Note that not all OOP languages and techniques produce bloat, but that is the general trend.

Third, there is a faulty assumption that only OOP can provide components. Components are pre-built "plug-ins" or "add-ons" that can be added to software. I have used charting and DB components successfully with FORTRAN (a non-OOP language) before OOP was the rage. It worked very similarly to OOP objects. You had things like "handles" instead of an object (instance) that allowed the equivalent of multiple instances. For example, if the OOP approach looks something like this:

  include thing_class
  objectthing yaddle1 = new objectthing()
  objectthing yaddle2 = new objectthing()
  yaddle1.someaction(8)
then the non-OOP approach would look something like this:
  include thing_package
  integer yaddle1, yaddle2   // declare handles
  yaddle1 = newthing()
  yaddle2 = newthing()
  someaction(yaddle1, 8)
Not really that much different. What's the big deal? (Plus, there are other possible approaches.) I think OOP pushers need history lessons before promoting OOP's brand of component re-use. (It is true that the OOP way may catch more problems at compile time, but this is true of any concept that is embedded into a language. You can't realistically embed every possible concept into one language, as described under the OOP Q&A section.)

More on components, and more on Internal Vs. External Reuse, and a WikiWiki reuse discussion.

Encapsulation

Although the definitions of encapsulation tend to vary, it is usually worded as a combination of protection (compiler-enforced rules or associations) and proximity (grouping together of related operations). It is also often associated with the melding of data and behavior. For these reasons, "encapsulation" is not basal and/or consistent enough to be a useful term in my opinion.

One goal of encapsulation is to prevent programmers from using the wrong procedure or data type on the wrong item. It also has the goal of creating "black boxes" where one does not have to know how it works, only how to use it. There are various ways to do this. OOP mentality often takes this concept to an extreme.

Although it is a worthy goal, it often ends up bloating the syntax beyond reasonable use. It also makes persistence storage more difficult and more program-dependant because OOP tends to want to meld data and procedures tightly together.

Some OO fans claim that encapsulation can prevent unauthorized updates to data. However, this assumes that it is possible to limit data access of a given data item to one class. For example, having class A prevent all other classes from issuing a change-request (such as an UPDATE query) to a given data item. I see nothing inherent in the OO paradigm which guarantees this. It would have to be system- or language-specific. (See Data Protection).

ITOP provides a form of encapsulation by using Control Tables to keep related logic in one easy-to-manage place. Read also about Aspects, Interface Change Management, Abstraction, and Interface Bloat for more perspectives on encapsulation.

Polymorphism

Polymorphism is the ability to perform the same or similar function on different "types" of things. The two most common ways to do this are through varying parameter types, and having the same-named function (method) for different, but related things.

Varying parameter passing was already discussed under the Wrong Credit section. An example of same-named functions would be having a "Draw" function for different shape types. ITOP can satisfy this using column functions on Control Tables. Our shape example shows how regular relational tables can assist in polymorphism.

See also The 3 Polymorphism Killers.

Inheritance (and it's problems)

This relates to the ability to "inherit" properties and operations from another item. For example, a base class might be "Mammals". All mammals have features in common, such as having newborns that drink mother's milk. A new class called "Canines" can inherit all the features of the Mammals class, such as milk production. In theory this reduces duplication by having common features automatically inherited.

However, reality is not allways so simple. I have heard OOP programmers complain about finding OOP code from other programmers where inheritance was overused, creating "spaghetti" inheritance. This is the 90's version of "goto" spaghetti coding of the 60's.

In fact, the most popular selling OOP development environment, Visual Basic, has chose not to support direct inheritance. Dan Rahmel, et al., has this to say about inheritance:

Inheritance is a hotly contested topic in the object community because...a single change to a class can have far-reaching effects on all the children defined from that class. (From 'Client/Server Applications with Visual Basic 4, Sams Publishing, 1996.)

Inheritance is often sold as some magic scheme that can propagate "friendly" changes to all related parts of an OOP software system. Even carefully designed software is very fragile. If a human can't fully predict ALL the side-effects of changes, how is OOP software going to automatically predict and prevent unforeseen side-effects? (A human can perhaps predict 99% of the side-effects, but that missed 1% can crash the entire application.)

For example, suppose a generic OOP class for addresses was built. Whenever an address is needed for another class, the address class can simply be inherited or instantiated instead of recreating definitions over and over again for name, address, state, zip-code, etc.

So after years of inheriting or instantiating this address class to many applications and data sets, it is decided to add an "e-mail" field (property) to base address class. The developer is thinking, "WOW!, a change to one class propagates among several modules. What a time saver!"

However, some modules export the address to a customer's system. That system sees the new e-mail field and crashes because it was not designed to handle an e-mail field in it's addresses. (They are not using the same language and/or object definitions.)

Second, a report that prints out sales contact addresses for the CEO now looks funny because the new e-mail field bumped other fields off of an already crowded report line. (Managers like to cram as much into a report as possible to avoid flipping pages. They don't want to hear excuses about "protecting future field growth".)

Thus, a simple change broke at least two applications. With OOP you can inherit power, but you can also inherit unforeseen side-effects. Inheritance can create a house of cards. If you accidentally bump a root card in the wrong way, KABOOM!

People in Usenet who saw this example said things like "Oh, if you only did this and that to each instance or reference you would have avoided these problems." Even if one did not have the gift of hind-sight (which we as readers do), special variations for each usage is probably more work than simply copying address field formats to each new table/class.

Auto-propagation of changes can cause just as many problems as the lack of change propagation, if not more. Sometimes duplication of slightly varied constructs is better than a referenced central construct with a bunch of exceptions engineered into it.

(Note that a nifty compromise may be to automatically track address definition instances, but not automatically change them. This way when there is a change, address definitions can more easily be hunted down to see if they need manual tweaking. This avoids automatic changes, but does allow better management of changes. It also allows step-by-step changes instead of the sudden OOP propagation.)

Much of the famous, but annoying problems of Windows DLL conflicts result from a form of inheritance. A vendor, or even Microsoft will sometimes automatically install a newer version of a DLL. The newer version may have fixed some known bugs and added some new features or speed.

However, an existing application that uses the DLL that was just updated may not "like" the changes, and go KABOOM! When experts track down the "conflict" problems, they are often found to be the result of very subtle differences between the DLL versions. (It is impossible for a DLL builder to test a new DLL with all versions of all applications that used the older one.) Any programmer knows that subtle differences can be enough to crash programs.

If each application could only use the DLL's that it was shipped with (and was tested with), then other applications are less likely to interfere with it. "Good fences make for good neighbors." Although you cannot inherit improvements, you are also less likely to inherit unforeseen side-effects.

Further, the base classes can become obsolete as technology and/or your business changes or grows, but nobody will risk overhauling these base classes because so many other parts of the system rely on them. This is a common problem in the IT industry, even without (before) OOP. Inheritance will only magnify the problem.

Note that experience tells us that it is best to subclass only operations or objects that have simple, unambiguous, and non-changing interfaces (even if the implimentation is complex). For example, sorting is fairly easy to describe and define, and there is little risk in repairing or replacing a sorting class (assuming that things like collating sequences have been settled on.)

See Also:
Control Tables and Inheritance
A Banking Example
Hierarchies
Subtype Proliferation Myth

Inheritance Buildup

This is a problem where subclassing produces larger and larger inheritance trees over time. Because it is expensive and risky to tinker with existing classes that have mounds of code that depends on them, the temptation is to subclass an existing class rather then "clean up" or simplify existing classes when changes over time make some of the higher or middle-level structures obsolete.

A variation of this concept is called the "fragile parent problem". See the above discussion on "Inheritance".

Note that some OO fans suggest performing periodic code reorganizations known as "refactoring". Please see the Boundaries write-up for more on refactoring (not to be confused with regular "factoring".)

Fragile Parent Problem

See "Inheritance Buildup" and "Inheritance" above.

Full Spectrum Table Coverage (FSTC)

This is an ITOP term for a system or tool that can easily implement or use all three major types of tables. Many programming languages either support only type 3 (DBMS), and/or type 1 (RAM-only structures).

AutoPersistence

This is a feature of data structures in which they are automatically buffered and (optionally) saved to hard-drive (file) storage. This eliminates an undesirable tie to RAM (memory) limits, and eliminates the labor intensive step of developing storage algorithms for data structures.

It baffles us why autoperistence is marginalized by the industry.

Some have claimed that autopersistence slows down access to data items (objects). However, disk caching technology actually puts some or all of the structure into memory during processing, thus making its performance almost identical to memory-only structures. (Plus, it gives added "virtual addressing space" if RAM runs out.)

Internal-Aware Expression Evaluation

This is the ability to evaluate functions and expressions that access internal functions and variables. It is roughly analogous to Perl's eval() function (but not unique to Perl). It is generally the ability to evaluate (execute) an expression on-the-fly (run time). Although the expression is usually a string value, it may come in other forms.

SQL generally lacks this feature because one cannot say something like:

  select * from tablex where myfunc(location) > 7
In this example, the SQL evaluator usually has no idea what "myfunc" is unless it was specified to the database server in some way. Contrast this to similar, yet working, XBase statements:
  Use tablex
  List all for myfunc(location) > 7
Although stored procedures and other tricks can sometimes work around this "blindness" limitation, it is still an important barrier to concepts like Control Tables and other powerful programming constructs. After all, its the programming languages' job to be the programming language, not the database server's job (unless a way can be found to cleanly interchange them.) Also DBMS scripting languages are rarely a treat even if you ignore the isolation factor.

Persistence-Poor

"Persistence-poor" describes a tool or language that makes it tough to store data, control logic, and other information to hard-disk. This includes the ability to search and index stuff from storage. We claim that persistence is one of the biggest weaknesses of OOP as found in the common OOP languages. Sure, they provide ways to do persistence, but their approach is generally a bulky, clumsy afterthought.

Auto-Inherited Collection Manipulation (AICM)

This refers to the ability to easily sort, search, summarize, view-filter, share, cross-reference, etc. data collections (and perhaps control logic collections) without having to move the objects or items to another collection container. With ITOP almost every repeating structure (collection) can easily be kept in tables, which can and should come with a standard set of collection manipulation operations.

Most OOP approaches require one to move a collection to a special container class that has one or more of the sort, search, summarize, view-filter, and related operations. By automatically inheriting these features, ITOP can be a great time (and code) saver.

View-filtering refers to the ability to narrow one's item view to a particular subset of the collection and/or collection properties. Filtering does not change the actual items, but items can be changed through a view. Aggregate item change operations are also a part of ITOP, similar to an SQL "Update" command, but with Internal-Aware Expressions. (See, we are already using our new vocabulary.)

See also the Collection Bill-of-Rights and the Graduation Problem below.

The Graduation Problem

This is when some wimpy collection container, such as arrays (including hash arrays), suddenly need more operations or power beyond their original container. Changes in such requirements are very common in the business world. The programmer then has to rewrite all the array manipulation operations to use a more powerful collection container, such as an SQL database engine. (Although SQL is a bit formal and clunky, it is unfortunately the most common way used for getting more collection power.)

There is no reason why a common collection interface (or better yet, built-in syntax) cannot be provided for nearly ALL collections. This would:

  1. Reduce the learning curve by having the same operations act the same way and be called the same way.

  2. Reduce the language complexity by not duplicating the same operations with different collection types.

  3. Eliminate the need to rewrite applications when the size or complexity or collections change.

If you agree that having different interfaces is silly, the next question is what the common interface should be. Answering that question is what ITOP is all about. (Criticisms of a common interface are addressed here.)

Comb Code

This is where the structure of program code repeats similar constructs over and over in a ladder-like organization. It's structure roughly follows this pattern:
    1
       A
       B
       C
    2
       A
       B
       C
    3
       A
       B
       C
    4
       etc...
Note that some number levels may have a few more or less letters than others. See Control Table Theory for more information about fixing comb code and recognizing variations of it.

SOMPI And SIMPO Grouping

SOMPI grouping stands for Subclass Outer, Methods/Properties Inner. SIMPO grouping stands for Subclass Inner, Methods/Properties Outer. See also Comb Code (above) and Control Table Theory.

Repetition Factoring

Repetition factoring is the concept of extracting out repetition of similar patterns and constructs and moving them to one place. Using a subroutine in place of repetitive code is one common form of repetition factoring. Factoring can often make maintenance of code easier and "safer" by giving you only one or fewer places to visit to make changes.

In mathematical terms, repetition factoring out would turn:

    xa + xb + xc + xd + xe + xf

Into:

    x(a + b + c + d + e + f)
See Goals & Metrics for repetition factoring situations.

Syntactical Relevance Ratio

This is a measurement of the relevant syntactical elements to non-relevant or repetitive elements. For example, SQL provides table aliases to simplify the visual complexity of an SQL statement by allowing shorter table names when listing many fields. We have presented other examples of syntax simplification.

API's and OOP method/properly syntax can get very combursome for some types of applications.


Communism
also looked good in theory, but its base assumptions
about human nature were flat wrong!


| OOP Drawbacks | Table Oriented Programming | Paradigms List | Goals & Metrics | Main |
Doc version: 3/9/2002