This was a looooooong time ago, so I can't remember the details of the specific tools. I even forgot the name of the SCM, it was some weird nonstandard one.
The codebase I was working on was Java, server and client.
I actually learned a lot from that job! I stayed just long enough to see how they do everything so I can then do the opposite in my career. I purposefully quit before I ran the risk of being infected by their attitude.
Random examples of the madness that went on in that place:
- They created a custom database engine in Java that used millions of tiny flat files to store the data. The reasoning was that Oracle was too expensive. They then had to license Oracle anyway because they couldn't generate the ad-hoc reports their customers wanted out of their own DB engine.
Lesson learned: Always use common, popular CoTS OLTP DBMS products. Don't make a DB engine unless the DB engine is your only product.
- No transaction capability in the custom DB. The logic for writing data to the DB was... hilarious.
Lesson learned: never try to "emulate" transactions. Always use the built-in transaction idioms of the underlying system.
- Deadlocks, live-locks, data corruption, and even thread leaks were the norm, not the exception. The server would only start up successfully 2 out of 3 times at best.
Lesson learned #1: Multi-threaded code is either proved to be correct mathematically, or it is Wrong with a capital W. There is zero wiggle room on this.
Lesson learned #2: Use the best quality[1] multi-threading libraries for their "primitives" such as bounded producer-consumer queues. Never try to hand roll these yourself for any reason, ever. Just don't.
Lesson learned #3: If you code is riddled with critical crash and correctness bugs, you'll never know if the change you just made has introduced a new bug.
- Only full compiles would work (by default).
Lesson learned: a 30 minute delay in the type-build-run-debug cycle is productivity-killing madness. This is the most important metric to optimise for anyone in charge of a dev team. Pretty much nothing matters more than this, because it multiplies out everything else. Buggy code? Now it's minimum 30 minutes to fix a bug. Slow code? Any optimisation had better be worth the 30 minute wait! Behind on features? 30 minutes minimum for the smallest change. Etc...
- Ignoring naming errors, such as functions being named as if it does one thing but it actually does something unrelated. Similarly, there were tons of typos in identifiers.
Lesson learned: IDEs with refactoring capability were invented for this. If you don't fix it, every newbie is going to trip up. Every time. Every. Single. Time. It will cost you more time eventually if you don't fix it, not less. Just. Fix. It.
- Not using debuggers. This one was the most hilarious for me. Developers would spend hours adding debug print statements to the code, while I would just create a breakpoint, run the code, and solve the problem immediately.
Lesson learned: programming is 10% typing, 90% debugging. If you use an editor like VI or Emacs to optimise your typing speed, you are optimising the 10%, not the 90%. You've failed fantastically badly at your job. You're not a typist. You're a correctness ensurer. Act like it.
- Testing is optional in the same way that tasting the food you cook is optional. I mean, sure... if you're really good you can make tasty food reliably. If.
Lesson learned: IDE-integrated test frameworks can save more time than they take to set up. At a minimum, test any "clever" code with every corner-case. Null and empty inputs. One input. Multiple inputs. Max/min inputs. Inputs expected to trigger errors. Etc...
- Reams of dead code left in the source. That dead code had changes made to it after it had become dead. Bug fixes. Renames. Cleanup. Consistency changes. On and on, and on. The staff were easily spending 30% of their time editing code that wasn't used.
Lesson learned: dead code is pure, unadulterated overhead at every step. Checkout? Wasted time. Build? More wasted time. Running tests? Time wasted. Refactoring? Extra work. Etc...
[1] At the time I used EDU.oswego.cs.dl.util.concurrent by Doug Lea, which was amazing. Several modern standard libraries are modelled on it, including Java's java.util.concurrent.