Some programmers may be narcissists, sure. But I think
the big obstacle standing in the way of collaborative programming is having to be
aware of too much other code in the first place.
It's very hard to read code, because code isn't linear--it's more like de-serializing a graph in your head, and so it takes somewhere between O(n log n) and O(n^2) to fully grok a codebase of size N. So we should limit N as much as possible.
Now, as programmers, we theoretically know how to limit N just fine. We can still make programs as big as we like, but as long as there's low coupling between the components making up those programs, each component can be separated into its own codebase. Each component exports a documented API, each other component consumes those APIs, treating their dependent components as black boxes. This is well-known stuff.
But despite "decrease coupling!" being one of the first things pounded into most programmers in Introduction to Blub courses since the 70s, coupling is still way too high, and codebases far too large as a result. Low coupling, when dressed up in terms like "Service-oriented architecture" or "the Unix philosophy", is seen as mystifying and novel to most programming journeymen, like some sort of weird Eastern religion. This probably means that, despite learning what coupling is and being able to recite that definition in a sort of cargo-cult manner, they never really viscerally absorbed what coupling does, the pain it causes, or how to lower it[1].
You shouldn't have to dive into a codebase big enough to drown yourself in. Codebases shouldn't be that big. Each codebase should be more like a little puddle, maintained by a few people (or even just one narcissistic person!), which is small enough that, if someone gets hit by a bus (or gets annoyed with the narcissist), it's both able to be read in one sitting, or able to be re-written from scratch in one sprint.
Effectively, this creates a "bus factor" of infinity: if any component is small enough that it can be learned, or recreated, then the original programmer is unnecessary to that component's upkeep. Because, sort of like an EC2 instance, the component is just as likely to be created anew, as it is to be "closed" and then "opened" again.
---
[1] I imagine that you could imbue such knowledge with a single assignment to the effect of:
* We'll be working with three already-written programs, Foo, Bar, and Baz. They're each available in source-control on Github.
* Foo is componentized already, so it's split into five codebases. You can clone the main one, then run `git submodule update` to get the rest.
* Bar and Baz, meanwhile, are big monolithic codebases. Just clone the whole mess.
1. On each of the original projects, two open bugs is listed in the issues. Fix the first bug in your fork.
2. Now, refactor your fork of Bar into componentized form. Copy the submodule approach of Foo.
3. Now fix the second bug listed on each of the projects' issue-trackers.