Entropy Tries to Thwart Us
This is challenging because codebases always go in the opposite direction. As you make more changes and new people join the team, everybody understands less and less of what ought to be happening; the fact the code works at all is nearly miraculous! Soon, everyone who touches the codebase adopts an "If it ain't broke, don't fix it" attitude.
Success depends on understanding, though. We have to understand the code to add new features, fix important bugs, refactor, and bring new teammates aboard. Not only that, but problems that are deeper than code, like architecture and scalability, can't be addressed without first understanding.
Understanding Must Be Widely Distributed
One person understanding isn't enough. After all, what happens if that one person gets eaten by a komodo dragon?
There are deeper problems than that, though. Imagine that your brain becomes tightly coupled with a bit of code. The first problem is that your brain is faulty, and you will forget. The second (scarier) problem is that, if you're the only person who understands a piece of code, you own it and you'll maintain it. Forever. It doesn't matter what you else you progress to, when a problem arises with that code, it's your problem. It encourages context switching, and lots of tiny, strange code silos.
How to Create Understanding
How do you increase understanding on a large scale, then? Let's go through a few approaches, none of which are earth shattering.
Automated tests. When you have simple, isolated tests that are run often, it means anyone can learn about the code, make a change, see the effects, and feel good about the work they just did; you are creating understanding. Unit tests, BDD-style tests, integration tests? All of these work.
Refactoring. As you are adding features or fixing bugs, you can create understanding if you're constantly working to make the code as clear as possible. The great thing about these changes is that they can be trivial. One technique is just to revisit the names used in a chunk of old code. If a variable contains sales invoices and you change its name from temp to sales_invoices, you have succeeded. Make more changes like that!
Documentation. Yes, documentation can create understanding, but only if it accurately reflects the current state of your code. The most effective way to do this is to generate it dynamically based on the code itself: method signatures, assertions, url routes, the requirements stated in your BDD tests.
Environment automation. There are probably a lot of magical bits in your environment. Maybe your build process doesn't work unless this one particular directory is owned by this one particular user, or your CDN occasionally serves up old assets and you have to poke around in the Amazon Web Service dashboard to fix it. These weird workarounds are often simple, but you encounter them infrequently enough that no one remembers exactly what's happened or why. Do your brains a favor: automate all of this. Once it's written, it can be understood.
How to Create Misunderstanding
You can easily abuse all of the methods I just said, and actually use them to create misunderstanding.
A test creates misunderstanding if it depends on data that's changed by other tests. If your tests don't repeatably succeed, regardless of order, you're causing confusion.
Refactoring can create misunderstanding if you take well-understood code and change it dramatically, without also writing tests.
Documentation often causes more harm than good. Think about the nearest gigantic, outdated Word doc, or the comments in your code you fail to revise as you refactor. At some point, someone will read that and get confused.
Environment automation causes misunderstanding if it doesn't accurately reflect the state of your enviroment. Maybe you have some disaster recovery scripts lying around. Do they work, or would looking at them only give you misconceptions about the way your environment used to look?
Conclusion: Be Smarter.
Ultimately, software development is really, really hard. We have to think in terms from single bits to clusters of super-powered VMs. The best (only?) way to work effectively together and build great things is to constantly and collectively work towards a better understanding of our code.