Working at a company with 6 million+ lines of C++ COM code with an old UI (still in use) that was written in Delphi but was migrating to a new system (written using MFC....) with zero documentation other than a Wiki nobody updated, and 2 senior developers who weren't overly communicative and would be lumbered with fixing others' code because they didn't tell them how anything worked. Then the "junior" developers would leave because they kept getting berated for doing stuff wrong, and the cycle would start again with another developer who got the job. An eternal cycle of stressed, frustrated, bored (because they wouldn't be given interesting code to work on because they weren't trusted/"good" enough) developers, but 2 very busy senior developers.
The newer C++ code had a good idea in a function-based system for the UI but the functions were too generic and would accept parameters of any type, so a lot of the runtime code was working out if the object you were passed was of the right type. It was too flexible, and also meant the implementation team who would design the software for the users hadn't a clue what the parameters were meant to be.
The schema for the database was generated from their own textual file format that let you specify soft relationships instead of hard FK relationships. XML was nowhere to be seen. The parser for this format was convoluted and also very picky about the format of the schema file.
The schema would be translated into header files so that you could refer to fields in the database by an enum value, and then pass off reading/writing that field to the underlying database code so that developers (ideally) didn't have to write SQL. It was their own ORM system but it was quite simplistic and to do anything worthwhile you had to write your own SQL. The schema for the database was not published, nor did it use sensible names, so you'd have to learn this baffling schema and hope for the best. All transactions pending for the database were put into an object that transacted them in one, and they were under the illusion that this was their sole invention in the entire universe. Whilst a good idea, the singular nature of these transaction objects and self-contained business logic meant that when object A hit the database, it might load object B, C, D and E. Each of these might load F, G, H, I and J and so on, so you ended up with a colossal tree of SQL hitting the database. It was incredibly slow.
The database also used magic numbers to change the behaviour of the software. Even in the user interface that the customers used, they taught on training courses that to make it do X Y or Z, simply open the config editor (an Excel-like grid) and the number 5 into grid B25...! As if this was perfectly understandable or usable? It never ever struck them that it might be a good idea to put English words next to settings.
At runtime, the software loaded its schema in a C++ object that it had serialized to the database when the database was upgraded. Since the pointers inside this object had now changed, at runtime on loading it would hook up the pointers to point to the correct parts of this object. If the schema upgrade failed when running its SQL, the schema object might still get saved I think so you could end up with the physical database schema being X whilst the serialized object was Z, so at runtime it was haywire.