Software maintenance

J

jacob navia

Recently in another thread, we discussed a bit about C vs C++
maintenance. As usual in those discussions, many of the arguments and
ideas behind the views expressed weren't even said, and even less dicussed.

I would like to make the motives behind my viewpoint explicit.

Object oriented programming (OO for short) took off in the middle of the
80s. It promised increased modularity through components and reduced
numebr of lines of code through reuse.

The reality is quite different however.

OO legacy code is very difficult to deal with due to acute lack of
modularity and the additional dependencies introduced by the frameworks
that OO code needs to get going. Since the frameworks lack in many cases
a formal API, the code using them becomes completely dependent of the
framework through inheritance...

This can be impossible to get rid of later, when either the framework is
abandoned or (the other extreme) it changes regularly each year and each
year you have to spend a lot of work just to keep up with it.

Another big problem is the lack of modularity of OO, because modularity
relies on explicit and well defined INTERFACES and APIs, that are seldom
used in OO programming. Everything is tied with everything else, and the
code becomes a horrible tangle of code, appropiately known as a "tar
ball" :)
`
C++ adds to the problems of code maintenance with its implicit
overloading of identifiers that makes impossible for the maintenance
programmer to figure out easily what function is being called when he
sees a function call or even a simple statement like "a+b". In most
cases the only sure way to know is to put a breakpoint and try to bring
the program to that point in the debugger. This doesn't always help
unless the debugger is available and the work to be done to bring the
program to that point is not beyond a certain threshold.

Add to this lethal mix the template problem. Templates give genericity
to C++ at an enormous cost in maintenance problems. In general, it is
impossible to debug problems in templates when they are extensively used
unless the one debugging the application is the original author of the
whole mess. Any modification even an apparently harmless one can produce
a cascade of completely incomprehensible error messages in a completely
unrelated part of the code. If many modifications were done before
recompiling the whole application this can be incredibly difficult to
solve. Templates introduce a new sub-language in C++ that is run during
compile time. It is completely impossible in a complex application to
test templates with all possible types that they could receive as input.
Concepts were designed to allow some specifications to be given to
templates in an explicit form, but they failed (more on this later). We
are stuck with no specifications for template parameters at all. Testing
is therefore much more difficult.

One of the most telling facts about the complexity of C++ was the
"Concepts" catastrophe. After years of effort, the creator of the
language realized that he was unable to introduce a new feature and the
feature was taken out of the new C++ standard to be published shortly.

This proves that C++ has grown so complex that there isn't any human
mind capable of understanding it, not even the mind that started the
language several years ago.

Now, since C++ is uncomprehgensible in its totality, C++ programmers
master *some* part of it well and use a subset of it that fits their
needs and the needs of the company. Problem is, obviously there isn't a
SINGLE subset that is approved by everyone :)

Each programmer will use then, the constructs he/she is used to. The
only looser is the maintenance programmer that needs to understand them ALL.

Much of my time is spent debugging a C++ mountain of code. Maybe that
is why my repugnance against C++ and complexity holes is so great, I am
surely biased. But people here can't deny that maintenance is a BIG part
of any software activity, and that languages are seldom designed from
that point of view.

Many speak about easy of use, meaning the easy to WRITE code. I prefer C
because easy of use means for me more the ability of READING and
UNDERSTANDING code in that language.

Obviously these are my views and engage only myself. I hope we can avoid
polemic in discussions here. This is not an "all out" attack on C++
either. It is just a dissenting view about it, and an explanation why I
prefer C.

jacob
 
M

Malcolm McLean

I stopped using C++ some years ago, for many of the same reasons.

However another reason is that object-oriented design is difficult to
do. Procedural decomposition is usually quite easy - identify the
central algorithm, and write it if possible in a general way,
implement and debug it. Then put the auxiliary functions around it to
format the input and display the results to the user. Designing a good
object hierarchy, by contrast, is hard.
 
I

Ian Collins

Recently in another thread, we discussed a bit about C vs C++
maintenance. As usual in those discussions, many of the arguments and
ideas behind the views expressed weren't even said, and even less dicussed.

I would like to make the motives behind my viewpoint explicit.

Object oriented programming (OO for short) took off in the middle of the
80s. It promised increased modularity through components and reduced
numebr of lines of code through reuse.

You, like so many other are starting form the erroneous assumption that
C++ is an OO language. It is not, it is a multi-paradigm language.
C++ adds to the problems of code maintenance with its implicit
overloading of identifiers that makes impossible for the maintenance
programmer to figure out easily what function is being called when he
sees a function call or even a simple statement like "a+b".

Its is pretty bloody obvious from the context what's being added!
Unless the code is written by a former C programmer who writes long
functions and declares everything at the top.

Most of the crappy code I've had to fix up (in both C and C++) was
written by newcomers bringing baggage from another language. It's worse
with C++ when they want too squeeze in as many new toys in as they can.
That's were solid mentoring can save the day.
Much of my time is spent debugging a C++ mountain of code. Maybe that is
why my repugnance against C++ and complexity holes is so great, I am
surely biased. But people here can't deny that maintenance is a BIG part
of any software activity, and that languages are seldom designed from
that point of view.

Have you ever heard of unit tests or test driven development? I only
spend a fraction of my time debugging (no matter which language I'm
using), if I mess something up, my tests fail and I revert the change
and do it correctly.
Many speak about easy of use, meaning the easy to WRITE code. I prefer C
because easy of use means for me more the ability of READING and
UNDERSTANDING code in that language.

Obviously these are my views and engage only myself. I hope we can avoid
polemic in discussions here. This is not an "all out" attack on C++
either. It is just a dissenting view about it, and an explanation why I
prefer C.

If C could do RAII, I'd probably use it more often than I do. That
feature alone makes code so much cleaner and less prone to bugs. So
much so I've worked with a team who only used the C subset of C++ plus
RAII and function overloading.

Any tool can be abused and any language can be used to write
incomprehensible code. It is the development process not the language
that determines the overall quality of the resting application.
 
A

August Karlstrom

Recently in another thread, we discussed a bit about C vs C++
maintenance. As usual in those discussions, many of the arguments and
ideas behind the views expressed weren't even said, and even less dicussed.
[...]

Good post!


/August
 
N

Nick Keighley

Recently in another thread, we discussed a bit about C vs C++
maintenance. As usual in those discussions, many of the arguments and
ideas behind the views expressed weren't even said, and even less dicussed.

I would like to make the motives behind my viewpoint explicit.

Object oriented programming (OO for short) took off in the middle of the
80s. It promised increased modularity through components and reduced
numebr of lines of code through reuse.

and at its best it can achieve the modularity. I'm less convinced by
the number of lines argument. Desparate attempts at implementation
reuse by inheritance can be truly horrid.

See LSP
The reality is quite different however.

as the old saw has it "you can write FORTRAN in any language". Well,
you can write crap in any language too.
OO legacy code is very difficult to deal with due to acute lack of
modularity

well written OO code is well modularised and has clean interfaces.
It's kind of what OO is all about.

and the additional dependencies introduced by the frameworks
that OO code needs to get going.

not in my experience. You don't have to have frameworks. If you're
going to do anything useful in C you're going to need libraries or
whatever.

One of the most telling facts about the complexity of C++ was the
"Concepts" catastrophe. After years of effort, the creator of the
language realized that he was unable to introduce a new feature and the
feature was taken out of the new C++ standard to be published shortly.

that was rather scarey

Each programmer will use then, the constructs he/she is used to. The
only looser is the maintenance programmer that needs to understand them ALL.

and for the record I have maintained a large C++ code base. And yes
parts of weren't excellent.
Much of my time is spent debugging a C++ mountain  of code. Maybe that
is why my repugnance against C++ and complexity holes is so great, I am
surely biased. But people here can't deny that maintenance is a BIG part
of any software activity, and that languages are seldom designed from
that point of view.

C++ was explicitly supposed to be suitable for large projects (we can
agree or not if it succeeded).

Have you read Lakos?

Have you heard of the Open-Closed Principle?

Many speak about easy of use, meaning the easy to WRITE code. I prefer C
because easy of use means for me more the ability of READING and
UNDERSTANDING code in that language.

I've seen some pretty poor C in my time. With badly designed
interfaces and leaky APIs (in the abstraction sense). Global data,
vast functions, huge "modules".
Obviously these are my views and engage only myself. I hope we can avoid
polemic in discussions here. This is not an "all out" attack on C++
either. It is just a dissenting view about it, and an explanation why I
prefer C.

I'm not sure you're criticising C++, but particular C++ programs.

Your points on the difficulties of templates strikes a resonance with
me. "oh dear they used a template here :-("
 
F

Felix Palmen

* Ian Collins said:
You, like so many other are starting form the erroneous assumption that
C++ is an OO language. It is not, it is a multi-paradigm language.

I think you're right here, only I'd explain it a little differently: C++
supports object orientation by providing appropriate language
constructs, but it does not force you to use it. For the term
"multi-paradigm", I'd expect some functional or declarative constructs
in a language nowadays.

On the other hand -- I really hate a lot of existing C++ code because it
uses this "C with objects" style. If I want to do that (I want it quite
often), I use C for it.
Its is pretty bloody obvious from the context what's being added!
Unless the code is written by a former C programmer who writes long
functions and declares everything at the top.

I still prefer having all declarations of local variable at the
beginning of the local scope. If this causes problems understanding the
context, it's a clear indicator that your function is to long.
Any tool can be abused and any language can be used to write
incomprehensible code. It is the development process not the language
that determines the overall quality of the resting application.

That's correct indeed, but I think the complexity of C++ really adds to
the risk of ending up with such code, especially when written by people
without years of C++ experience.

Regards,
Felix
 
B

BGB / cr88192

Malcolm McLean said:
I stopped using C++ some years ago, for many of the same reasons.

However another reason is that object-oriented design is difficult to
do. Procedural decomposition is usually quite easy - identify the
central algorithm, and write it if possible in a general way,
implement and debug it. Then put the auxiliary functions around it to
format the input and display the results to the user. Designing a good
object hierarchy, by contrast, is hard.

my reasons are mostly that it has a more complex syntax and semantics, and
mostly that most of what it offers is syntax sugar...

also, apart from the single-inheritence case, the object system gets messy
and confusing (I personally prefer the Java/C# style SI+interfaces design
instead, both for offering a cleaner design and semantics, and also for
being simpler WRT the implementation).

exceptions are nice, but are not standard in C, ...


as for style, I suspect I use a mix of procedural and OO style.
I may use OO-like strategies when appropriate, ...

usually I prefer to divide the larger app into "components", which focus on
problems at a much larger scale than typical OO objects (which are typically
very small-scale).


however, the "jerk off the class heirarchy" aspect of OO isn't something I
particularly relate to.
yet, many people think of OO, and think of it in terms of some big elaborate
system for jerking off the class heirarchy and throwing in as many fancy
sounding words as possible (and trying to mimic the "tree of life" diagrams
or similar...).


most of my designs have tended to be more "bottom rooted", with everything
common generally residing at the bottom of the tree (typically shallow), and
often with N/A operations being no-op and returning an error status or
similar.

often, I do similar in C, although I more typically use "daisy-chaining"
(where the "sub-classes" are "context dependent pointers") rather than
physical struct extension or nesting...

or such...
 
B

BartC

overloading of identifiers that makes impossible for the maintenance
programmer to figure out easily what function is being called when he
sees a function call or even a simple statement like "a+b".

Doesn't lcc-win32 (a C compiler) have the same problem?
 
I

Ian Collins

I stopped using C++ some years ago, for many of the same reasons.

However another reason is that object-oriented design is difficult to
do. Procedural decomposition is usually quite easy - identify the
central algorithm, and write it if possible in a general way,
implement and debug it. Then put the auxiliary functions around it to
format the input and display the results to the user. Designing a good
object hierarchy, by contrast, is hard.

Whether OO is difficult to do really depends on the individual. If you
tend problem solve by breaking the problem down into objects, it is
easy. if you don't, it's hard. I think in objects and I was writing OO
style C long before I discovered the infant C++.

A good object hierarchy, like a good functional decomposition will find
its self through continual refactoring.
 
M

Malcolm McLean

Whether OO is difficult to do really depends on the individual.  If you
tend problem solve by breaking the problem down into objects, it is
easy.  if you don't, it's hard.  I think in objects and I was writing OO
style C long before I discovered the infant C++.
A lot of people confuse "object orientation" with "enacapsualtion".

In C we can create objects like, say, a balanced binary tree. We can
have a function to intialise the tree (a constructor), a function to
add an element, a function to enumerate the elements, a fucntion to
delate an element, and a function to destroy the tree and free up all
the memory. In C++ you would naturally call this a "class".

However you haven't yet got an object-oriented design. Object-oriented
design comes when someone says "a hash table has many things in common
with a balanced binary tree, let's abstract out the common elements
(you can add items, delete items, look up items quickly with a key
etc), and provide them with a common interface. Then client code can
be passed either a hash table or a red black tree or an avl tree, it
won't know or care about the difference."
You can't do object-oriented design in C without murdering the
language with tables of function pointers and the like. You can do it
in C++, that's the main reason it was invented.

I'd disagree that the difficulty of object-oriented design depends on
the individual. Of course some people are better at it than others.
But it's inherently a difficult thing to build up a hierarchy of inter-
related objects that share common functionality. For instance hash
tables and red black trees are clearly "containers". So are arrays.
But is it really wise to make arrays "containers"? It's a very
difficult question. These issues are constantly thrown up in object-
oriented design. Once having made a decision, it's hard to go back. I
have seen good object-oriented designs, but only a few of them.
 
F

Felix Palmen

* Malcolm McLean said:
You can't do object-oriented design in C without murdering the
language with tables of function pointers and the like. You can do it
in C++, that's the main reason it was invented.

How is this "murdering the language"? C++ Compilers use a vtable, that's
not much of a difference (other than some optimizations like having the
vtable only once per class and leaving methods out that aren't
overloaded).

The syntax for calling the member functions [ foo->bar(...) ] also looks
quite similar.

Generally, a language providing structured data and procedural style
constructs is enough to do some OOP. Having function pointers/references
additionally enables the use of polymorphism.

Of course, C++ provides much more than these minimum requirements.

Regards,
Felix
 
M

Malcolm McLean

How is this "murdering the language"?
You need a vtable, then you need to pass about the objects as void *s,
then have some scheme for nesting the base class structure, and
resolving back to the base class vtable.

It can be done, but there's more to it than just declaring a few
function pointers.
 
F

Felix Palmen

* Malcolm McLean said:
You need a vtable, then you need to pass about the objects as void *s,
then have some scheme for nesting the base class structure, and
resolving back to the base class vtable.

It's not that hard to hide these things behind a few functions and
preprocessor macros -- BTDT.
It can be done, but there's more to it than just declaring a few
function pointers.

The effective scheme comes down to just that -- you call your member
functions through pointers in the struct. How is this "murdering the
language"?

Regards, Felix
 
M

Malcolm McLean

It's not that hard to hide these things behind a few functions and
preprocessor macros -- BTDT.


The effective scheme comes down to just that -- you call your member
functions through pointers in the struct. How is this "murdering the
language"?
A toy example will have just a few function pointers embedded in a
struct. You can achieve polymorhism of a sort.

When you move to a full-blown system, you realise that this simple
system doesn't provide you with the flexibility that you need. You
need to pass in a void pointer. You then need to query it for the
interface. You then need to get the function pointer from the
interface. You then need to adjust the pointer to the right base. Then
you call the function. It returns a void pointer which you have to
query for another interface ... The code doesn't look like C.
 
F

Felix Palmen

* Malcolm McLean said:
When you move to a full-blown system, you realise that this simple
system doesn't provide you with the flexibility that you need. You
need to pass in a void pointer. You then need to query it for the
interface. You then need to get the function pointer from the
interface. You then need to adjust the pointer to the right base. Then
you call the function. It returns a void pointer which you have to
query for another interface ... The code doesn't look like C.

As long as you can live with each object carrying its function pointers
for the member functions (which you can, as long as you don't
instantiate millions of objects), this is just not true. The only thing
you really HAVE to do is passing /in/ (not out) the "this" pointer as
(void *) and checking type compatibility. This can be perfectly hidden
behind some macros, the code of course "looks like C".

Regards, Felix
 
J

jacob navia

Le 13/09/10 10:59, BartC a écrit :
Doesn't lcc-win32 (a C compiler) have the same problem?

In principle yes, in practice no. Since there are no classes and no
hierarchy, overloading is easy to follow. In all documentation I
underscored that operator overloading works with types that should mimic
the typical types used with that operators. Arithmetic operators work
well with NUMBERS (of any kind) and should be AVOIDED with anything else
but NUMBERS. Generalized sequential container access with the square
brackets operator should be used for indexing ONLY.

Some exceptions are so standard that they can be used, for instance:

HashTable *h;
/* ... */
h["some key"] = foo;

In general however, operator overloading should NOT be overused. I
warned explicitely in my documentation against this.
 
D

David Resnick

Le 13/09/10 10:59, BartC a écrit :



In principle yes, in practice no.
...
In general however, operator overloading should NOT be overused. I
warned explicitely in my documentation against this.

I don't see how this is better than C++. Do you actually think most
people writing code read the compiler documentation much? Most books
on C++ I've read encourage very light usage of operator overloading
(other than operator= of course), except for arithmetic types. But
that doesn't stop newish developers from thinking that operator+ that
they learned in school is cool, and is intuitively obvious in this
particular case and means merge the sets or append the string or make
the hash bigger or add to the tarfile or ...

Above is not meant as a flame, just an observation. I agree with the
general thrust that C++ without careful choice of features to use is
harder to maintain than C. I don't like to have to work so hard to
figure out which code will be executed in complicated inheritence
schemes, for example. But I do like the standard library templates
containers (particularly now that hash_xxx is added), cast operators,
exceptions, simple classes (in particular for dtors/ctors), simple
inheritance, and a few other features. But am content to use the set
of C++ I want for those, as I don't see morphing C into C++ lite is
helpful. Obviously all IMHO.

-David
 
M

Malcolm McLean

* Malcolm McLean <[email protected]>:

As long as you can live with each object carrying its function pointers
for the member functions (which you can, as long as you don't
instantiate millions of objects), this is just not true. The only thing
you really HAVE to do is passing /in/ (not out) the "this" pointer as
(void *) and checking type compatibility. This can be perfectly hidden
behind some macros, the code of course "looks like C".
You need the base functions as well, quite often. So you'll find with
that scheme you're squirrelling away function pointers to base methods
within your derived classes. I'm not saying it can't be made to work,
particularly if you ban interfaces and multiple inheritance and just
have simple inheritance. But the approach tends not to scale.
 
F

Felix Palmen

* Malcolm McLean said:
You need the base functions as well, quite often.

| ((baseclass) this)->someMethod(...);
So you'll find with
that scheme you're squirrelling away function pointers to base methods
within your derived classes.

Only if you inherit and extend functionality, which is necessary
sometimes, but should not be common case in a good OO design. You don't
need to save these pointers in your derived class, a static pointer in
the implementation module of the class will do.
I'm not saying it can't be made to work,
particularly if you ban interfaces and multiple inheritance and just
have simple inheritance. But the approach tends not to scale.

Depends on what exactly you want to do with it. A lot of people consider
multiple inheritance bad design, too. I have seen good uses of it, but
if I want something like this, I'll probably go for C++. If I just want
a clean object model and don't need all the stuff like MI, RAII,
operator overloading etc, I prefer C :)

Regards,
Felix
 
B

BGB / cr88192

How is this "murdering the language"?

<--
You need a vtable, then you need to pass about the objects as void *s,
then have some scheme for nesting the base class structure, and
resolving back to the base class vtable.

It can be done, but there's more to it than just declaring a few
function pointers.
-->

years ago, I had an idea:
allocate all memory objects with a "typename".
this type is, in turn, linked to several different vtables (a vtable used
internally to the GC, a vtable supplied by the types library, ...).

then, one can do things like typecheck objects, use generic operations on
objects ('toString()', ...).
typically, the per-type vtables are managed by the code exporting the
interface in question, whereas code related to the specific type is usually
responsible for registering vtable members (or, in other cases, registering
the vtables).

note:
this is essentially backwards of how JVM and similar tend to work, where the
JVM manages each interface vtable with the class that implements it, whereas
in my system, typically the interface manages the vtable, which would be
looked up given the class.


however, this system does not itself support inheritence.
typically, inheritable objects are built on top.

however, thus far I have not found any "good" way to do C/I OO which doesn't
require a (typically awkward) API.

I created a C/I system before, much more recently than the above, which had
its design based mostly on the C/I system exposed by JNI. much like JNI, it
proved somewhat awkward to use.

some improvements could be made (mostly to allow a lighter-weight API), but
would require reorganizing its internals some, which would be a PITA given
how big and complicated the thing is, so yeah...


so, it mostly competes with (and generally loses to) my older systems
(systems based on the type-tag system, and typically supporting inheritence
via daisy-chaining structs and vtables), and also to code using Prototype-OO
(which, ironically, shares many of the same internal facilities as the C/I
system, as the C/I system was partly just built on top of a lot of the
substructure from the P-OO system...).


note:
daisy-chaining is the inverse of the typical C/I OO implementation (and also
my main C/I system), in that it starts with the base class, and each class
typically provides a "subclass" (or "userdata" or ...) pointer or similar,
and so to get to each subclass, one typically has to indirect through this
pointer.
(to get to the final derived class type, one would have to step along until
this pointer is NULL).

the result is that the natural place to put most common functionality is in
the base-class.

potentially, this system could be combined with a typical physical extension
C/I system (each subclass pointer effectively pointing just past its own
physical end). note that each subclass would only define fields and vtable
entries for its own level (rather than for all levels above it as well),
which would mean that vtables with no overrides could be used directly from
the parent class (no need to copy their contents into the new vtable).

struct ObjBase_s {
void *vtable; //both vtable and also class-info
void *subclass;
//fields...
};

it would also simplify mapping the C/I system to C, whereas mapping
physical-extension C/I systems to C often turns nasty (like, a big mess of
casting like in GObject, ...).

however, one could argue that it could use more memory per-instance (due to
the additional pointers), as well as possible access delays (since deep
inheritence could lead to a number of indirections to access a field).


however, the C++ ABI does not organize its classes this way, and I had used
the C++ ABI partly for determining how to lay out class structures in
memory, ...


or such...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top