Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I always see abstraction is a very useful feature the OO provides for managing the code-base. But how are large non-OO code bases are managed? Or do those just become a "Big Ball of Mud" eventually?

Update:
It seemed everyone is thinking 'abstraction' is just modularization or data-hiding. But IMHO, it also means the use of 'Abstract Classes' or 'Interfaces' which is a must for dependency-injection and thus testing. How non-OO code bases manage this? And also, other than abstraction, the encapsulation also helps a lot to manage large code bases as it define and restrict the relation between data and functions.

With C, it is very much possible to write pseudo-OO code. I don't know much about other non-OO languages. So, is it THE way to manage large C code bases?

share|improve this question
6  
In a language agnostic way, please describe an object. What is it, how is it modified, what should it inherit and what should it provide? The Linux kernel is full of allocated structures with lots of helpers and function pointers, but that would probably not satisfy the definition of object oriented for most. Yet, it is one of the best examples of a very well maintained code base. Why? Because every sub system maintainer knows what is in their area of responsibility. –  Tim Post Nov 29 '10 at 17:41
7  
In the old days, we used separate linking for mocks and stubs for unit testing. Dependency Injection is just one technique among several. Conditional compilation is another. –  Macneil Dec 1 '10 at 4:54

16 Answers 16

You seem to think that OOP is the only means of achieving abstraction.

While OOP is certainly very good at doing that, it’s by no means the only way. Large projects can also be kept manageable by uncompromising modularization (just look at Perl or Python, both of which have excelled at that, and so do functional languages like ML and Haskell), and by using mechanisms such as templates (in C++).

share|improve this answer
18  
+1 Also, it's possible to write a "Big Ball of Mud" using OOP if you don't know what you're doing. –  Larry Coleman Nov 25 '10 at 13:05
6  
@Gulshan: Many large C code bases are OOP. Just because C doesn’t have classes doesn’t mean that OOP cannot be achieved with a bit of effort. Furthermore, C allows a good modularization using headers and the PIMPL idiom. Not nearly as comfortable or powerful as modules in modern languages, but once again good enough. –  Konrad Rudolph Nov 26 '10 at 8:18
8  
C allows modularization on the file level. The interface goes in the .h file, publicly available functions in the .c file, and private variables and functions get the static access modifier attached. –  David Thornley Nov 29 '10 at 19:19
1  
@Konrad: while I agree that OOP is not the only way to do it, I believe OP probably had strictly C in mind, which is neither a functional nor a dynamic language. So I doubt that mentioning Perl and Haskell will be of any use to him/her. I actually find your comment more relevant and useful to OP (doesn’t mean that OOP cannot be achieved with a bit of effort); you might consider adding it as a separate answer with additional details, maybe supported with a code snippet or a couple of links. It would at least win my vote, and quite possibly OP's. :) –  Groo Aug 26 '11 at 7:14

Modules, (external/internal) functions, subroutines ...

as Konrad said, OOP is not the only way to manage large code bases. As a matter of fact, a rather lot of software was written before it (before C++*).

share|improve this answer

The modularity principle is not confined to object-oriented languages.

share|improve this answer

Realistically either infrequent changes (think Social Security retirement calculations) and/or deeply ingrained knowledge because the people maintaining such as system have been doing so for awhile (cynical take is job security).

Better solutions are repeatable validation, by which I mean automated test (e.g. unit testing) and human testing that follows proscribed steps (e.g. regression testing) "as opposed to click around and see what breaks".

To start moving toward some sort of automated testing with an existing codebase I recommend reading Michael Feather's Working Effectively with Legacy Code, which details approaches for bringing existing codebases until some sort of repeatable testing framework OO or not. This leads to the sort of ideas others have answered with such as modularization, but the book describes the right approach to doing so while not breaking things.

share|improve this answer

Even without abstraction most programs are broken up into sections of some sort. Those sections usually relate to specific tasks or activities and you work on those the same way you would work on the most specific bits of the abstracted programs.

In small to medium sized projects this is actually easier to do that with a purist OO implementation sometimes.

share|improve this answer

Though dependency injection based on interfaces or abstract classes is a very nice way of doing testing, it isn't necessary. Don't forget that almost any language has either a function pointer or an eval, which can do anything you can do with an interface or abstract class (the problem is that they can do more, including many bad things, and that they don't in themselves provide metadata). Such a program can actually achieve dependency injection with these mechanisms.

I have found being rigorous with metadata to be very helpful. In OO languages the relationships between bits of code are defined (to a degree) by class structure, in a way standardized enough to have things like a reflection API. In procedural languages it can be helpful to invent those yourself.

I've also found code generation is much more helpful in a procedural language (compared to an object oriented language). This guarantees meta-data is in synch with the code (since it is used to generate it) and gives you something a bit like aspect oriented programming's cut points - a place were you can inject code when you need it. Sometimes it's the only way to do DRY programming in such an environment that I can figure out.

share|improve this answer

Abstraction, abstract classes, dependency injection, encapsulation, interfaces and so on, are not the only way of controlling large code bases; this is just and object-oriented way.

The main secret is to avoid to think OOP when coding non-OOP.

Modularity is the key in non-OO languages. In C this is achieved just as David Thornley just mentioned in a comment:

The interface goes in the .h file, publicly available functions in the .c file, and private variables and functions get the static access modifier attached.

share|improve this answer

Actually, as you recently have discovered, first order functions are all you need for dependency inversion.

C supports first order functions and even closures to some extent. And C macros are a powerful feature for generic programming, if handled with necessary care.

It's all there. SGLIB is quite a good example on how C can be used to write highly reusable code. And I believe there's a lot more out there.

share|improve this answer

One way of managing code is to decompose it into the following types of code, along the lines of the MVC (model-view-controller) architecture.

  • Input handlers - This code deals with input devices such as mouse, keyboard, network port, or higher-level abstractions such as system events.
  • Output handlers - This code deals with using data to manipulate external devices such as monitors, lights, network ports, etc.
  • Models - This code deals with declaring the structure of your persistent data, rules for validating persistent data, and saving persistent data to disk (or other persistent data device).
  • Views - This code deals with formatting data to meet the requirements of various viewing methods such as web browsers (HTML/CSS), GUI, command line, communication protocol data formats (e.g. JSON, XML, ASN.1, etc).
  • Algorithms - This code repeatably transforms an input data set to an output data set as fast as possible.
  • Controllers - This code takes inputs via the input handlers, parses the inputs using algorithms, and then transforms the data with other algorithms by optionally combining inputs with persistent data or just transforming the inputs, and then optionally saving the transformed data in persistent via the model software, and optionally transforming the data via the view software to render onto an output device.

This method of code organization works well for software written in any OO or non-OO language because common design patterns are often common to each of the areas. Also, these kinds of code boundaries are often the most loosely coupled, except algorithms because they link together the data formats from the inputs to the model and then to the outputs.

System evolutions often take the form of having your software handle more kinds of inputs, or more kinds of outputs, but the models and views are the same and the controllers behave very similarly. Or a system may over time need to support more and more different kinds of outputs even though the inputs, models, algorithms are the same, and the controllers and views are similar. Or a system may be augmented to add new models and algorithms for the same set of inputs, similar outputs, and similar views.

One way OO programming makes code organization hard is because some classes are deeply tied to the persistent data structures, and some are not. If the persistent data structures are intimately related with things such as cascading 1:N relationships or m:n relationships, it is very hard to decide class boundaries until you have coded a significant and meaningful part of your system before you know you got it right. Any class tied to the persistent data structures will be hard to evolve when the schema of the persistent data changes. Classes which handle algorithms, formatting, and parsing are less likely to be vulnerable to changes in the schema of the persistent data structures. Using an MVC kind of code organization better isolates the messiest code changes to the model code.

share|improve this answer

I guess the answer to your question lies with Linux. Its a non-OOP based project written completely in C.

So go ahead and figure out how that has been managed. Tip: Linus is on Google+ :-)

share|improve this answer

When working in languages which lack inbuilt structure and organisation features (e.g. if it doesn't have namespaces, packages, assemblies etc...) or where these are insufficient to keep a codebase of that size under control, the natural response is to develop our own strategies to organise the code.

This organisation strategy probably includes standards relating to where different files should be kept, things that need to happen before/after certain types of operations, and naming conventions and other coding standards, as well as a lot of "this is how it is set up - don't mess with it!" type comments - which are valid so long as they explain why!

Because the strategy is most likely going to end up being tailored to the specific needs of the project (people, technologies, environment etc...) it is hard to give a one-size-fits-all solution to managing large code bases.

Therefore I believe the best advice is to embrace the project-specific strategy, and make managing it a key priority: document the structure, why it is that way, the processes for making changes, audit it to make sure it is being adhered to, and crucially: change it when it needs to change.

We are mostly familiar with refactoring classes and methods, but with a large codebase in such a language it is the organising strategy itself (complete with documentation) that needs to be refactored as and when necessary.

The reasoning is the same as for refactoring: you will develop a mental block towards working on small parts of the system if you feel that the overall organisation of it is a mess, and will eventually allow it to deteriorate (at least that's my take on it).

The caveats are also the same: use regression testing, make sure you can easily revert if the refactoring goes wrong, and design so to facilitate refactoring in the first place (or you just won't do it!).

I agree that is is much trickier than refactoring direct code, and it is harder to validate/conceal the time from managers/clients who might not understand why it needs to be done, but these are also the types of project most prone to software rot caused by inflexible top-level designs...

share|improve this answer

If you are asking about management of a large code-base, you are asking for how to keep your code base well structured on a relatively coarse level (libraries / modules / building of subsystems / using namespaces / having the right docs at the right places etc.). OO principles, especially 'abstract classes' or 'interfaces', are principles for keeping your code clean internally, on a very detailed level. Thus, the techniques for keeping a large code base manageable don't differ for OO or non OO-code.

share|improve this answer

Your premises are wrong. You don't need abstract classes, dependency injection or other fancy stuff to do testing.

share|improve this answer

The biggest technical challenge is the namespace problem. Partial linking can be used to work around this. The better approach is to design using coding standards. Otherwise all the symbols become a mess.

share|improve this answer

How it's handled is that you find out the borders of the elements you use. For example, the following elements in C++ has a clear border and any dependencies outside the border must be carefully thought out:

  1. free function
  2. member function
  3. class
  4. object
  5. interface
  6. expression
  7. constructor call / creating objects
  8. function call
  9. template parameter type

Combining these elements and regognizing their borders, you can create almost any programming style you want within c++.

Example of this is for a function would be to regognize that it's bad to call other functions from a function, because it causes dependency, instead, you should only call member functions of the parameters of the original function.

share|improve this answer

While it is possible to manage non OO code bases using functional decomposition, modularization, namespaces, its pretty hard to manage a large code base especially as the problem & soln space become complicated without OO approach.

share|improve this answer
1  
There were gigantic code bases way before OOP caught on, though. Just look at the whole C language. There are things that come close, like structs, and I am no expert on C/++, but there is a lot of procedural code that existed before OOP that was maintained just fine. A game-programmer colleague once told me that the language-of-choice in the '90s was procedural C ... look at any game released 15 years ago –  sova Nov 29 '10 at 19:09
1  
Ah I see what you mean, but I might still disagree... in an extreme example, what if none of your maintainers know OOP? Then it's trivial to prove that OOP is harder... maybe I should get away from these proofs for a while –  sova Nov 29 '10 at 19:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.