Arguments related to ObjectOrientedProgrammings affect on the impact of changes on code and code maintenance effort.
Moved from BenefitsOfOo
Regarding claim: "[OOP] Reduces the impact of requirement changes on code."
I'd like to see an example. The examples in the popular OO books are not very realistic in my opinion. They assume change-pattern probabilities that differ from my experience. And the authors seem afraid of RDBMS. They seem to assume that change pattern X is more likely than change pattern Y, when in my many years of experience, they are either the same, or Y is more likely. Related: ChangePattern.
- [Well, for a claim like "reduces impact of requirements change on code", your first question should have been "relative to what?" The answer to that question is 'procedural' programming in identical circumstances. In any case, RDBMS can help OO programming as much as it can help procedural (so long as the OO programmers don't stab themselves by creating DomainObjects and BusinessSimulator?s - a tendency that runs them into OO-relational mapping problems). If RDBMS allows users to subscribe to queries with DeltaIsolation (rather than just select the query repeatedly via polling loop), RDBMS would be suitable even in most SystemsProgramming? domains (where OO techniques are currently strongest).]
- OOP and RDBMS tend to fight over territory unless you "cripple" your OOP down to almost being procedural anyhow. Non-orthogonal tools are a breeding ground for confusion and inconsistency. (I'd like to hear more about your "subscription" suggestion, but perhaps in another topic.) --top
- [I strongly disagree that OOP is "crippled" by reducing the degree to which it contains domain data. But it might be better to think of it this way: OOP is best used as a tool to dynamically construct a program - sometimes a procedural program, sometimes an event-driven display, sometimes a command processor, etc. Nearly all DesignPatterns are examples of this, as are FunctorObjects and such. Procedural programming allows you to write a program that takes inputs and produces outputs. OO allows you (and other developers) to conveniently write 'parts' of a program then later combine/coordinate them into a larger whole-program that takes inputs and produces outputs. (I agree that subscription of queries is better discussed in another topic.)]
The reason an example rarely appears is because it's so obvious. The central point of OO (from a changing requirements POV) is the interface, which is a formal specification of how program A talks to program B. Provided the interface remains consistent, changes to A will not necessarily affect B. Interface inheritance makes conformance to an interface substantially easier. Implementation inheritance makes it easier still, provided you have an expressive enough type system (e.g., one which supports mix-ins).
- [Picking a few nits: unless you are using a language that can develop, create, and coordinate FirstClass processes (as in ErlangLanguage), the 'program' scale ("program A talks to program B") is too coarse. You might as well claim that every language with 'modules' may advertise itself as ObjectOriented - a position to which many would object.]
- Your nits are not. A program is a list of instructions for a computer to execute. A thread is what actually effects the execution of the program. A process is the environment in which the aforementioned thread executes (it may, or may not, be backed by an MMU-enforced address space; this is an implementation detail). Therefore, any single statement in any programming language you care to think of is a valid program, as is something as large and sophisticated as the shuttle's control software. Therefore, it is entirely possible of two programs cooperating even inside the same piece of source code. Often, these are called functions. But they needn't be: in Smalltalk, they're called methods, and are referenced using selectors and run-time dispatching called message sending (they're called this because Smalltalk was originally supposed to implement objects as processes!). In C++, they're implemented as functions with an implicit "this" parameter, and dispatched via a jump table. Etc. Yes, this means even SQL queries are, themselves, programs.
- [If you believe that the benefits of OO come from formal interfaces to 'programs' by which you mean any executable statement or list thereof, then I submit that procedural and functional programming, also, have 'formal specifications of how programs communicate' and thus should, by the reasoning above, be 'obviously' better for requirements change on code.]
- [Anyhow, Top's argument regarding ChangePatterns is not an unreasonable one. You assume that changes typically allow for "the interface remains consistent". Sometimes that assumption holds, but it is rather hit-or-miss. Related: the ExpressionProblem.]
- ''Predicates, dynamic feature-set request lists, and domain noun categorization meta-tizing are powerful non-OOP techniques to keep interfaces flexible, I would note. OOP simply missed the predicate and SetTheory boat. It's that fricken simple.
- No, it's not that fricken simple. You can assign names to concepts -- that makes them classes. You CLEARLY demonstrate this in your rebuttal to the very concept you're arguing against!! In fact, OO was the very first application of SetTheory to typing in computer programs! How can you say it missed the boat, when it essentially is the boat itself? Inheritance is nothing more than the union of two sets!
- Please clarify. If its off-topic, lets find an appropriate topic first.
- You might have a valid claim with predicates, but without a specific example I cannot be sure. This needs more clarification. However, there are OO languages that implement PredicateClass?es.
- In other words, OO languages that are almost a relational query system? Interesting, but why compete but then stop at 70% of the way when 100% tools readily exist? Either go all the way or surrender to the better gunner.
- [I'll somewhat agree with Top on this one. MultiMethods, open functions, and predicate dispatch are not among what I understand to be OO techniques. They operate contrary to the concept of sending 'messages' to 'objects'. (However, Top, there are likely hundreds of reasons to stop at 70%. Keep in mind that constraints are what LanguageDesigners barter to buy features. See related discussion in GrandParadigmUnification.)]
- OO's perspective on "interface" is too static, repetitious, and old-fashioned. One should think about factoring interfaces, not just implementation (InterfaceFactoring). --top''
- OK ... you just said that one should think about proper OO design. Thanks for proving my entire point.
When it comes to relational databases, you don't have interfaces to hide behind. If a schema changes, you're right fscked -- the entire god-#### codebase needs to be retrofitted to account for the changes. I've been there, done that, got the t-shirt, sold it on e-bay, and bought myself a book on proper software engineering techniques with the proceeds.
Schemas can be considered an interface. And indirection can be provided with DB "views" if needed. One can also use application-level functions and DB-side stored procedures to "wrap" tables. However, this tends to limit one's ability to use relational algebra instead of iterative app code for many kinds of processes, perhaps flooding the network in the process. But, the devil's in the details. Please, a specific example/scenario.
Name Slice Scenario
I'll go ahead and introduce a scenario here. The database starts out with a schema as such:
table: contact // original version
----------
contact_id
name
street_address
city
etc...
After a year or two, it has been decided to change it to give allow better person name data.
table: contact // revised version
----------
contact_id
last_name
first_name
middle_name // or initial
street_address
city
etc...
Obviously this change would break all application queries that explicitly reference "name".
With an OOP language that hides the difference between methods and attributes, one can simply rebuild the "name" method to be "calculated"; that is, append the 3 sub-parts of the name to produce "name".
However, this trait is not universal among OOP languages. But one could probably rig up an initiator/constructor method to perform the conversion. However, there's still the problem of saving "name". This is not a simple fix. The user-interface (screens) for collecting name info needs changing because business requirements have changed to require detail that it didn't require before. No paradigm (except maybe AI) can get around this part of the problem. It is a change in business requirements.
- This would never, ever, ever, ever be an issue in properly formed OO software anyway, since that level of detail is fully abstracted behind the class. The question isn't, "How can I reconstruct the person's name?" it is rather, "What can I actually DO with the person's name?" This is what is meant by abstraction: the schema can change right out from underneath the application, and all the changes needed to work with the new changes are kept nice and local. But, the real benefit of OO, is that you can rely on (a) strong typing [if the language supports this] to ensure that program A properly interfaces to program B, and (b) you can re-use implementations of abstract types by means of implementation inheritance. On this premise, your example is completely non-sequitur.
- Even many OOP fans are fans of dynamic or "loose" typing. Typing is generally another topic. Plus, it may pit referential integrity against language typing.
- With OO, FormFollowsFunction. With relational, it's just the opposite: there is a very strong incentive to not change the form even if the functionality you need calls for it, precisely because of how expensive it is to change it.
- Without specifics, this is coming across to me as BrochureTalk.
If one is using basic procedural-style API's to fetch table rows from table "contact", then some change is needed for any procedure using "name". But it is also possible to make a change on the DB side to
avoid having to change every procedure. One approach is rename the table "contact" to something else, such as "contact_info", and then create a view called "contact" that looks just like the old table, using a calculated "name" column.
A better choice would be "view columns" such that one would not have to view-ify the entire table to hide the change, but merely one column. If view columns were implemented, then a "dummy" column called "name" would be added to the revised table that would calculate the name by appending the 3 new columns. (See [to be inserted].)
I agree that lack of view columns is an annoying flaw of existing RDBMS, but to be frank it would not used very often. Most of these kinds of schema changes tend to impact known sub-sections of applications such that one does not need to worry about the entire application, in my experience. This is why most vendors aren't in a hurry to add view-columns.
One could also use INSERT and UPDATE triggers to populate an actual "name" column based on the new name parts. But, this can be confusing.
- There is a hugely critical flaw with this approach: now you're burdening the DB server with a non-DB-related task. You are employing views as a patch to not have to go back and fix all the code which is known to be poorly abstracted. The burden on the DB server, for example, would positively destroy companies which rely on the greatest imaginable performance from their databases. For example, at Attributor (where I last worked), the use of views, as you describe further down, would lengthen all basic database operations by a factor significant enough to cause major customer complaints.
- You are exaggerating "hugely critical". And, I am only saying its an option, not necessarily the best. Writes to "contact" table may not be that frequent in companies that deal with larger contracts (lots of work per few contacts). Thus, "it depends".
- One of the reasons why modern microprocessors have 22 or more pipeline stages instead of just 4 is because of the higher clock rates -- a smaller number of pipeline stages requires a greater amount of random logic per stage, and therefore, greater latencies. We can apply the same methods of operation to software systems as well -- you get far, far greater performance on software whose processing is distributed amongst a larger number of stages than you do in a single stage. It makes far greater sense to retrofit production code in accordance to a production schema change.
- I don't know what the context of this is.
Another approach is to make procedural wrappers, such as "getContact" that returns a row as a map (associative array) or the like. It can calculate "name", similar to the OOP approaches mentioned above.
However, bunches of accessors tend to bulk up code in my experience. Further, one cannot use relational algebra on accessors. It limits one to record-at-a-time processing.
- In my experience, this is the norm, not the exception. And, no, this has nothing to do with the predominance of OO thinking in the workplace either. The only tools which regularly work in bulk quantities of data are either high-level administrative or reporting tools. In the last seven companies I worked at, ALL business logic operated record-at-a-time. This likely explains why OO has dominated in the business industry just as it has dominated in the UI design industry too.
- I deal a lot with management-level interactive reporting tools, and record-at-a-time would be too problematic. Plus, you have to reinvent a lot of CollectionOrientedProgramming manually using record-at-a-time. Whatever happened to "reuse"? And OO has *not* dominated the business industry. Even many OO proponents complain that most app programmers are simply putting procedures in OOP clothing and merely calling it "OOP". --top
Top's Specific Example of Change Impact Management using Relational Technology
For example, suppose we had a "suspicious contact tracker" sub-application that keeps a list of suspicious customers or suppliers for the Fraud Department to later investigate. We want to have a table called "suspicious_contacts" that keeps track of such. We don't need to move the actual name because we can use just a key to the "contacts" table because we can later JOIN to get it.
Here's a code example that implements QueryByExample-like techniques. I'll use ColdFusion code because it easily allows embedded conditionals in SQL . Comments start with "<!---".
<cfFunction name="log_suspicious_name">
<cfArgument name="firstNameMatch" default="">
<cfArgument name="lastNameMatch" default="">
<cfArgument name="p_reason" required="true">
<!--- Execute SQL query --->
<cfQuery datasource="std">
INSERT INTO suspicious_contacts
(contact_Ref, reason)
SELECT contact_ID, '#p_reason#'
FROM contacts
WHERE (1=1)
<cfIf Not isBlank(firstNameMatch)>
AND first_name LIKE '%#firstNameMatch#%'
</cfIf>
<cfIf Not isBlank(lastNameMatch)>
AND last_name LIKE '%#lastNameMatch#%'
</cfIf>
</cfQuery>
<!--- error checking not shown for brevity --->
</cfFunction>
The beauty of this is that
no data has to come back to the application. The query is telling the DB to take matching info from one table and put a reference to the matches in the other. The actual processing happens on the RDBMS server.
If we use method wrappers or app-side procedural wrappers, then we'd have to bring each and every row of the "contacts" table over to see if there is a match. Not only can this increase network traffic, but also is likely more coding. If we let the DB do what it does naturally as above, then we save code and network traffic. (The network is usually more of a resource bottleneck than CPU in biz apps.) It can add up with other operations.
- This is a net win if, and only if, you don't already have contact_ID in hand. Odds are likely that if you already know the name components for the suspicious contact, you already have his contact ID as well.
- Not necessarily. What if another company warns you, "A dude going by the alias "Sky Lukewalker" has been doing some nasty stuff. Keep an eye out." Further, perhaps a fancier QueryByExample interface would be used so that we could also filter by other address fields. But, the same issues still apply.
Relational languages are a good thing. (SQL has its flaws, but even with them is very useful.) Imperative wrappers are often a poor substitute.
--top
Samuel A. Falvo II's Specific Counter-Example of Change Impact Management via ObjectOrientedDesign
I'm going to offer a counter-challenge -- implementing device drivers for an operating system using purely relational techniques. This is the classic example of polymorphism and interface management, both of which are instrumental components of proper OO design. Consider Unix' philosophy of "Most things are files." That alone is an abstraction. It's an abstraction that you cannot express relationally.
Next, we need to figure out what we can do with those files. Well, you can read from them, and you can write to them, at the minimum:
class Readable is
read(data: Buffer, sizeOfData: INT): INT;
end;
class Writable is
write(data: Buffer, sizeOfData: INT);
end;
Sometimes, a device has the ability to relocate its read/write pointer too:
class Seekable is
seek(offset: INT);
tell: INT;
end;
So, for example, a simple RAM disk implementation might be defined as:
class RamDiskDevice? <: Readable, Writable, Seekable is
...
end;
However, a CD-ROM is not writable:
class CdRomDevice? <: Readable, Seekable is
...
end;
while a printer is neither readable nor seekable:
class PrinterDevice? <: Writable is
...
end;
But, what about things like
network connections? Those support reading and writing, but not seeking.
But, wait!! Network connections support "file-like behavior" only insofar as they're able to emulate a byte-pipe.
Networks are inherently message-oriented. Thus, really, only
reliable network connections may expose file-like interfaces with the anticipated semantics that doing so implies. Thus, we need a new set of interfaces which are similar to their byte-pipe bretheren:
class MsgReadable? is ... end;
class MsgWritable? is ... end;
So far so good: now, what kind of network connections are we talking about? Are these AX.25 connections? TCP/IP or UDP/IP? What about UDP/IP over AX.25? This kind of mixing and matching of software in ways not directly anticipated by any of their authors
requires interface consistency. Schemas be damned -- it's an implementation detail at best.
Let's look specifically at AX.25, since I'm most familiar with it. AX.25 supports datagram operation like Ethernet and IP does, so it makes sense to expose its message-oriented interfaces appropriately.
class AX25Connection <: MsgReadable?, MsgWritable? is ... end;
But, in OS version 2, customers are now demanding that we support AX.25's reliable mode as well (implementing, thus, a byte-pipe). We can implement this change fairly easily:
class AX25Connection <: MsgReadable?, Readable,
MsgWritable?, Writable
is ... end;
Things to note:
- The interior implementation of AX25Connection might very well change radically, since now it has to implement (directly or indirectly) timers to handle so-called ARQ negotiations, packet windows, negotiating MRUs with the connected peer, et. al. None of this alteration becomes visible to the application software, however.
- In fact, none of the changes needed to support Readable and Writable manifest in any other part of the OS either!
- Implementation inheritance (in the form of traits (TraitsPaper) or of MixIn classes) permit re-use of already existing code in the context of AX25Connection. That's an important qualification; procedural code can emulate this (indeed, the fact that OO code compiles to machine language is living proof of this), but can do so only at the expense of source code clarity. Remember, OO languages are notations optimized for expressing problems in terms of abstractions we call objects. Procedural languages are notations optimized for procedural denotation. The former necessarily supports proper abstraction while the latter supports, at best, abbreviation.
That means, in no uncertain terms, that if a bug were to appear in how AX25Connection handles its reliable-mode activities, fixing the class(es) behind this implementation will also very likely fix a heretofore undetected bug in, e.g., AtmConnection
? or TcpIpConnection
?. Procedural languages offer this capability too,
but only for procedures, never for state. A procedure is
worthless without some kind of state to operate on, and if fixing a procedure means I have to update a structure definition that requires the complete recompilation of both the OS
and every application for that OS, then its role as a
useful notation is compromised.
And that's where we get into OO versus relational. Relational has a lot of good ideas. But OO has even more good ideas, perhaps at the expense of some of those you find so near and dear to your heart. OK, so some OO folks re-invent the relational wheel now and again. But who cares, when it provides a net savings in development effort, code comprehension, and software reliability? SQL provides NONE of these features. Some of the SQL black magic you like to promote not only consumes VERY valuable time resources on the DB servers, but it also results in queries that are positively unreadable to mere mortals! Finally, reliability is compromised because, in my experience at least, sophisticated SQL has a nasty habit of being HIGHLY dependent on a specific SQL provider. Wanna move from MySQL to Oracle? You're SOL unless you want to retrofit all your queries to match. Sometimes, even specific versions of the same database from the same vendor cause issues. My website's blog broke utterly because Serendipity used a non-trivial SQL query which depended on the database's automatic type promotion behavior. When that behavior no longer existed, I had to hand-patch my blog's PHP files. Every. Single. One. Of. Them.
- Capabilities offered by different RDBMS vendors are different enough that one-for-one translation is often not possible. Thus, pop-and-plug swappability is mostly a pipe-dream. If you really know you'll need it up front, then its better to use a generic SQL style. However, it will limit your options. There are already plenty of existing topics on RDBMS vendor swapping. We don't need to reinvent those arguments here. I'll insert links when found. Further, function wrappers can pretty much do the same thing as OO one's can if we want to clog our app with tons of indirection.
- As far as queries being "unreadable", I've seen plenty of OOP that is unreadable. Bertrand Meyer's own jet screen example in his "OO is God" book is unreadable in my opinion. Perhaps they were bad query writers involved in your case. Comparing bad procedural/relational code to good OO code tells us nothing universal. I cannot tell without seeing actual code. Anecdotes are not worth very much around here. And no paradigm/tool prevents all difficult changes. They each shine and ache in different ways at different times.
- They seem worth their weight in gold when you use them.
- Who weighed them? Where is the certificate? Anyhow, we have things like PayrollExample so that we don't have to rely on anecdotes. There you can see my runnable example kicking OO-Bob's ass. --top
THAT is why OO handles change impacts a whole lot better than relational or procedural.
THAT is, therefore, why the industry uses OO over these technologies. And it is, in fact,
THAT very reason why functional languages, despite how I love them so, have not conquered the world. In fact, it wasn't until OCaml that people
really started looking seriously at
FunctionalLanguages in commercial industry,
precisely because of its OO capabilities.
Regarding above example, I concede device drivers to OOP. See OverUsedOopExamples. I don't build device drivers for a living. If I did, they OOP may be the cat's meow. I generally don't challenge OOP for building device drivers. OOP may be better for *some* domains. I don't question that. A similar example can be found in TopsQueryResultSet, by the way. --top
First of all, you admit that you don't code anything except management-type applications.
Second of all, you then use your myopic world view to impose your pro-relational-everything dogma on, well, everything. You belittle OOP at every chance you get because it doesn't conform to your world view.
You then make grossly inaccurate claims about, in this case, change impact management based on your self-admitted myopic world view of coding.
What really pisses me off, though, is that when I respond with a VALID COUNTER-EXAMPLE to your claims about OopAndChangeImpact, you belittle ME (you must be near(sic); spelling corrected above) for doing so, despite YOU posting the most over-used RDBMS example in the known universe, while attempting to slough everything under the carpet, pretending to be cordial about it. "It's useful for *some* domains."
Look, for the last time, NOT EVERYTHING IN THIS WORLD REVOLVES AROUND YOU OR YOUR PATHETIC LITTLE MANAGEMENT APPLICATIONS. There is a HHHUUUGGGEEE world of software applications out there, and your little niche doesn't dominate. I'm sorry you feel overly challenged by this, but that's the truth.
Until you actually get hired in a capacity other than management applications, it is not I, but YOU, who is new here. Don't forget that. I've been a coder for some 30 years -- I've seen the fads come and go, and I've seen what worked and what didn't. So if you want to get personal about this, let's. I'd rather not.
I suggest this be moved to DomainPissingMatch. Very few OOP books put domain disclaimers in their writing. They strongly imply that OO is good for all software all the time. And I never said that I *only* do management reporting apps. And please STOP DELETING MY REPLIES! --top
You're lucky I don't delete this entire page. Be happy with what you have.
- Thank you for your benevolence, God.
- Grow up, or get out.
- Excuse me, but you are the one acting immature. Threatening mass deletion is evidence of this, along with statements such as "pathetic little management applications". If that is maturity, then give me a Ford Pinto. Perhaps you don't realize how arrogant that all sounds. Well, it does.
- Welcome to the club. Now you know how I feel when you make claims like, "You must be new here." Or, "Device drivers? Oh God." You really don't like it when you look in a mirror, do you?
- I meant new to C2's wiki. The second was more of "oh no, not this again". It was not meant to imply that you were stupid or defective as a human being. --top
- I will agree those two statements were "unfriendly" on my part. However, they are not the first unfriendly remarks. I see a gradual tit-for-tat proliferation. But how about we both start fresh and try to be more diplomatic. Deal? (I removed those two remarks, BTW.) --top
This is not a domain pissing match, either. You are not going to play favorites as long as I'm here -- you blame OOP for not putting stipulations in their materials on domain applicability. I blame you for the same. Fair is fair. Oh, and I already acknowledged that relational algebra has its uses and applications, but, I don't see that anywhere on this page anymore. So, uhh, PLEASE STOP DELETING
MY REPLIES!
I have not intentionally deleted ANYBODY's replies. --top
CategoryObjectOrientation, CategoryExample, CategoryChange