It's maintaining someone else's code.
Now I'm a mainframe guy who's outlived his profession. OOP was not an option, but we got very proficient at encapsulation and portability. But even in the best of times, we still still spent 70% of our time enhancing, modifying and maintaining existing code.
I can't imagine how OOP design facilitates maintenance. Given the need to alter class "xyz" that inherits from a parent and a grandparent with a number of interfaces, overloading and children that may entirely override my tested changes, I'm going to have to spend days tracking down and testing all the combinations and permutations -- even for the most trivial change -- am I not?
And that would be for maintaining code from someone who was an experienced oop and patterns master.
It would seem to me that whatever system cycles you save and how many programming hours you might save through re-use (assuming there is a common repository to know what reusable modules are out there) is wiped out by the first non-trivial change made to the system -- including recovering from the concomitant business snafu.
Again, I'm not trying to pick a fight or flame anyone.
It's just that all the kudos that I read for OOP (outside of encapsulation) seems focused on development, not maintenance.
What do you think?
Assuming the code you're maintaining uses good OO architecture, here are some ways it could aid maintenance.
If you have several related classes -- "abc", "efg", and "hij" -- that all need to be changed in the same way, and all inherit from parent "xyz", then you can make the change in just one place to affect them all at once. Though, as you noted, when there are layers of inheritance, you may need to trace the inheritance chain to find the appropriate place to make the change. And the functionality for any given class may feel less centralized; the functions aren't in just one place. And that's true. OOP encourages us to identify and separate code that is common to two or more data structures. So it's less centralized, sure, but also more flexible and eases reuse.
Interfaces especially, I believe, aid maintenance. Let's say, hypothetically, you have some classes that implement search indexing and lookup, but now you want to change to a different search library. Seems like that would be painful. Any place where you application uses search will need to be updated. But if both libraries implemented the same interface, then the rest of your application can continue using the same method calls it always has. Interfaces make it easier to seamlessly swap one component for another, because those components have agreed to a common API.
I find your arguments well considered and cogent. Let me play devil's avocate.
I'm not sure I can abide your assumption Jeff. In my mainframe world, programs required change for one of two reasons: (1)the business requirements changed and (2) the program(s) contained bugs and needed to be repaired. The latter was the more common.
If you have several related classes -- "abc", "efg", and "hij" -- that all need to be changed in the same way, and all inherit from parent "xyz", then you can make the change in just one place to affect them all at once.
Again, in my mainframe world, I was able to achieve such encapsulation through invoking "paragraphs" in my code. If I needed the same activity in another program, I could (and did) package that code into a "subroutine" to be "linked" at compile time -- which meant I only required the change in "just one place" (IF the program was written by a capable programmer).
Though, as you noted, when there are layers of inheritance, you may need to trace the inheritance chain to find the appropriate place to make the change.
But suppose that the appropriate place to make the change is in a higher inheritance change from where the "bug" manifests? Is it not likely that other modules inherit from this higher level (else why would it exist)? Finding the higher level is not too big a problem, but how do I assure that I've found all its children? If I miss one, I may correct a minor problem only to create a larger one in another part of the application?
And the functionality for any given class may feel less centralized; the functions aren't in just one place. And that's true. OOP encourages us to identify and separate code that is common to two or more data structures. So it's less centralized, sure, but also more flexible and eases reuse.
Is this not a development focus? For maintenance, I have to track down all the places it's reused before I make a change.
Interfaces especially, I believe, aid maintenance.... Let's say, hypothetically, you have some classes that implement search indexing and lookup, but now you want to change to a different search library. Seems like that would be painful. Any place where you application uses search will need to be updated. But if both libraries implemented the same interface, then the rest of your application can continue using the same method calls it always has. Interfaces make it easier to seamlessly swap one component for another, because those components have agreed to a common API.
I understand "both libraries implemented the same interface" is the idea to facilitate maintenance. Yet, how often is this likely to happen -- where the children operate precisely the same way when their parent changes?
Again, I'm playing devil's advocate from a straight maintenance viewpoint.
True enough. Bad programmers will write bad code. But the same is true of procedural style. In this regard, OO is neither the problem nor the solution.
Absolutely. But then I have to ask, if a criticism of OO is that you have to trace through classes, then why is it different than tracing through linked packages? And when you fix a bug in a class, you're worried about having to re-test any other class that depends on that now-modified class. But wouldn't the same problem arise in procedural style? If you make changes in a linked package, then you'd have to re-test any code that depends on that now-modified package.
I think that's a valid concern. But I also think procedural style doesn't alleviate that concern. If you need to fix a bug in a linked package, how do you find all the code that depends on that package? Wouldn't you still be worried that your bugfix might cause an unforeseen problem elsewhere in the application?
Though, in both cases -- OO and procedural -- hopefully no other piece of code is relying on that bug as if it were a feature.
I'm clearly getting repetitive, but isn't procedural the same way? Wouldn't you still have to track down all the places where code is reused before you make a change?
In good OO code, it happens more often than you might think. In fact, some design patterns, notably the strategy pattern, specifically call for a family of algorithms to implement the same interface so that they can be interchangeable.
If I were to be glib, I'd suggest that claiming that OOP is as good as procedural is not a resounding endorsement.
But that is not what I'm getting at. (pardon the grammar). I'm not as concerned about the star programmer or the bad one. I'm trying to get my arms around how maintainable the code a median-capable OOP programmer/architect produces.
To borrow an acronym from a related discipline, procedural programs (which most often are modular -- not straight-line) are WYSIWYG. OOP, not as much in my opinion.
In a procedural program, --
-- the first lines of code are typically the equivalent of a front-controller.
-- the modules the controller invokes typically are lexically together in the source document. The exceptions, of course, are linked subroutines, but they are fairly rare.
-- the invoked modules (performed paragraphs) typically cannot override a procedure or overload a method.
-- all variables are global (at least within the program) and typed.
-- all references to the variables and module/paragraph names are identified and can (and normally do) appear as part the compilation process.
These perhaps are not characteristics of a "good" (by most criteria) or bad application, but they are a boon to a maintainable one.
I haven't had the privilege of working in a shop that uses OOP-capable languages. So I don't know how well or poorly the median-capable programmer goes about designing and implementing his/her program product. In the mainframe world, there were many best practices that were NOT in the everyday repertoire of those writing programs. The logic being "Good Programmers write 'Structured' (or 'Modular' or 'Technique du Jour') programs. I'm a good programmer. Therefore I write Structured (or...) programs."
The only saving grace was that the code was all in one place, with cross references, when it was time to do the inevitable repairs.
I find your profound comments quite germane and thought provoking. Thanks for your responses.
I don't think such a broad statement carries the same meaning as what I had said. I was referring to a much more narrow and specific topic, that OOP won't miraculously turn bad programmers into good ones.
Nothing in this world short of a bullet to the head turns a bad programmer into a good one. The only thing that really lasts are things like SOLID principles which, while expressed in terms of OOP are a very good guide for writing well structured programs in any environment.
Oh-oh. I see that I offended. Not my intent. Please accept my sincerest apology.
Neither is it my interest to identify the best-of-the-programming breed nor the worst. I'm focused on the typical OOP programmer's impact on software maintenance. Or, more specifically, that part of maintenance that code-repair consumes. For example, the THROW command that may not be caught anywhere near where it was thrown -- and then with a generic message. Agreed that the "Good" OOPer would not do that, the "Bad" surely would. What about the typical?
Are these not a subset of the Gang of Four principles which serve as the basis for Patterns?
Though the GoF principles greatly simplify the building of solid OOP applications, it is precisely these principles that proscribe extensive inheritance and interfaces that got me to thinking about the impact on maintenance.
I see I'm taking the strong-anti OOP position here. That's not my intent. Maybe if I could secure a position in an OOP shop, I would find that my concerns are unfounded.
Maybe my question should be "What portion of the software budget does application repair and enhancement consume in an OOP shop?
Does SOLID as generally presented speak in terms of objects? Certainly. But I think you can apply SOLID to any sort of program:
Single responsibility principle: the notion that an object should have only a single responsibility.
Same could be said for a paragraph in your examples it seems. Never have heard of software parts called paragraphs by the by -- what language are we talking about here?
Open/closed principle: the notion that “software entities … should be open for extension, but closed for modification”.
Other modules should not be able to mess around with the internals of another module's implementation.
Liskov substitution principle: the notion that “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”. See also design by contract.
Or, things that implement a specification should be interchangeable parts.
Interface segregation principle: the notion that “many client specific interfaces are better than one general purpose interface.”
I like to call this "keep your program in layers" -- have a number of specific adapters that convert input into common formats rather than a single massive ball of wax.
Dependency inversion principle: the notion that one should “Depend upon Abstractions. Do not depend upon concretions.”
Build interfaces and code against interfaces. See Liskov.
The Gang of Four patterns are quite different -- they speak to a number of common implementation patterns across many types of problems. Many of the patterns apply just as much to procedural programming as OOP.
What portion of the software budget does application repair and enhancement consume in an OOP shop?
Interesting question. I'm really not sure you could measure it effectively without normalizing all factors -- we all inherit projects from bad developers. And what you define as maintenance code -- what if you practice continuous delivery where there is lots of side-by-side development time?
Personally I think getting stuck in a single pattern is a bad idea. No matter how much OOP you do sometime somewhere your app needs to process data. Even if you are writing pure procedural code you almost certainly need to get near OOP when you get near any sort of thick GUI client. Functional program is amazing in some places -- it is handy to be able to jump into that style no matter what you are writing.
The mainframe language COBOL -- stands for COmmon Business Oriented Language and was the primary language used in commercial organizations. "Paragraphs" were labels containing code to be "Performed" much as modules are called -- or targets of an unconditional branch or "GO TO". The language allowed, but did not enforce, concepts such as the do-while, and if-then-else. The case-structure "GO TO ... DEPENDING ON" ("switch" in PHP5, I forget the equivalent in Java) was seldom used.
In other news -- thanks for your response. I apparently did not read your SOLID URL reference carefully enough and got off track when
is the exact wording in my Patterns book.
So I'm still not clear on OOP's impact on software maintenance versus procedural in the real world.
Gotcha. Never did any COBOL myself, nor have come horribly close to it so I'm not familiar with the terminology. But from what it sounds like your paragraphs map to the same "chunk" of code a class might be.
They might well use the same terms -- I would never argue that the GoF did not have a massive effect on thought about software in recent history. But they speak to different problems.
I would generally argue that OOP vs Procedural vs [INSERT KEWL NEW PROGRAMMING TECHNIQUE] don't directly effect how much brownfield versus greenfield development one does. That is probably more a function of who is hiring your outfit and why. As for the pain factor of maintenence coding, I think that has alot to do with how you structured the development and if you've got good, automatable frictionless procedures and tools going rather than anything on the code techniques level. Note I'm in Zed Shaw's school of programming so I might qualify as the lunatic fringe.
This topic is now closed. New replies are no longer allowed.