355 comments

[ 0.18 ms ] story [ 65.2 ms ] thread
[flagged]
That just ports all the problems to C...

You're better off incrementally rewriting it in modern C++. Replace those raw pointers with references or smart pointers!

All of that #define madness replace with templates and constexpr. I would also add if you can get it running on more than one compiler. G++ and MSvc and clang. Each one has its own subtle bits of errors it likes to throw out. Also get it running properly with something like valgrind. You are going to need it.
... but you only need a C compiler, not a grotesquely and absurdely massive and complex c++ compiler.

This is all wrong.

What, are you building the compiler yourself? :x It'll be way worse than those grotesquely and absurdly massive and complex compilers.
It's funny. My first step would be

  0. You reach out to the previous maintainers, visit them, buy them tea/beer and chat (eventually) about the codebase. Learned Wizards will teach you much.
But I didn't see that anywhere. I think the rest of the suggestions (like get it running across platform, get tests passing) are useful stress tests likely to lead you to robustness and understanding however.

But I'd def be going for that sweeet, sweet low-hangin' fruit of just talking to the ol' folks who came that way before. Haha :)

IME, this only works if you can get regular help from them. A one-off won't help much at all.
Yeah you need to cultivate those relationships. But with a willing partner that first session will take you from 0 to 1 :)
A lot of the time it takes you from 0 to 0.1, but (a) every little helps (b) if you take them out for lunch or a post-work beer or whatever it can build a relationship where you can ask follow up questions via email.

Ideal is if they have a life such that something morally equivalent to "how about we meet up and I'll pay for the food/beer" is a viable thing to suggest later.

Oh, and always remember - the way to a geek's schedule is often through their wife. If you get a chance to meet their partner, TAKE IT and be on your best behaviour, if $partner likes you then you have massively increased chances of making useful things happen later.

Yeah, that's a good point. But I think more often it's closer to 1 because (I guess this depends on our definition of 0 and 1 haha! :)) a) you get the benefit of their conceptual models for thinking about the codebase, which is hard won and highly useful, and b) you grok any practical pitfalls or things to remember when running, building, testing, that may be simple enough, but in the space of all possibilities are hard to come by without knowing them.

So basically you get to go from: 0 - can do nothing at all; to 1 - being able to feel confident diving in and looking around, framed with the downloaded conceptual models, knowing how to hit the ground trotting, if not running.

To me, 0.1 is more like you slugged it out for a couple hours poking around by yourself, and now you know a handful of confusing things with low confidence.

Chatting to one who has been there, done that lets you have more confidence, and know more actionable stuff, which is highly useful. I suppose it depends on your conversation, listening and comprehension skills tho! Hahaha! :)

But seriously not everybody learns well in that situation. I just happen to. I'd much rather talk it over with someone and absorb, than watch a video or read a tutorial.

Finally -- haha! :) -- I guess there are some geeks to whose schedule the way is through their husband or whatevs haha! :)

I've always found discussing why former employees left a project incredibly enlightening. They will usually explain the reality behind the PR given they are no longer involved in the politics. Most importantly they will often tell you your best case future with a firm.

Normally, employment agreements specifically restrict contact with former staff, or discussions of sensitive matters like compensation packages.

C++ is like any other language, in that it will often take 3 times longer to understand something than re-implement the same. If you are lucky, than everything is a minimal API lib, and you get repetitive examples of the use cases... but the cooperative OSS breadcrumb model almost never happens in commercial shops...

Legacy code bases can be hell to work with, as you end up with the responsibility for 14 years of IT kludges. Also, the opinions from entrenched lamers on what productivity means will be painful at first.

Usually, with C++ it can become its own project specific language variant (STL or Boost may help wrangle the chaos).

You have my sympathy, but no checklist can help with naive design inertia. Have a wonderful day. =)

Uh, yeah, that's a great idea, too! That's the big question. What happened to the previous team? Can give you org insights as well as code ones. Info on company strategy, priorities, work cadence. Actually a pretty good open ended conversation opener! Hahaha! :)
Note that depending on your jurisdiction discussion of compensation may be a right protected by law.
Contract law has weird consequences in different places.

Indeed, if the legal encumbrance is not legal, than its often unenforceable.

Talking with your own lawyers before doing something silly is a good habit. ;-)

> A one-off won't help much at all.

Monumentally disagree. One-off session with a guy who knows the codebase inside out can save you days of research work. Plus telling you all about the problematic/historical areas.

I'm just stating my experience. A single day, if they still have access to the codebase might be able to clear up some top-level concepts.

But the devil is in all the tiny details. What is this tiny correction factor that was added 20 years ago? Why was this value cut off to X decimals? Why didn't they just do Y here? Why do we override this default behavior?

It's tens of thousands of tiny questions like that which you can't ask until you're there.

I don't understand what you're saying. Clearly both types of meetings (one-off vs recurring) would be helpful. The one-off may save you days/weeks of research, but it seems like you're not satisfied with that unless you can answer every single minor question you might have across the entire codebase.
I'm saying that if you're maintaining a code base for years, a single day's explanations won't do much of anything. It's a drop in the bucket.

It's not a bad thing, and it's certainly good to do, but it's not a solution to the problem.

If your granularity for a task is measured in years then you have a much different and harder problem.

Effectively everything becomes a "drop in the bucket".

Having a one day intro might save you two weeks of the 6 - 36 month task of getting to know the code base.

Like ... it doesn't help that much. Mainly it saves some frustration of the build system is insane or the source code is spread out in mails, a hd and a floppy in a drawer or the current source state is broken and need to be reverted.

But if the original author is there to do a handover, the chances are the company is properly run and the work will be a breeze anyway becouse the code is good and well structured.

We'll just have to accept that we live in different worlds. I still consider what you described a massive save.
Exactly. Someone to guide you in the right path. Gonna help a lot! Hahaha :)
Easier said than done

My step 0 would be: run it through an UML tool to get a class diagram and other diagrams.

This will help you a lot.

> Get the tests passing on your machine

Tests? On a C++ codebase? I like your optimism, rs

No one has time to maintain the UML in production.

You may luck out with auto-generated documentation, but few use these tools properly (javadoc or doxygen). =)

The GP said nothing about keeping and maintaining it, only generating it. Use it to understand the codebase, then archive it or throw it out.
Exactly. You inherited 500k SLOC of C++ that grew together since 1985. You don't know the interconnections between the classes that have accumulated in that time. It was also developed by multiple teams, and likely had very different approaches to OO during these past nearly 40 years. The UML diagrams won't tell you everything, but they will tell you things like the inheritance hierarchy (if this was a 1990s C++ project it's probably pretty nasty), what classes are referenced by others via member variables, etc. This can be hugely informative when you want to transform a program into something saner.
I always interpreted most polymorphism as sloppy context-specific state-machine embedding, and fundamentally an unmaintainable abomination from OOP paradigms.

OO requires a lot of planning to get right (again, no shop will give your team time to do this properly), and in practice it usually degenerates into spiral development rather quickly (<2 years). Thus, 14 years later what you think you see in documentation may be completely different from the conditional recursive definition some clown left for your team (yes, it happens eventually...)

500k lines is not that bad if most of it is encapsulated libraries... =)

I wouldn't make it the first step. If you do, you will probably waste their time more than anything.

Try to work on it a little bit first, and once you get stuck in various places, now you can talk to the previous maintainers, it will be much more productive. They will also appreciate the effort.

There’s a fine balance with no right or wrong answer. Previous maintainers will appreciate if you spent literally more than a second trying to understand before you reach out to them, but for your own sanity you should know when it’s time to stop and call for help.
Yeah, that's fair enough. I guess since we're already zero-indexed maybe my -1 step is Prep. Hahaha! :)
While this sounds good, it sort of assumes you're trying to solve some puzzle, and you're going to them for hints and answer. That's not the problem at hand tho: we're not asking them to solve some puzzle for us, we're just seeking understanding.

Because what we're actually doing is building capability to be able to work on a wide range of problems on our own. And getting guided by people who already know there way around the codebase and know what everything does is going to get you there quicker than any other method (unless docs are truly excellent, which in most cases they're not).

So, after reflecting on it more, I disagree. I think assuming you can poke around for yourself, and start to build up a picture of what things do, may end up giving you confident delusions, that could be then hard to unlearn, or at least blockers to further understanding. Better to go to the old guard with a blank slate mindset, and let them fill you in on the overview.

Then go away and work on it, having that guidance in store, and then (if you were friendly enough in the first meet) come back for additional. That is what I think people would truly appreciate.

Think about it: assume you were someone who generally had time to help people coming to you for assistance, would you not love to sit someone down and give them an in depth download of the big picture, and details overview, of this thing into which you poured great love and devotion (or at least, many hours, sweat and dollars?). I think you would.

Anyway, it's a reminder to me that what often sounds right in the moment, and is easy to agree with, actually turns out to not be the best idea. I'm always eager to see another side, and empathize with someone else's view, but the lesson for me is: I often take that too far, and in doing so, forget what I originally came in with, forget my own frame, and forget what's true, so eager am I to see it from someone else's point of view. So that's a big lesson for me. Thanks for being part of it! Haha! :)

Maybe do a quick look at codebase first so you can identify biggest WTF's and ask about them.

After all, if you have inherited a codebase with no tests, with build process which fails every other time, with unknown dependency info, and which can only be built on a single server with severely outdated OS... are you sure the previous maintainer is a real wizard and all the problems are result of not enough time? Or are they a "wizard" who keep things broken for job security and/or because they don't want to learn new things?

Yes! Good idea. Locking in at -1. Hahah! :)
Even if that person is not a great archwizard, they still have more experience in that project than you and you will probably learn some things that will make your life less miserable, because you will better understand what to expect and what kind of failed solutions have been tried before.
> It's funny. My first step would be

> 0. You reach out to the previous maintainers, visit them, buy them tea/beer and chat (eventually) about the codebase. Learned Wizards will teach you much.

Have you ever tried that? This is legacy code. Even if the handover was yesterday, they cannot tell you about anything useful they did more than 6m in the past.

And that's the best-case scenario. The common-case is "That's the way I got it when it was handed over to me" answer to every question you ask.

I have, and assuming your predecessor doesn't mind, you still get enough useful answers to make it worth it a lot of the time.

Especially if you manage to get to just chatting about the project - at some point they'll almost certainly go "oh! while I remember," followed by the answer to a question you didn't realise you should've asked.

The value is often in the commiseration rather than the interrogation, basically.

    > You reach out to the previous maintainers, visit them
I could have brought them flowers, and shared a moment of silence contemplating eternity. I don't know if it would significantly have helped understanding the code base though..
You mean the guys the company laid off last week?
Ouch, yeah. I appreciate your topicality with this comment given the recent saga of tech layoffs. Much luck to you if you're in that experience right now!! Say you got fired form a S.Eng job at 350K yearly and some young grunt on 165K slid into your DMs and wanted to meet to chat. What would make you agree to that? I think it's unlikely, but perhaps if they reflected to you all the things you already thought were stupid about that company, and that you suffered with. You would want to help them as in their suffering, you could see your own, despite getting laid off. I think in that situation, there's a chance you might actually go over the codebase with them, in a conversation that mixed 1 part that, with 1 part self-commiserating and complaining/airing/venting about the org.

What at first seems an unlikely pairing, I think, given the right "relational config" in moment could actually end up working pretty well. Seems there's plenty opportunities for such a coupling to have many fruitful outputs for both parties.

> What would make you agree to that?

Money. Charging the higher end of typical consulting rates. That's your competition.

It's OK to want to help the young engineers, it doesn't mean that you shouldn't charge the company while you are at it. It would be foolish to do it for free, you effectively wouldn't do it for free even if you still worked there.

I was once tasked with deploying a piece of software on a closed network (military), to run on a old custom OS - it wasn't a huge program, around 50k lines of code.

I did encounter a bunch of bugs and problems underway, and wanted to reach out to the devs that wrote it - as it was customer made for my employer.

Welp, turns out it was written by one guy/contractor, and that he had passed away a couple of years earlier.

At least in the defense industry you'll find these sort of things all the time. Lots of custom/one-off stuff, made for very specific systems. Especially on the hardware side it is not uncommon that the engineers that made the equipment are either long gone or retired.

Welp, you might have needed to consult a seance for that one.

Interesting point about the "author expiry" window. I was thinking about that today regarding something else:

Let's say in 20 years time, no database code has been updated for the last 20 years. And all the people who worked on it, can't remember anything about it. Yet, it still works.

That means that everyone who uses that database everyday, doesn't know how it works. They believe that it works, this belief is widespread. And the database providing results to queries, is a real thing. And it does work -- but nobody knows how.

This is common. I don't know in any detail how the MacBook I use works. But it does. I don't know how many things I use actually work. But they do work.

It seems the only difference, in the world of things that "work", and which most people who use them do not understand how they work, is that there are two classes of things: those things for which there is a widespread belief that they do work; and those things for which the belief that they work, is not widespread. But in either case, they work.

Even more common in the world of industrial automation.

Lots of old early-gen PLCs from the 70s/80s still ticking, with no documentation and the techs/engineers/companies long gone.

We worked on one such PLC, around 3 decades old at the point, and it came down to probing I/O, reverse engineering the functionality.

But at some point, if there hasn't been enough legacy support, there comes a time where people just have to bite the bullet and re-build a system from the ground up - and integrate it in parallel with the old system running, until it can be removed completely.

Too bad many of the old and forgotten systems are still running and integral, so they get put inside a glass cage with "DON'T TOUCH!" warning sticker.

create bindings and externalize function libraries for other languages, hope to your prefered deity nothing breaks
This adds additional problems. IE, Start replacing legacy C++ with Python, now debugging and following the flow of the code becomes very difficult.
If it is C++ I wouldn't think about python in most cases. Rust should come to mind. Ada, or D are other options you sometimes hear about.
Is it possible to integrate any of those while allowing seamless debugging? IE, step right from one into another? I've yet to see that happen.
If nothing else my C++ debugger will see the functions of everything in Rust, D, or ada - it might be a mangled name but generally I can figure them out. Once you step into python the debugger is going to see the python runtime functions and you need to dig into them to figure out what of your functions you are running.

I have yet to figure out how to get any language other than C++ into my system so I can't say how well it works in the real world. Then again I work on an embedded system with real time controls so I can rarely use a debugger since as soon as I hit a breakpoints all my must happen at time X functions fail to run and the whole system fails in a few ms.

A good read. We recently did "Rewrite in a memory safe language?" successfully. It was something that shouldn't have been written in C++ in the first place (it was never performance sensitive).
Would you mind sharing what language you used?
Probably not a project spanning more than 3 decades of development and millions of lines of code?
Do you have a public write-up (blog post)? If yes, you should post it on HN. It would probably generate lots of interesting conversation.
I'd swap 2 and 3. Getting CI, linting, auto-formatting, etc. going is a higher priority than tearing things out. Why? Because you don't know what to tear out yet or even the consequence of tearing them out. Linting (and other static analysis tools) also give you a lot of insight into where the program needs work.

Things that get flagged by a static analysis tool (today) will often be areas where you can tear out entire functions and maybe even classes and files because they'll be a re-creation of STL concepts. Like homegrown iterator libraries (with subtle problems) that can be replaced with the STL algorithms library, or homegrown smart pointers that can just be replaced with actual smart pointers, or replacing the C string functions with C++'s on string class (and related classes) and functions/methods.

But you won't see that as easily until you start scanning the code. And you won't be able to evaluate the consequences until you push towards more rapid test builds (at least) if not deployment.

On the flip side, auto-formatting will trash your version history and impede analysis of "when and why was this line added".
You can ignore commits from git blame by adding them to a .gitattributes file.

This is assuming Git of course, which is not a given at all for the average legacy c++ codebase.

Good to know. Thanks for the tip!
I'm not hardcore on auto-formatters, but I think their impact on code history is negligible in the case of every legacy system I've worked on. The code history just isn't there. These aren't projects that used git until recently (if at all). Before that they used something else, but when they transitioned they didn't preserve the history. And that's if they used any version control system. I've tried to help teams whose idea of version control was emailing someone (they termed them "QA/CM") to make a read-only backup of the source directory every few months (usually at a critical review period in the project, so a lot of code was changed between these snapshots).

That said, sure, skip them if you're worried about the history getting messed up or use them more selectively.

SVN was a thing by the mid-2000's, and history from that is easy to preserve in git. Just how old are the sourcebases in question? (Not to shoot the messenger; just like, wow.)

edit:typo

The first large C++ project I worked on in mid-1990s was basically preserving a bunch of archived copies of the source tree. CVS was a thing but not on Windows, and SourceSafe was creating more problems than it been solving.
I kept regular tarballs of a project that used SourceSafe right near the start of my career, and found I was more likely to be able to find an intact copy of the right thing to diff against from my tarballs.

I think after a year or so I realised that even bothering to -try- to use SourceSafe was largely silly, got permission to stop, and installed a CVS server on a dev box for my own use.

(yes I know the VCS server shouldn't really be on the dev box I could potentially trash, I didn't have another machine handy and it was still a vast improvement)

I've heard a lot of stories about mid-90s codebases for sure
Some of these systems dated back to the 1970s. The worst offenders were from the 1980s and 1990s though.

It's all about the team or organization and their laziness or non-laziness.

I looked at a C++ codebase from 1997 at a previous job - I don't know much about the history but comments in one of the old files tracked dates and changes to 2001. Not sure what happened after that but in 2017 someone copy-pasted the project from TFS to git and obliterated the prior history.
I've had issues doing decent copies from SVN to GIT. They both have different ideas about user identity, and how fragmented it can be.
I maintain a C++ codebase that was originally written in 1996, and is mission critical for my organization. Originally maintained in Visual Sourcesafe, then in TFS source control, and now git. Some parts of it were rewritten (several times) in C#, but the core is still C++.

I was very worried when we transitioned to git that history will not be preserved and tried to preserve it, but it proved too much hassle so I dropped it.

In fact that proved not to be a problem. Well, not a problem for me, since I remember all the history of the code and all the half forgotten half baked features and why they are there. But if I'm gone then yes, it's going to be a problem. It's in a dire need for a rewrite, but this has been postponed again and again.

>I think their impact on code history is negligible in the case of every legacy system I've worked on. The code history just isn't there.

Not sure if I agree here or not - whilst yes, the history isn't there, if it's a small enough team you'll have a good guess at who wrote it.

Definitely found I've learnt the style of colleages so know who to ask just from the code outline.

Legacy systems that you inherit don't have people coming with them very often. That's part of the context of this. You often don't have people to trace it back to or at least not the people who actually wrote it (maybe someone who worked with them before they got laid off a decade ago), and reformatting the code is not going to make it any harder to get answers from people who aren't there.
I've been in situations where even without access to the people knowing which of them wrote something gives me a better idea of how to backwards infer what (and of course sadly occasionally 'if') they were thinking while writing the code.

Then again, I think most of the tells for that for me are around the sort of structure that would survive reformatting anyway.

(and, y'know, legacy stuff, everything's a bloody trade-off)

On per file level it's just 1 commit. It's not really a big deal
clang-format can be applied to new changes only, for this very reason.

Adding it will remove white space nitpicking from code review, even if it isn't perfect.

I believe you can configure `git blame` to skip a specific commit. But in my experience it doesn't matter anyway for two reasons:

1. You're going to reformat it eventually anyway. You're just delaying things. The best time to plant a tree, etc.

2. If it's an old codebase and you're trying to understand some bit of code you're almost always going to have to walk through about 5 commits to get to the original one anyway. One extra formatting commit doesn't really make any difference.

You can instruct git to ignore specific commits for blame and diff commands.

See "git blame ignore revs file".

Intended use is exactly to ignore bulk changes like auto formatting.

This is another reason why you should track important information in comments alongside the code instead of trusting VCS to preserve it in logs/commit messages, and to reject weird code missing comments from being merged.

Not saying that fixes decades of cruft because you shouldn't change files without good reason and non-white space formatting is not a good reason, but I'm mentioning it because I've seen people naively belief bullshit like "code is self explanatory" and "the reason is in the commit message"

Just comment your code folks, this becomes less of a problem

How does reformatting trash the history? It's one extra commit..

I guess if it splits or combines lines that could cause some noise if you really want the history of a single line... But that happens all the time, and I don't see how it would really prevent understanding the history. You can always do a blame on a range of lines.

Maybe I'm missing something though, genuinely curious for a concrete example where reformatting makes it hard to understand history!

If you ask the IDE to show blame info next to each line, then a lot of lines will be from the big reformatting. If course you can dig in and retrieve the history still, but it's an extras step then. Btw, it seems that at least Git has a way to make `git blame` avoid considering certain commits (.Git attributes). Maybe that works in IDEs too!
Nit: The post scopes "tearing things out" to dead code as guided by compiler warnings and unsupported architectures.

If going the route, I'd recommend commenting out the lines rather than removing them outright to simplify the diffs at least until you're ready to squash and merge the branch.

Better to use `#if` or `#ifdef` to prevent compilation. C & C++ don't support nested comments, so you can end up with existing comments in the code ending the comment block.
I think `#if` and `#ifdef` are not good ideas because they prevent the compiler from seeing them in the first place. A better solution is just `if (false)` which is nestable, and the code is still checked by the compiler so it won't bit rot.
CI is different from the others, here! At minimum, building a "happy path(s)" test harness that can run with replicable results, and will run on every one of your commits, is a first step, and also helps to understand the codebase.

And you're jumping around - and you'll have to! - odds are you'll have a bunch of things changed locally, and might accidentally create a commit that doesn't separate out one concern from another. CI will be a godsend at that point.

Yeah, I've done a fair bit of agency work dropping in to rescue code bases, and the first thing I do is run unit tests and check coverage. I add basic smoke tests anywhere they're missing. This actually speeds me up, rather than slowing me down, because once I have reasonably good coverage I can move dramatically faster when refactoring. It's a small investment that pays off.
I would absolutely not recommend auto-formatting a legacy codebase. In my experience large C++ projects tend to have not only code generation scripts (python/perl/whatever) but also scripts that parse the code (usually to gather data for code generation). Auto formatting might break that. I have even seen some really cursed projects where the _users_ parsed public header files with rather fragile scripts.
I was listing the items in the original article's #3 and saying I'd move them up to #2 before I'd go about excising portions of the project, the original #2. I still stand by that. But you can read my other comment where I don't really defend auto-formatting to see that I don't care either way. I made it about four hours ago so maybe you missed it if you didn't refresh the page in the last few hours.
This is pretty great advice for any legacy code project. Even outside of C++ there is a huge amount of code bases out there that do not compile/run on a dev machine without tons of work. I once worked on a Java project that due to some weird dependencies, the dev mode was to run a junit test which started spring and went into an infinite loop. Getting a standard run to work helped a ton.
The difference between greenfield and legacy code is just a few years. So learn to work with legacy code and how to make it better over time.
Start grinding leetcode and find another gig?
> Rewrite in a memory safe language?

like c++11 and later?

How is that memory safe? Even vector out of bounds index is not memory safe.
You can access a vector with a function that throws an exception if you so desire
You can also just write no code at all if you so desire. It certainly won't cause any memory issues that way. (Hint: What you yourself decide to write or refrain from writing is not the problem. You're not they only person who ever worked on this legacy codebase, and you want guarantees but default, not having to check every line of code in the entire project.)
no you just have to write a githook with some static analysis, like literally everyone who does proper c++. Safety hasn't been an issue in c++ for more than a decade. It's just a made up thing by people who don't use the language but only want to hate.
Go look at the CVEs and github issues of modern C++ codebases. Your statement is nonsense. Chromium is still plagued by use after free. How high do you set the bar? Which codebases are we allowed to look at?
I had that exact discussion with someone else a while ago. And when you actually go through the chromium memory bugs, it's 100% avoidable with an absolute baseline of competence and not using ancient bugs. It's unfair that C++ always has to compete in its state from 1990s against languages in their current iteration.
That's why I asked what the bar was? If Google is writing shitty C++ even with the world's eyes on their code base, who is doing it right? No one writing anything sufficiently complicated that's for sure.
However you feel about this issue: It's pretty widely known that google is bad at c++. Most codebases will be of significantly higher quality.
Google chrome must be one of the most used (in terms of CPU time) C++ software in the world right now. That means it's been fuzz tested (by the developers as well as by the users and also the random websites that gives it garbage html and javascript) extensively. I can only think of the Linux kernel that is more widely used, and Linux is not C++.

Since you seem to be very good at c++, can you point to a "significantly higher quality" c++ projects please? I'd like to see what it looks like.

> Since you seem to be very good at c++, can you point to a "significantly higher quality" c++ projects please?

Not the parent commenter, but there are quite a few very high-quality C++ projects out there.

- LLVM

- KDE projects

- CMake

- Node.js

- OpenJDK

I don't think popular or large correlate with code quality. In fact it's probably the opposite. It uses pretty ancient c++ stuff, which immediately disqualifies it from bring of high quality in regards to the cpp code (and also is the cause for their security bugs)
>It's pretty widely known that google is bad at c++

No, it's not. This is literally the first time I hear about this. Do you have any sources to support your claim, especially in light of the fact that Google has written and contributed to some of the largest and most important C++ codebases in existence?

It's funny; I spent a couple of hours last week helping some students debug out-of-bounds indices in their Rust code.

I've written bugs that would have been caught by the compiler in a memory-safe language. I think the last time was maybe in 2012 or 2013? I still write plenty of bugs today but they're almost all logic errors that nothing (short of AI tools) could have caught.

vector.at() is memory safe. You get a choice. Easy to ban [] if you cannot statically prove it is safe.

C++11 isn't the most memory safe language, but C++11 is a lot safer than older versions, and C++23 is better yet. I'm expecting true memory safety in C++26 (time will tell), but it will be opt-in profiles which isn't ideal.

This is not idiomatic C++ in any C++ standard. You can also just replace vector with map so the brackets insert, but that isn't either.
Prefer at to [] is standard where I write C++. map is not standard because vector is almost always much faster random access in the real world (that is n is normally small enough that a linear search is faster than a binary search because of caching)
Oops, shared_ptr circular reference.

Oops, null smart pointer.

Oops, UB made my reference null.

Oops, invalidated iterator.

Oops, aliased pointers.

Oops, race condition.

Oops, recursion.

Oops, moved-from object is partially formed.

rm -r

Problem solved

> C++

> Not even once

(comment deleted)
If you mean "rewrite from scratch", believe me, it is the worst thing you can do. I speak from experience, it is tempting but the few times I have done that, a few months later as I get burnt, I could only think of how an idiot who never learn I was.

Legacy code is like that because it went through many bugfixes and addressing weird requirements. Start over and you lose all that history, and it is bound to repeat itself. That weird feature that makes no sense, as it turns out, makes a lot of sense for some users, and that why it has been implemented in the first place. And customers don't care about your new architecture and fancy languages, they want their feature back, otherwise they won't pay.

Another way to look at it is when you asked to maintain a legacy code base, that's because that's software that has been in use for a long time. If it was that bad, it would have been dropped long ago, or maybe even cancelled before it got any use. Respect software that is used in production, many don't reach that stage.

Of course there are exceptions to that rule, but the general idea about rewriting from scratch is: "no" means "no", "maybe" means "no", and "yes" means "maybe".

I'm being like 1000% facetious, and agree that rewrites are bad.
I have been involved in a successful rewrite. It cost billions of dollars and many years when the code wasn't working so the old system was still in use. We also ended up bringing over some old code directly just to get something - anything - functional at all. For many years my boss kept the old version running on his desk because when there was a question that old system was the requirements.

Today we only have to maintain the new system (the old is no longer sold/supported), and the code is a lot better than the old one. However I suspect we could have refactored the old system in place for less time/money and been shipping all the time. Now we have a new system and it works great - but we already have had to do significant refactors because some new requirement came along that didn't fit our nice architecture.

> because some new requirement came along that didn't fit our nice architecture.

This is the thing.

I was doing the event section of the website and it was the event. I mean, it was preposterous to even think of running multiple events. Fast forward a few years, the company is now many times the size after very rapid growth, has an office in the UK and now runs multiple events. Would you have made an architecture for multiple events back then? YAGNI whispers you not to...

When considering this question, I've designed for things knowing they would come and they never did. I've also designed for situations that did come, but 10 years later we discovered a significant downside that we didn't anticipate and so the seemingly non-elegant hacks would have been better.
>worry not, by adding std::cmake to the standard library and you’ll see how it’s absolutely a game changer

I'm pretty sure my stomach did somersaults on that.

But as for the advice:

>Get out the chainsaw and rip out everything that’s not absolutely required to provide the features your company/open source project is advertising and selling

I hear you, but this is incredibly dangerous. Might as well take that chainsaw to yourself if you want to try this.

It's dangerous for multiple reasons. Mainly it's a case of Chesterton's fence. Unless you fully understand why X was in the software and fully understand how the current version of the software is used, you cannot remove it. A worst case scenario would be that maybe a month or so later you make a release and the users find out an important feature is subtly broken. You'll spend days trying to track down exactly how it broke.

>Make the project enter the 21st century by adding CI, linters, fuzzing, auto-formatting, etc

It's a nice idea, but it's hard to do. One person is using VIM, another is using emacs, another is using QTCreator, another primarily edits in VSCode.. Trying to get everyone on the same page about all this is very, very hard.

If it's an optional step that requires that they install something new (like commit hook) it's just not going to happen.

Linters also won't do you any good when you open the project and 2000+ warnings appear.

An optional step locally like pre-commit hooks should be backed up by a required step in the CI. In other words: the ability to run tests locally, lint, fuzz, format, verify Yaml format, check for missing EOF new lines, etc, should exist to help a developer prevent a CI failure before they push.

As far as linters causing thousands of warnings to appear on opening the project, the developer adding the linter should make sure that the linter returns no warnings before they merge that change. This can be accomplished by disabling the linter for some warnings, some files, making some fixes, or some combination of the above.

>> It's a nice idea, but it's hard to do. One person is using VIM, another is using emacs, another is using QTCreator, another primarily edits in VSCode.. Trying to get everyone on the same page about all this is very, very hard.

This is what's wrong with our industry, and it's no longer an acceptable answer. We're supposed to be fucking professional, and if a job needs to build a tool chain from the IDE up we need to learn to use it and live with it.

Built on my machine, with my IDE, the way I like it and it works is not software. It's arts and fucking crafts.

If you're saying everyone should agree on the same IDE and personal development toolset, I disagree, sort of.

The GP was suggesting the effort to add CI, linters, fuzzing, auto-formatting, etc was too hard. If that can be abandoned entirely, perhaps the legacy codebase isn't providing enough value, and the effort to maintain it would be better spent replacing it. But the implication is that the value outweighs the costs.

Put all the linters, fuzzing, and format checking in an automated build toolchain. Allow individuals to work how they want, except they can't break the build. Usually this will reign in the edge cases using inadequate tools. The "built on my machine, with my IDE, the way I like it and it works" is no longer the arbiter of correct, but neither does the organization have to deal with the endless yak shaving over brace style and tool choice.

> neither does the organization have to deal with the endless yak shaving over brace style and tool choice

I hear you, but an organization that fears this, instead of Just Pick Something And Deal With It, is an organization that probably doesn't have the right people in it to succeed at any task more arduous than that.

Conversely, and organization that imposes arbitrary choices and isn't capable of allowing people do use the tools they know best probably doesn't attract the best people. There are many different kinds of hammers, and making everyone who uses hammers use the same kind is, to say the least, counter productive.
I get where you're coming from, but frankly: nah. If you are so in-the-rut that you can't switch, say, text editors or IDEs and are compelled to have an Incredibly Normal Day about it, you're probably not actually possessed of the plasticity to be somebody I want to work with.

I use vim, IntelliJ, Visual Studio, and VSCode at least once every two weeks apiece, and it's no skin off my back to switch. Do thou likewise.

I absolutely agree with this. The plasticity is valuable, but everyone has a productivity sweet spot, and developers tend to go where they feel most productive and rewarded for that productivity.
I don't imagine the best people crying at being forced to use VSCode instead of CLion.
> every single carpenter in the world should use the exact same make and model of saw, for, uh, professionalism reasons
One person's saw literally triple checks the measurements before cutting, minimizes wastage, runs 3x faster, and is built by a company specializing in making saws.

The other saw was hand forged in a basement by the user, breaks every other day, and has a totally different blade width, and can only be used by the owner.

Picture the crew that shows up to stick frame your house.

First guy: hand saw and impact driver... cut and screw Second guy: Power Saw, and hammer. Cut and nail. Third guy: Safety glasses and a Nail gun. Forth guy shows up: compressor, asks where the power is (none) and if he can use some tools.

It would not work. You dont build a CNC production line for parts with every CNC being unique. We dont let devs pick what their production server OS is, we dont let them choose random languages. Tooling matters.

(comment deleted)
> It's a nice idea, but it's hard to do. One person is using VIM...

The things the author listed there are commonly not IDE integrated. I've never seen a C++ development environment where cpplint/clang-tidy and fuzzers are IDE integrated, they're too slow to run automatically on keystrokes. Auto-formatting is the only one that is sometimes integrated. All of this stuff you can do from the command line without caring about each user's chosen development environment. You should definitely at least try rather than giving up before you start just because you have two different text editors in use. This is C++; if your team won't install any tools, you're gonna have a bad time. Consider containerizing the tools so it's easier.

Clangd will happily run clang-tidy as part of completion/compile-as-you-type/refactor/auto independent.

On the editor/IDE of your choose.

I wouldn't call it fast, but it is quite usable.

> I've never seen a C++ development environment where cpplint/clang-tidy and fuzzers are IDE integrated

CLion from JetBrains has clang-tidy integrated (real-time).

I assume it's clangd? It can be used from vim, vscode, ... etc as well and get a uniform IDE diagnostic experience across text editors.
In Emacs I have clangd and clang-tidy running on each key stroke!

The project size is probably a lot smaller than what most people are working on though, and I have a fast CPU and NVME disk, but it's definitely possible to do!

I'm not sure about the fuzzer part though.

> One person is using VIM, ...

I don't get your point. You know you can autoformat outside editors right? Just configure pre-commit and run it in CI. It's trivial.

> If it's an optional step that requires that they install something new (like commit hook) it's just not going to happen.

It will because if they don't then their PRs will fail CI.

This is really basic stuff, but knowledge of how to do CI and infra right does seem to vary massively.

> It's a nice idea, but it's hard to do. One person is using VIM, another is using emacs, another is using QTCreator, another primarily edits in VSCode.. Trying to get everyone on the same page about all this is very, very hard.

I must have missed the memo where I could just say no to basic things my boss requires of me. You know, the guy that pays my salary.

As others have mentioned, none of these things actually change your development workflow. But if they did, you do have the ability to say no. If your boss fails to understand that you have an environment that you're productive in, that sounds like a bad place to work.
Every company have their own workflow adapted to their tooling so that teams can work among themselves frictionless.

It's ok if you use your own tooling you are comfortable with, but you should adapt to their workflow, and the employer has no obligation to tweak their workflow to integrate your own, it's yours to adapt.

You can, in fact, just do that. You are allowed. You may get fired if you are too much of a headache, but employers are actually flexible.
> It's a nice idea, but it's hard to do. One person is using VIM, another is using emacs, another is using QTCreator, another primarily edits in VSCode.. Trying to get everyone on the same page about all this is very, very hard.

Bullshit, all of these (and additionally C lion) are fairly easy to configure these for, with the possible exception of QTCreator (not a ton of experience on my end).

Just make it a CI requirement, and let everyone figure it out for their own tools. If you can't figure that out, you get to run it as a shell script before you do your PRs. If you can't figure that out, you probably shouldn't be on a legacy C++ project.

> It's dangerous for multiple reasons. Mainly it's a case of Chesterton's fence. Unless you fully understand why X was in the software...

If this is a function that no one links to, and your project does not mess with manual dynamic linking (or the function is not exposed), then it's pretty safe to remove it. If it's internal utility which does not get packaged into final release package, it is likely be safe to remove too. If it's a program which does not compile because it requires Solaris STREAMS and your targets are Linux + MacOS - kill it with fire.

(Of course removing function calls, or removing functionality that in-use code depends on, is dangerous. But there is plenty of stuff which has no connection to main code)

Been there, done that. Don't be a code beauty queen. Make it compile and make it run on your machine. Study the basic control-flow graph starting from the entry point and see the relations between source files. Debug it with step-into and see how deep you go. Only then can you gradually start seeing the big picture and any potential improvements.
Absolutely. Read the code. Step through with a debugger. Fix obvious bugs. If it’s legacy and somebody is still paying to have it worked on, it must mostly work. Changing things for “cleanliness and modernization” is likely to break it.
> Fix obvious bugs.

Be careful about that. Hyrum's Law and all.

Should have been clearer. You’ve probably been put on the project because something isn’t working. Fix the simplest, most obvious of these. Fixing a bug is a good way to learn.
> You’ve probably been put on the project because something isn’t working.

Perhaps if it's a change requested by the organization or the users. Just don't go "fixing" things that look like bugs without knowing if it's really a bug or expected behavior.

In my experience it takes at least a year straight working with code before you can form an opinion on if it is beautiful or not. People who have not worked in a code base for that long do not understand what is a beautiful design corrupted by the real world vs what is ugly code. Most code started out with a beautiful design but the real world forced ugly on it - you might be able to improve this a little with full rewrite but the real world will still force a lot of ugly on you. However some code really is bad.
Is it worth getting more into C++ in 2024? Lots of interesting jobs in finance require it but it seems almost impossible to get hired without prior experience (with C++ and in finance).
Yes.

I switched from Python to C++ because Cython, Numba, etc. just weren't cutting it for my CPU-intensive research needs (program synthesis), and I've never looked back.

My question isn't whether it's a good fit for a specific project, I'm more interested in whether it's a good career choice e.g. can you get a job using C++ without C++ experience; how realistic is it to ramp up on it quickly; whether you're likely to end up with some gnarly legacy codebase as described in the OP; is it worth pursuing this direction at all.
Did you see yesterday's article about the White House Office of the National Cyber Director (ONCD) advising developers to dump C, C++, and other languages with memory-safety issues?
Yes, and at the same time I’m seeing ads for jobs that pay more than double what I make that require C++.
I still think knowing C++ is pretty valuable to someone's career (at least over the next 10 - 15 years) if they're looking to work in fields that traditionally use C++ but might be transitioning away from it.

The obvious comparison is Rust. There are way more C++ jobs out there than Rust jobs. And even if I'm hiring for a team developing something in Rust, I'd generally prefer candidates with similar C++ experience and a basic understanding of Rust over candidates with a strong knowledge of Rust and no domain experience. Modern C++ and Rust aren't _that_ dissimilar, and a lot of ideas and techniques carry over from C++ to Rust.

Even if the DoD recommends that contractors stop using C++ and tech / finance are moving away from it, I'd say we're still years away from the point where Rust catches up to C++ in terms of job opportunities. If your main goal is employment in a certain industry, you'll probably have an easier time getting your foot in the door with C++ than Rust. Both paths are viable but the Rust path would be much harder IMO.

We’re still in for another 20 years of hardcore veteran Cxx programmers insisting that either the memory safety issue is overblown or just a theoretical issue if you are experienced enough/use a new enough edition of the language.
The C++ committee is looking hard at how to make C++ memory safe. If you use modern C++ you are already reasonably memory safe - the trick is how do we force developers to not access raw memory (no new/malloc, use vector not arrays...). There are some things that seem like they will come soon.

Of course if you really need that non-memory safe stuff - which all your existing code does - then you can't take advantage of it. However you can migrate your C++ to modern C++ and add those features to your code. This is probably easier than migrating to something like Rust (Rust cannot work with C++ unless you stick with the C subset from what I can tell) since you can work in small chunks at a time in at least some situations.

C++ is really a language that you want to specialize in and cultivate years of deep expertise with, rather than having it as one tool in your belt like you can with other languages.

That's certainly a choice you can make, and modern C++ is generally a pretty good experience to work with. I would hope that there's not a ton of active C++ projects which are still mostly using the pre-2011 standard, but who knows.

This exactly. It’s a blessing and a curse, because I’d love to move to a “better” language like Rust or even Zig. But with 20+ years of C++ experience I feel like I’d be throwing away too much to avoid C++ completely. Also agreed that modern C++ is pretty decent. Lamenting that I’m back in a codebase that started before C++11 vs my previous job that was greenfield C++14/17.
I would be very surprised if most people actually choose to develop in C++. It's a very good language choice for many domains, and I suspect interest and expertise in those domains drives people to C++ more than a desire to program in C++.
Modern C++ is the language of choice for high-performance, high-scale data-intensive applications and will remain so for the foreseeable future. This is a class of application for which it is uniquely suited (C and Rust both have significant limitations in this domain). There are other domains like gaming that are also heavily into C++. Avoiding legacy C++ codebases is more about choosing where you work carefully.

It goes without saying that if you don't like the kinds of applications where C++ excels then it may not be a good career choice because it is not a general purpose language in practice.

> Modern C++ is the language of choice for high-performance, high-scale data-intensive applications

> C and Rust both have significant limitations in this domain

Rust? Please provide concrete examples. I don't believe it.

IME, c++ was easier to ramp up on than typescript. C++ still a lingua franca in many domains, e.g., robotics, games, finance.
Finance? No, most of it was rewritten in the 2000s to Java or DotNet. Sure, a bunch of HNers will reply here that they work on high frequency market making systems that use C++, but they are an extreme minority in the industry at this point.
Depends on the industry you are interested in entering.

My myopic view of the world has seen the general trend from C to C++ for realtime embedded applications. For example: in the Automotive Industry all the interesting automotive features are written in C++.

Great, now they can all inherit from “HopeIDontCrashToday”
Well, tomorrow is the "who's hiring?" thread...
I enjoyed the article and learned something. But I've been wondering: When people say "rewrite in a memory-safe language", what languages are they suggesting? Is this author rewriting parts in Go, Java, C#? Or is it just a smirky, plausibly deniable way of saying to rewrite it in Rust?
Author here, thanks! A second article will cover this, but the bottom line is that it entirely depends on the team and the constraints e.g. is a GC an option (then Go is a good option), is security the highest priority, etc.

I’d say that most C++ developers will generally have an easy time using Rust and will get equivalent performance.

But sometimes the project did not have a good reason to be in C++ in the first place and I’ve seen successful rewrites in Java for example.

Apple is rewriting some C++ code in Swift, etc. So, the language the team/company is comfortable with is a good rule of thumb.

Makes sense, thanks!
So you saw a post about C++, it didn’t mention “Rust” once, mentioned “memory safe” languages which there are dozens of, and yet found a way to shoehorn in a dismissive comment about a meme. Nice.

We’ve reached the rewrite-in-rust meme stage of questioning whether the author is a nefarious crypto-Rust programmer in lieu of not being able to complain about it (since it wasn’t brought up!).

(author actually shows up to advocate for rust)
Author actually replies that Go, Java, Rust, Swift are options depending on the context, and that general considerations like security are relevant as well. But whatever helps you grind your axe!
(comment deleted)
Whenever I inherited a project containing legacy code, regardless of the frameworks, tools, or languages used, we always found it necessary to drop it and begin anew. Despite my efforts to reuse, update, or refactor it, we inevitably reached a point where it was unusable for further development.
> Get out the chainsaw and rip out everything that’s not absolutely required to provide the features your company/open source project is advertising and selling

Great advice! People do not often think about the value of de-cluttering the codebase, especially _before_ a refactor.

Really liked it! Especially the "get buy in" is really good advice-- always stressing how the effort spent on refactoring actually improves things, and WHY its necessary.

Something that's kinda implied that I would really stress: Establish a "single source of truth" for any release/binary that reaches production/customers, before even touching ANY code (Ideally CI. And ideally builds are reproducible).

If you build from different machines/environments/toolchains, its only a matter of time before that in itself breaks something, and those kinds of problems can be really "interesting" to find (an obscure race condition that only occurs when using a newer compiler, etc.)

> 3. Make the project enter the 21st century by adding CI, linters, fuzzing, auto-formatting, etc

I would break this down:

a) CI - Ensure not just you can build this, but it can be built elsewhere too. This should prevent compile-based regressions.

b) Compiler warnings and static analysers - They are likely both smarter than you. When it says "warning, you're doing weird things with a pointer and it scares me", it's a good indication you should go check it out.

c) Unit testing - Set up a series of tests for important parts of the code to ensure it performs precisely the task you expect it to, all the way down to the low level. There's a really good chance it doesn't, and you need to understand why. Fixing something could cause something else to blow up as it was written around this bugged code. You also end up with a series of regression tests for the most important code.

n) Auto-formatting - Not a priority. You should adopt the same style as the original maintainer.

> 5. If you can, contemplate rewrite some parts in a memory safe language

The last step of an inherited C++ codebase is to rewrite it in a memory safe language? A few reasons why this probably won't work:

1. Getting resources to do additional work on something that isn't broken can be difficult.

2. Rather than just needing knowledge in C++, you now also need knowledge in an additional language too.

3. Your testing potentially becomes more complex.

4. Your project likely won't lend itself to being written in multiple languages, due to memory/performance constraints. It must be a significantly hard problem that you didn't just write it yourself.

5. You have chosen to inherit a legacy codebase rather than write something from scratch. It's an admittance that you don't have some resource (time/money/knowledge/etc) to do so.

> The last step of an inherited C++ codebase is to rewrite it in a memory safe language

Simply getting rid of any actually memory unsafe C++ and enforcing guidelines will do this for you in the C++ codebase.

"Rewrite it in X" only adds complexity because it's the flavour of the month as you said in your comment.

Author is already doing the work of rewriting large chunks of the codebase in C++, they may as well follow and implement a more restrictive subset of the language, I find High integrity C++ to be good. If I can get my hands on the latest MISRA standard that is likely good as well. These may not be "required" but they specify what is enforced in <enter "safe" language here>. So instead of having to reskill your entire devteam on a new language which has many many sharp edges, how about just having your dev team use the language they already know and enforce guidelines to avoid known footguns.

How would you get rid of any memory unsafe C++? Isn’t that just another way of saying “do not make mistakes”?
The same way you do it in rust, use wrappers for all memory allocation.

C++ has had RAII forever, since C++11(2024 btw now) have actually good wrappers, Box = std::unique_ptr, whatever the ref counter version is = std::shared_pte.

Do the other things he already said to do, ie clang-tidy with the correct rule will warn/error on any raw pointer usage. You don't need to "not make mistakes". If you never use raw pointers, other than these specific places you tell the linter that it was fine and you have checked then by default it will be memory safe. Does that sound familiar?

"But we don't need a linter in Rust!" It's just built into the LLVM frontend called the rust compiler. If it bugs you so much build a custom executable that runs the linter then the c++ compiler and call it your own internal compiler. First line of code can be "#!/bin/sh"...

If you say you aren't talking about Rust but one of the GC languages, then I agree with you, write it in that other language but then the correct solution is to rewrite the software in the first place since it was never written in the correct language to start with. Rewriting something in Rust is likely the same amount of complexity as fixing the C++ code, if you however first need to learn Rust well there's a reason I am not exactly pro rust ans yes I bave tried to use it in a proper complex project, all deadlines were missed and we ended up writing it in modern safe C++ instead purely because the language didn't force us to make up some obscure abstraction to appease the borrow checker.

Using smart pointers solves some problems, sure, but it does not make C++ memory safe.
This is generally the same path that LibreOffice followed. Works reasonably well.

We built our own find-dead-code tool, because the extant ones were imprecise, and boy oh boy did they find lots of dead stuff. And more dead stuff. And more dead stuff. Like peeling an onion, it went on for quite a while. But totally worth it in the end, made various improvements much easier.

The “rip everything out” step is not recommended. You will break things you don’t understand, invoke Chesterson’s Fence, and create enormous amounts of unnecessary work for yourself.

Make it compile, automate what you can, try not to poke the bear as much as you can, pray you can start strangling it by porting pieces to something else over time.

This was my job at Cisco. But it was a C code base, which used nonstandard compiler extensions, and so could not be built without the legacy compilers with their locally made extensions. Also the "unit tests" were actually hardware-in-the-loop tests. And the Makefiles referenced NFS filesystems automounted from global replicas, but none of them were on my continent.

Fun times. Don't work there anymore. Life is good. :)