I was tasked with implementing a networking stack. The software is self-contained with no external dependencies - it only needs the C++14 standard library, and a POSIX platform.
In my opinion there's nothing preventing it from being successfully implemented in a single repository as the logical separation (layers, classes, etc.) can still be laid out cleanly without the need to manifest as physical separation (a separate project-directory-with-a-Makefile per class, for example). However, my superiors insisted on splitting the project into multiple smaller repositories and I have a hard time evaluating their rationale.
One of their arguments is that "back in 2003 we have tried writing projects in single repositories and it didn't work, it needed a week to compile!". This does not convince me.
How would splitting the project up into multiple subprojects help in this scenario? If you have one 50k lines project, you have to compile 50k lines. If you have ten 5k lines projects you still have to compile 50k lines, but now you can't let the build system exploit the dependencies between entities to compile in parallel because you have serialised the build (first build package A, then package B, then package C, etc.).
Also, you can't just have class A
have B*
member and, at the same time, have class B
have a A*
member without either having them in the same project (to let the compiler see their headers), or creating yet another package containing only the headers for those classes. But this is madness! Instead of having one project you now have three... because of reasons. (As a side note, maybe this is caused by the fact that me and my team are forced to cram everything into Maven packages? I don't see how that helps us for exactly the "A*
-B*
" problem stated above.).
What is more, putting everything into packages stored on a central server (again, we are forced to use Maven) has the unfortunate effect of other developers affecting my work at the least expected moments. When I'm fixing a bug I expect my environment to remain constant - but when I build a project, and the build system automatically fetches new versions of all its dependencies the environment changes, and a bug that my colleague has just introduced shadows the one I'm working on because it happens earlier in the execution path... and now I'm losing time because I need to fix my local environment. I don't see how that helps us.
Again, this issue can be easily fixed by performing only offline builds without reaching out to the online repository but then I have to manually go into each project directory and run the build to make the packages available in local repository on ever developer's machine.
However, this has clear disadvantages in languages such as C++ because I have to remember in which order the packages must be built - otherwise, the size of objects produced by a library may be different from what the consumer of the library expects (it's the simplest example of what can go wrong: I build package A
with an old version of package B
, then I build package B
but the class it contains now has one additional int
member... and oops, we're out of sync and go straight to the SIGSEGV land); if I was allowed to make the project a monorepo even a simple Makefile would hand the dependencies correctly.
On the other hand, a single project has clear advantages (in my opinion):
- I can tell the test team "The last known good version is
0f52ab27199ffc
, use that for testing because we may have since broken X, I'm not yet sure, but it behaves strangely after we did Y." - I can freeze my environment for the time I need to fix a bug (simply by branching out from the master branch).
- I can fully utilise available tools to speed-up builds (e.g. invoke
make -j N
to launchN
jobs in parallel). - I can let the compiler loose on the codebase with
-O3
and let it optimise the resulting binary without placing obstacles in its way (e.g. it's much easier to inline a function that's available as source, than one that's available as a symbol in some.so
). - I can easily debug the program - no "Oh, I forgot to add
-g
to projectfoo
." or "Make breakpoint pending on future shared library load?
" in GDB.
Are my arguments valid, or am I misguided and splitting a project into a bunch of .so
s is really the way to go ("because that's how we do it in our Java projects")?