Ask HN: How do you organize teamwork in C?

15 points by dorfuss ↗ HN
With OOP it's all about classes, abstractions, interfaces etc. and design patterns. But outside the object oriented programming, how actually is the teamwork organized? Could you please suggest any online articles to read?

I've tried googling it but I failed to find anything.

19 comments

[ 3.0 ms ] story [ 54.4 ms ] thread
There is no pre-made recipe. You define the scope of the project, you split it in a few "modules" with a well defined interface (functions headers in this context) and data structures and you start coding.

You should do some searches to check how big C projects like the Linux kernel (or any of the BSDs kernels) is managed.

"With OOP it's all about classes, abstractions, interfaces etc. and design patterns."

The ability to create abstractions is pretty much a part of any programming language, including assembler. They're a property of the structure of the code, not of specific language features. For example, in the Linux kernel, which is written in C, there's an abstraction called a file descriptor that represents a stream of bytes that your program can read/write, whether it comes from a file on a disk, a serial port, a pipe, etc.

Interfaces also exist in all languages: they're just a well-defined set of calls that one piece of code uses to communicate with another piece of code. For example, the C runtime library (or any library) has an interface (API), and callers of the library don't need to know about the internal structure of the library to be able to call its services. (There's a pretty good book called "C Interfaces and Implementations"[1] that talks about how to write reusable code in C.)

Classes are also possible to implement in C. A class is pretty much a data structure with a bunch of functions (the methods) that manipulate its data. In C, this can be represented by a structure and bunch of function pointers. The "constructor" function calls malloc() to create an instance of the class, sets its member variables from its parameters and returns a pointer to it. (Implementing inheritance is a bit tricky.)

To be able effectively split development tasks across a team, the same rules apply as in any other language: implement functionality behind well-defined APIs so that one part of the code can be used by another without having to know the details of how it works.

[1] http://www.amazon.com/dp/0201498413

You don't need function pointers without inheritance, just take an instance pointer as the first parameter to each class member function.
It's exactly the same way in large C projects (minus the "design patterns" bit). The project is divided into libraries and programs that are clients of those libraries, each team member gets a couple of them.

If you understand how a large Java program is organized, you understand how it works on C projects. Just remove some of the fluffy Gang-of-Four stuff, and add back in a tiny veneer of DIY interface/object abstraction.

(The first 8 years of my career involved shipping C and C++ on 3 different large projects).

JavaScript teams face a similar issue, due to its dynamic nature. I find that strict coding standards (ex: making sure code aligns) goes a long way.
The organization of your code should not necessarily reflect the organization of your team. Well-organized code does help your team scale, but beyond that, you shouldn't have a three-pass compiler just because three teams wrote the code, and you shouldn't have object-oriented responsibility align with team responsibility; that can get adversarial very quickly.

Have a good continuous integration (CI) system. Enforce that commits must not break the build. Have good commit messages. Have a culture of code review; no line of code should go in without someone other than the author reading it. Make sure that the culture of your company or team encourages people to review code carefully; someone who finds lots of issues (real issues) should be rewarded for a keen eye, not penalized for slowing down development.

Beyond that, if your codebase is sufficiently large than no one person could ever have all of the design in their head at once, then encourage people to become experts in diverse areas: "oh, for that subsystem you should ask Adela, she knows it inside-out". Everyone should have some areas they're an expert in, and more importantly, every area should have at least a couple of experts.

Figure our your data structures as a team first. Decide what operations you will perform on those structures and the results. Use header files as your contract/interface, and divide up the work. Then your team works can work on the implementations/code.
The Linux kernel is probably the largest and best known example. The process actually resembles (or used to) Brooks's pilot-copilot system quite a lot, with Linus Torvalds and Alan Cox making the decisions on what actually got included. The vast army of contributors submits patches, but they would generally be localised to a particular driver or subsystem. Bigger changes (such as rewriting the IO system, or the original addition of the hotplug system) require advance coordinated planning.

In C anyone sensible ends up with a pseudo-module system such that only a particular set of C files are allowed to contain functions that modify a particular set of data structures. Like OO encapsulation that you have to check yourself.

Thank you, precious remarks.

Should I assume that C projects are generally more hierarchical in nature, with one leader who orchestrates the entire production and where it would be rather difficult for team members to "freely" commit a module or a function here and there like you would with adding some sub-class in OO, because then it could ruin some other parts of the code?

Could this be also understood as a reason why Linus doesn't like C++, because it promotes higher flexibility and less carefully designed architecture, that in turn leads to security/performance issues?

C projects are generally subject to more code review as the compiler is no help at all with allocation issues and someone has to check ownership semantics manually whenever a function has a pointer return type.

Thinking about this, almost my entire career has been spent writing "C with classes": projects where the preferred subset of C++ is a very narrow one. The first one didn't even use the STL (it wasn't reliably portable enough at the time), instead defining a few container and zero-copy string classes of its own. It did however have overnight CI builds on dozens of different platforms including more than one non-GCC vendor compiler. We had an Itanium. We were living in the future.

The exceptions to this were C projects of size 1-2 people. Oh, and a glimpse inside a big multinational whose C products were the products of successive mergers lined up along side one another in the source control system. The sort of environment where it might take weeks to get anything committed without breaking some obscure corner of the test suite.

http://harmful.cat-v.org/software/c++/linus (2007) presumably?

"inefficient abstracted programming models where two years down the road you notice that some abstraction wasn't very efficient, but now all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app. "

That sounds like he believes it's less flexible.

Yes, indeed I was refering to that Linus' post. But thanks for the link, I found Bjarne Strousrup's interview particularily interesting to read: http://harmful.cat-v.org/software/c++/I_did_it_for_you_all

Blew my mind:

"...this new language had to divorce itself from Unix, by hiding all the system calls that bound the two together so nicely. This would enable guys who only knew about DOS to earn a decent living too.

"I believe most people have figured out for themselves that C++ is a waste of time but, I must say, it’s taken them a lot longer than I thought it would.

"It was only supposed to be a joke, I never thought people would take the book seriously. Anyone with half a brain can see that object-oriented programming is counter-intuitive, illogical and inefficient."

You can do OOP with C, I've used this for a lot of my projects and it usually works out really well, the code is readable and it's quite easy to scale.
C has the same abstractions and the same OOP capabilities. You just have to type more to get there. Take a look at gtk+ and its supporting libraries for examples. The gist of the idea is a struct is the same as a class but public by default. Your this pointer, constructors and destructors must be called explicitly and error handing as well is explicit. In terms of how you can manage this among a larger team, would be to expose strict interfaces through methods that expect a struct pointer if it's an object. Next you can separate the concerns of the application in the same way you would in any OOP language. IMO this is probably a bad idea as the amount of typing necessary for a marginal performance win is probably not worth the effort. I like to think of it as a whole lot of dirt you will need to move with you fingers. Languages like golang and rust are perhaps better options to explore first?