14

I have a strong Java/Groovy background and I've been assigned to a team that maintain quite a big C code base for an administrative software.

Some pain points, like dealing with blob in the database or generating reports in PDF and Excel have been externalized to java web service.

However, as a Java dev, I'm a bit confused by some aspects of the code:

  • it's verbose (especially when dealing with 'exception')
  • there's a lot of huge methods (many 2000+ lines method)
  • there's no advanced data structures (I miss List, Set and Map a lot)
  • no separation of concern (SQL is joyfully mixed all around the code)

As a result I feel that the business is hidden in tons of technical code and my brain, shaped with Object Oriented and a pinch of Functional programming, is not at ease.

The good side of the project is that the code is straight forward: there is no framework, no byte code manipulation at runtime, no AOP. And the server can simultaneously answer to 10000+ users with a single machine by using less memory than java needs to spit "hello world".

I want to learn how to write C code accordingly to commonly accepted modern principles. Is there any commonly accepted principles about how modern C should be written and structured ?

Something a bit like the equivalent of the 'Effective Java' book, but for C.

Edit at the light of answers and comments:

  • I'll try to adapt my mindset to C code and not try to mirror it to OOP.
  • I've started to scan-read the recommended coding style guides from the comment (The GNU Coding Standards and The Linux Kernel Coding Style).
  • I'll then try to propose this code style to my co-workers. The most difficult part might be to convince co-workers that huge method could be split in smaller parts and that to repeat the same 4 lines of error handling code could be avoided by the help of a method.
Guillaume
  • 2,207

4 Answers4

15

I can read from your question the problem isn't that the code is old C but just bad programming. Most of the problems you mentioned like verbosity, huge 2000+ line functions, or no separation of concerns is applicable to any language, C or Java alike.

Verbosity was mentioned in context of error handling. You didn't provide an example so I can only remind that error handling code is also code. There is no excuse for repetitive sections of boilerplate code. Factor it out; either to a function or (if it's not worth to create a separate function) do the goto Error; pattern and move the error handling and resource cleanup to an Error: section at the bottom of the function.

If passing the error up the call chain seems to be the problem, ask yourself: does the function up there really need to know some little guy down here had a problem? Exception mechanisms built-in a language make it easy to do so, but in general it's better to handle exceptions early (in any language) so that the error condition doesn't pollute the logic of high-level code. And if the function up there really needs to know, there are ways to emulate exceptions with setjmp and longjmp.

I think the only really C-related problem mentioned is the lack of standard containers. While Set can in general be replaced with a sorted array and Map (for the most part) with an array of pairs or a struct (if you know the key set before hand, map[key] = value turns into s.key = value), but the fact is there's no dynamic array container in the standard library. In C99 you can at least declare a variable length array on the stack (int array[len]) but you need to calculate len beforehand (usually not hard) and of course you can't return it as any stack-allocated object. Most project end up with writing up their own dynamic array container or adopting an open-source one.

On a closing note, I'd like to point out that I've been there. I've been the Java programmer that moved to C++ and pure C. I would like to advise "read book X to learn good C" but there isn't any just like there isn't any for Java. The way to go forward is to soak up all the intricalities of the language and the standard library; google a lot, read a lot, and code a lot until you start thinking in C. Trying to write things in C as you would in Java is as frustrating as trying to write a sentence in a foreign language with words directly translated from your mother tongue; both you and the reader will cringe. The good news is that learning good programming is slow but learning another language is fast. so if you write decent code in Java, applying the same general common-sense to C will work too.

An Owl
  • 446
8

The good side of the project is that the code is straight forward: there is no framework, no byte code manipulation at runtime, no AOP. And the server can simultaneously answer to 10000+ users with a single machine by using less memory than java needs to spit "hello world".

I would recommend you be cautious of whether this is worth your time and the company's money to spend resources on "modernizing" a working software with low code complexity and who performs well. There is a high likely hood that you will introduce new bugs yourself, especially since it seems to be a system you are unfamiliar with.

If you still want to go down that route then I would suggest the following:

  • Make (or generate) a state diagram of the software/code
  • Dive into the code and make a list of the most complex or critical parts of the code respectively
  • Find someone who is knowledgeable about that code base and ask them why it has been built this way and what has been known to cause problem
  • Write documentation from what you have learn

At this point, you will decide whether or not this is worth exploring. If your company's culture does not reward failure then get the green light from a higher up or a manager.

  • Compartmentalize the different building blocks of the software and write unit tests for each.
  • Iterate until you can glue the different modules together
  • Do further testing that simulate real user interaction (stress tests etc.)

I think that's a fairly good road map and take you wherever you need. Without knowing the specifics of this project it is hard to help you much. Please do not discard my disclaimer as overly alarmist. Tons of excellent programmers have beaten the dust by trying to rewrite an existing project to their favorite language or using "modern" tools. That's a decision that must be carefully thought out and I urge you not to go rogue and do that on your own without management support or assistance from your colleagues.

1

If you would prefer a higher level language, there are some languages like C++ or Objective-C that can be mixed with C code quite easily.

Alternatively, C and C++ are reasonably compatible. You might be able to just compile the whole codebase as C++ with few changes - you'll have the occasional variable named "class" or "template" that you need to rename, but in practice that will be all. (sizeof ('a') is different in C and C++, but I don't think I've ever used that).

If you go that route, consider that the next maintainer might not be too fluent with C++. Don't get carried away. Take advantage of C++, but only so far that a C programmer can easily make sense of it.

gnasher729
  • 49,096
1

Basically, writing good C code is just the same as writing good C++ or Java code: You want a class, use a struct. You want inheritance, include the base struct as a nameless first member. You want virtual functions, add a pointer to a static struct of function pointers. And so on, etc. It's exactly what C++ does under the hood, the only difference is, that it's explicit in C. Thus, you can do perfectly object oriented programming in C, it just looks a bit different and more boilerplaty than what you are used to.

The point is, that good programming is about paradigms, not about language features. True, it is always nice if your language features provide good support for the paradigms you want to use, but the language features are not a requirement. Once you realize this, you can write good code in pretty much any language (apart from some esoteric languages like brainfuck or INTERCAL, that is).

Of course, the problem remains that the standard C library does not contain any of those nifty container classes that you are used to. Unfortunately, that means that you will need to either use your own, or work around this lack by use of dynamically allocated arrays. But I'd wager you'll soon find out, that all you really need is dynamic arrays (malloc()) and linked lists/trees that are implemented via pointer members within your classes.