13

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.
14

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.

  • 1
    All in all, this is a really good answer. I would just object to seeing setjmp()/longjmp() as a valid tool: It does not even try to perform any cleanup. Any allocation will be leaked, any held lock won't be released, any opened file won't be closed, and any transient data inconsistency will become permanent. IMHO, this function pair is basically the worst hack ever invented, with the sole justification that it was possible to implement it. In the end, there is really only one valid way to do error handling in C: explicit error codes. – cmaster Nov 11 '16 at 13:39
  • @cmaster yea. Personally, setjmp/longjmp seem a fish out of water in C and I never used them. I felt compelled to include them only because of the numerous tutorials/libraries on the internet for mimicking exceptions, so I thought there are people out who actually use it. – An Owl Nov 24 '16 at 23:50
7

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.

  • 2
    I reallize that my question was not clear at all. I don't want to refactor the code. At all. I want to maintain the existing code base the way it is. However I want to learn how to write modern C for the new feature. And here I'm lost. Most of the documentation I've found is about how to code in C, not about how to write 'modern' C. Maybe there's no such thing like 'modern' C... – Guillaume Nov 4 '16 at 8:12
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.

  • 1
    I have to disagree here. C and C++ are distinct languages, and some code required by a C++ compiler (explicitly casting the return value of malloc) is considered bad practice in C. The meaning of const and inline is also very different between C and C++, and of course C++ does not understand __restrict. Don't treat the languages as interchangeable, even in the subset of sources which compile in both. – Angew Nov 11 '16 at 13:22
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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