11

I have spent the vast majority of my programming career using Java and very OO based C++. I am really interested in learning to think more procedurally, so I have been starting to do some practice using a more restricted subset of C++ (basically C) and trying to keep my code very procedural.

My question lies in the area of how to structure a program. In Java, I am constantly thinking of objects... so when I create a class I am thinking of:

  • What is this object representing?
  • What can this object do?
  • What do others need to be able to do to it?
  • What do I expose, what do I hide?
  • Etc

In the case of procedural code, how does this sort of thing work? Lets say wanted a struct to represent a Car. I would create a header to define it in... but where do functions go that should operate on a car type? Would you also define all Car related functions in the same file line you would a Java class? Am I totally thinking about this the wrong way, in that there should not be any Car related functions since the emphasis should not be on the Car object but on the functions themselves? IS the idea of a member function totally thrown out the window?

The bottom line is my brain has been wired to think about Objects, Encapsulation, Polymorphism, etc.. and I cant seem to wrap my head around anything else. How can I practice?

Any advice is appreciated!

11
  • 1
    There is nothing about OOp that is non-procedural. The opposite of procedural is functional. Non-OOp is sometimes referred to as "structured". Moving away from OOp in the "structured" direction is, in my opinion, a regression, like abandoning electric light in favour of candlelight. Feeling romantic yet?
    – Mike Nakis
    Commented Apr 24, 2015 at 19:15
  • 1
    A long while ago after using C++ I've got a feeling: oh, I can't program C anymore! It's 2015. Why do you have such a need to go back? Commented Apr 24, 2015 at 19:19
  • 3
    @MikeNakis I think it'd be more accurate to say the opposite of functional is imperative, and procedural is just one offshoot of imperative programming.
    – Doval
    Commented Apr 24, 2015 at 19:20
  • @Doval you are right.
    – Mike Nakis
    Commented Apr 24, 2015 at 19:44
  • :) Seems that you guys know Latin... Commented Apr 24, 2015 at 19:54

8 Answers 8

12

A lot of procedural code is very OOP-like. Basically, instead of object.function(params), you do function(object, params). You can group your files accordingly.

However, what a lot of long-time OOP programmers don't realize is how limiting it is that a function must belong to one and only one class, and that all such functions must be grouped into one file accordingly. Procedural-style allows for many other kinds of groupings that often fit better under certain circumstances:

  • Combinations of two objects, like all functions dealing with both engines and transmissions.
  • Collections of objects, like all functions dealing with lists of cars.
  • All implementations of an interface. For example, the implementations of steer() for different kinds of vehicles could all be grouped into one file.
  • Functions that convert from one kind of data structure to another. For example, functions that take a collection of parts and return a car.

Basically, all those times you couldn't really figure out which class is the best fit for something, in procedural that's not really an issue. Don't get me wrong, the way OOP groups functions is successful because it's a very reasonable default. That doesn't make it the best grouping under all circumstances, though.

5
  • The usual solution to the “grouping problem” is to introduce a special object to own the methods: this is how functional programmers sneakily put the emphasis on treatments again when they must do OOP. :-) Commented Apr 25, 2015 at 7:16
  • second the inventing new objects approach. Some example I saw the other day added CashRegister, because neither a customer or an item would know about payments. It becomes procedural when you can't be bothered to make up a name and just call it PurchaseService
    – Ewan
    Commented May 11, 2015 at 9:15
  • The grouping of 'procedures' together as methods inside objects is very valuable part of why OOP is easier to reason about. Those that work together, live together. The forced cohesion is nice because having to jump around large codebases is exhausting. No, procedures cant always just be grouped into files to solve the problem, unless this was enforced as it is in OOP design. It would still result in the same unit of organization anyway, but lacking explicitness and with lots of code duplication.
    – Seph
    Commented Jul 4, 2018 at 10:07
  • Grouping is not enough because people reason about structured code by thinking about groups of procedures that call each other. Procedures placed into a file with one call grouping have to be duplicated in another file if its used by another grouping, otherwise there is no reasonability at all. Just a quaint filing away of code. You need a conceptual model to bind the unit of reuse together into something coherent for people. That way groupings can stay in the same place and simply be reused by callers.
    – Seph
    Commented Jul 4, 2018 at 10:07
  • God help you with data. But with OOP, all state changes of a grouping are front and center as well. They are also kept private. The object is a bag of data and its methods that operate on it. Sorry but while procedural code can "looks like OOP", it lacks in the nuances that make OOP such a useful design tool for people.
    – Seph
    Commented Jul 4, 2018 at 10:07
6

To be honest, this seems a bit like going backward and programming with one hand behind your back, but if by "structured", you mean, like how people created programs before Object Orientation in procedural languages, then it's about how you start thinking about the problem. In your fourth paragraph, you are essentially still thinking in an Object Oriented way. The way people wrote in languages like C, Pascal and BASIC back in the day was to start with the code not the data. An analogy would be that a computer program was like a recipe; a series of steps to follow.

So you wouldn't say "Let's say you wanted a struct to represent a Car". You'd say "I want to transport myself to the grocery store". You'd only start thinking about objects like "Car" when you got to the step that required it.

To use a more concrete example: Suppose you wanted to send a bunch of letters to addresses in a file. Thinking in OO, you'd probably first start thinking about things like address objects. But with old structured languages, you'd probably first think about the steps:

  1. Open file
  2. Read a address
  3. Format address information
  4. Print address
  5. Go to 2

You'd devolve each of those into steps, like

4:

  1. Print name
  2. Print house number
  3. Print street
  4. Print city
  5. Print state
  6. Print zip code

To make all this easier, you'd almost certainly put the address data into some sort of struct to keep it together, but that would be driven by the steps you were taking. You wouldn't start with that.

The thing is: this gets really hard to manage once you get to programs of any significant size. This is why nearly everyone thinks in objects these days unless they are writing a quick little script.

5
  • 2
    I was going to post the recipe analogy. I'm a bit surprised anyone would claim to be good at OOP but not "procedural programming". I mean, at some level, one has to write procedure code, even if it's deeply nested in some IWidgetMarshallingTierShimFactory<T,U> or whatever. I've long suspected that a lot of what passes for OOP tends to warp people's brains, though, especially if they take it too seriously. We have here some anecdotal evidence. Commented Apr 24, 2015 at 19:36
  • So would you say that intentionally avoiding classes as some form of alternative practice is a totally useless idea? In that case, is choosing to use C over C++ on a fresh project just pointless unless the underlying hardware requires it? I just have always though in this OO way, and I thought maybe it would help me to change the way I approach problems. Maybe it makes more sense to learn a language like Haskell to get into a totally different paradigm?
    – JParrilla
    Commented Apr 24, 2015 at 20:36
  • 1
    I wouldn't say "totally useless". I would say that thinking about things from an OOP perspective rather than a procedural perspective makes it much easier to deal with large programs. If I am writing a fifty line script, I still often do it in a straight procedural manner. I would say that a functional language is going to push you forward a lot more than trying to do it old-school.
    – user53141
    Commented Apr 24, 2015 at 21:31
  • @JoeP: when to choose C over C++ was disccussed here programmers.stackexchange.com/questions/113295/… or here: stackoverflow.com/questions/497786/…
    – Doc Brown
    Commented Apr 25, 2015 at 6:46
  • I like this example (address printing) since it addresses (pun intended) a typical problem when structuring OO programms: should print(DocumentPrinter p) be a method of the Address class, or is it the responsibility of the DocumentPrinter class to have a printAddress(Address a) method? In procedural code, you just have a procedure printAddress(Address a, DocumentPrinter p) somewhere in the code and usually don't care that much if it belongs to the printer rather than the address.
    – user281377
    Commented May 11, 2015 at 13:30
6

Start from data structures.

Write functions operating on those data structures.

If you want encapsulation, do it at the level of modules, not objects.

If you want polymorphism, use higher-order functions, not virtual dispatch.

That’s about it. OOP as practiced is not a significant departure from procedural programming. It’s primarily a set of reasonable defaults for constructing procedural programs.

8
  • 1
    You didn't show an example of procedural programming. You showed an example of how to use a procedural language in an OO way.
    – Dunk
    Commented Apr 29, 2015 at 20:22
  • 1
    @Dunk: I did exactly the opposite, per the original question. If you think that avoiding objects, member functions, and virtual dispatch still constitutes using a language in an OO way, then I cannot hope to convince you of anything.
    – Jon Purdy
    Commented Apr 29, 2015 at 20:30
  • You are passing behavior with the data. IOW, you are passing a class to the draw_shape function. Explain how that is not OO programming?
    – Dunk
    Commented Apr 29, 2015 at 21:06
  • @Dunk: Datatypes with member functions are dual to functions with closures. You interpret “pass this function and this value” as “pass this object”. You could also interpret it as “pass this activation”. They’re identical in this case, with only 1 function in the vtable or 1 value in the closure. In OOP you can’t change the vtable because the object decides its own behaviour, while in FP you can’t change the closure because the function is a black box. But in procedural programming, data and behaviours are not generally coupled at all, which I was trying to illustrate.
    – Jon Purdy
    Commented Apr 29, 2015 at 23:48
  • Is there a procedural (non-oo) language that supports templates anyway?
    – user281377
    Commented May 11, 2015 at 13:35
4

The way we used to do sort-of OOP back in the days of C was by declaring a struct and then declaring functions that accept such a struct as their first parameter. Then sometimes the need for polymorphism would kick in, so we would have our struct contain not only data fields, but also pointers to functions. It is quite pointless, really, doing by hand what C++ would do for us with far fewer lines of code, more safely, more elegantly, and in a way that everyone who knows C++ understands, instead of each shop rolling their own.

If you really want to abandon OOP in favour of structured programming, you have to move away from objects, and start thinking in terms of modules, but you will soon see that you will naturally and inevitably gravitate towards OOP, and there is nothing wrong with that, because OOP is a very convenient way of thinking about programming, and I would even dare say a necessity in order to build complex systems.

Before C++ we used to write modules which exposed functions, and quite often these modules were managers which contained some plurality of entities, so they would expose some sort of 'handle' to let you indicate which entity you were referring to, as for example the case is with operating system "file handles" of old, which you may have heard of. That was already OOP, even though it was not called by that name yet. In the beginning the handle usually was an int and it was used by the module as an index into some internal array of structs, but at some point someone figured that since the user code is not supposed to try and interpret the handle in any way, the handle could just as easily be an actual pointer to the internal struct; nobody would notice, and nobody should mind.

So, in order to do anything a) useful and b) large-scale you have to think OOP, but if you don't use an OOP language then your implementation will be behind some monolithic flat interface, and you will still have to do an awful lot of mindless manual work in order to achieve certain things like inheritance and polymorphism, (which you will need if you try to do anything of non-trivial complexity,) and which will be provided for you if you use an OOP language.

11
  • 2
    @JoeP: C++ has a variety of other features besides just OO features, such as exceptions. With C, it's pretty straightforward to imagine what your resulting assembly code is going to look like, with C++ that is pretty much impossible. Commented Apr 24, 2015 at 20:17
  • 2
    @JoeP Linus Torvalds' main objection seems to be not so much about C++, but about (in his words) the substandard programmers who (in his experience) use C++. In light of that, his beef appears to be to a large extent a social issue rather than a technological issue. I suppose he must have technical issues with C++ too, but I don't know them. (The few things he mentioned in that famous email don't really make sense.) So, I am not very fond of arguments by authority, and frankly, I am not in a hurry to take advice from someone who is ranting like a foul mouthed teenager.
    – Mike Nakis
    Commented Apr 24, 2015 at 20:44
  • 1
    @MikeNakis Yes for sure, I agree about what his argument was really about. In some ways it makes sense, because in my own experience I notice that all of the shiny features in C++ cause people to want to use them when they dont need to or dont truly know how to. By limiting them to C, you ensure that they wont make as much of a mess. Now of course, this says nothing about C++ as a language.. it is the fault of the developers. C++ being complex is not a bad thing in my opinion.. it is the responsibility of the developer to wield it correctly.
    – JParrilla
    Commented Apr 24, 2015 at 20:57
  • 1
    Discussing C vs. C++ in this manner is off-topic. And in any case, trust me, thanks to the preprocessor there is nothing that you really know when you are looking at a statement either in C or in C++.
    – Mike Nakis
    Commented Apr 24, 2015 at 22:13
  • 1
    @JoeP: Linus objections has also a technical aspect - at least at the time he wrote the post, the real-world portability of C++ was not nearly as high as of C, which is a show-stopper if you are going to write something like a highly portable operating system.
    – Doc Brown
    Commented Apr 25, 2015 at 7:02
3

The bottom line is my brain has been wired to think about Objects, Encapsulation, Polymorphism, etc.. and I cant seem to wrap my head around anything else. How can I practice?

The biggest – if not the only – difference between procedural programming and object-oriented programming is the notion of inheritance.¹ For instance, it is straightforward to implement a polymorphic list type in C, but translating inheritance relations would involve a lot of work. As long as no inheritance relations are involved, writing

Car myCar;
myCar.start();

in C++ is just a fancy way to write

Car myCar = car_construct();
car_start(myCar);

in C. So in procedural programming, you solve problems by defining data structures and treatments on these data structures, and this works pretty similarly as in OO-languages, except that you cannot use inheritance-based abstractions.²

If you want to progress in procedural programming, I would suggest studying programs written by others and writing similar programs yourselves. You will learn a lot by comparing your solution to the other and asking yourself how important the differences are. Reading and writing shell scripts is probably a good step to learn procedural programming, because this is a competence which is also useful on its own. You typically find a lot of scripts bundled with your operating system – and can find good quality scripts randomly on the internet.

It is also worth to note that one of the few³ widespread, of significant complexity and virtually bug-free program ever was written in a procedural language. The program I think of is TeX which was written in a Pascal-like language, and the code source is described extensively in a book TeX: the program which you could consider reading. It is easy to legally find or produce electronic copies of the book, and the nearest scientific campus will have a hardcopy in its library.


¹ Which you do not mention on your question!
² Inheritance is great to solve software engineering problems but it does not solve any practical problem. So in view of the problem solved by the software, inheritance solves meta problems.
³ I actually do not know another one!

3
  • 2
    Re-"the only difference...inheritance". That's only true if you choose to use OO in a procedural way or vice-versa. Procedural and OO are VERY different. In Procedural you are passing data around. The behavior is a first class citizen. In OO you are passing behavior around. The data (ie. the class) is the first class citizen. What I mean by first class citizen is the models you create will show behavior as its main bubbles in procedural, while OO will show classes. The consequences of that means that your designs and implementation will look very different depending on which you choose.
    – Dunk
    Commented Apr 29, 2015 at 20:17
  • @Dunk You are definitely right and I deliberately narrowed the answer to context suggested by the question: a language sharing mist of the features of C++ and Java and an inclination for procedural programming. Reading it again, the “only difference” statement might be a kind of a hyperbolae. :) There is a little trick in my answer: in languages a C++ or Java (without duck typing) we use inheritance to think of objects as behaviours – but I took care to rule that out. If you tell me that using these languages without inheritance makes them almost useless, I will not deny! :) Commented Apr 29, 2015 at 20:42
  • 2
    I admit that my comment is really geared at your "only difference" comment. I have seen quite a few, there's not much difference comments, so I had to respond. Having entered the field when structured design was all the rage, I can tell you first hand that OO and structured are dramatically different. I'm sure there are quite a few senior people who got to "enjoy" their first go at an OO project and thought that OO was just C with classes. They'll clearly tell you that the missed deadlines or failed project removed that myth from their mind forever. They are dramatically different mindsets.
    – Dunk
    Commented Apr 29, 2015 at 21:16
2

The way I see it, to go from proper object-oriented programming to a procedural style, you need to:

  • remove behavior from your objects; make them just containers of data (data structures)

  • implement behavior in separate classes (services); they will take the data structures as arguments and perform some action according to application use cases

  • use the same criteria of design quality you would use in OOP (cohesion, coupling, etc), only apply them to services

As others pointed out, this is probably a regression from OOP, especially for large projects. Could be a valid approach for simple scenarios.

1
  • Good answer about techniques. Not so sure about the shift of mindset. Commented Apr 24, 2015 at 20:05
1

What is this object representing? What can this object do? What do others need to be able to do to it? What do I expose, what do I hide? Etc

When you create a struct, the only thing you need to answer is what its data structure should be. It can't do anything, it can't hide anything, all it is is a convenient method of accessing plain memory. It may be a good idea to try to answer some more questions about the struct, but there is no formal grammar for writing the answer to those questions. (That is also true for a lot of good questions about OO objects.)

but where do functions go that should operate on a car type?

Wherever you think it fits best, the struct does not make a decision for you, it doesn't even dictate that the relevant code should be a dedicated function. Decide on an implementation when you actually have to use it.

IS the idea of a member function totally thrown out the window?

It doesn't have to be, the function(object, parameters) structure is equivalent to the basic use of member functions. It should compile to pretty much the same thing.

Encapsulation

A bit simplified, all encapsulation ever does is throw an error message if you try to break it. Sure it can be nice to have some formal guarantees about what code can't do, but you mostly get the same benefits by defining non-compiler-checked boundaries. In any case, the only encapsulation method that you actually lose is private members and methods. In my experience, those are not nearly as important as variable scopes.

Polymorphism

The big case for OO. Solves a lot of weird problems, and doesn't have any simple replacement. But there are lots of structures that do similar things, here is a few:

  • Make a struct with space for all the data needed by any desired subtype, have a variable mark what subtype it is. You can use multiple such marks in order to make something that resembles multi inheritance. Rather than overloading methods, the code that deals with such structs must simply check the type and act accordingly. It may waste some memory, but in most cases that is probably not a big deal. If you have a lot of different subtypes it could however spread the code defining the differences between those subtypes all over your program.

  • Include function pointers as member variables, these can be set to the functions matching the subtype.

  • Include pointers to sub-structs, with memory for this specific subtype.

  • Make all the desired subtypes distinct struct, and copy-paste any code that apply to all. Doesn't sound good, but if the differences take up a lot more code than the similarities, it could be a reasonable choice. The don't-do-this-at-home version is where you construct the memory layout so that the same code will work on different structs.

For learning purposes I think it is a wonderful idea to shelf an advanced feature, especially if it is one you tend to overuse. In real-world code, you should of course use the OO features where it makes sense.

2
  • Thanks for that. I think your last sentence sums it up. I am not trying to justify forcing myself to use pure C in a job or in some large project that others will use.
    – JParrilla
    Commented Apr 29, 2015 at 18:04
  • The intent is to try to use a stripped down language to explore parts of the system that I have ignored for years (since my CS degree program). My goal is to get some practice working close to the machine just for fun and to have a break from my daily work in Java and Ruby. Also I really want to get more involved in some of the C based open source projects in GNU and Linux so I thought it would be a good idea to practice.
    – JParrilla
    Commented Apr 29, 2015 at 18:15
1

I'd recommend reading some of the structured programming "classics": books by Ed Yourdon, Larry Constantine, especially a lot of Dave Parnas' stuff. They talk a lot about top-down design, using structure charts and data-flow diagrams, breaking code into modules and how to handle coupling and cohesion. It's interesting reading, and some of the tools (e.g., data flow diagrams) can still be used today. I sometimes feel that a structured approach is a better fit to some problems (those involving parsing or state machines, usually), but that's not a popular opinion.

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