Jonathan M Davis
Posted in reply to Walter Bright
| On Thursday, January 11, 2024 12:33:48 PM MST Walter Bright via Digitalmars-d wrote:
> On 1/11/2024 5:07 AM, deadalnix wrote:
> > These are just simple thing that I have on top of my mind, but there are a ton more.
>
> Thanks for taking the time to write this list. I would like to see all of your issues, though! Let's make a list and then have a bugzilla entry for each of them, so we can start picking them off.
>
> > such as @nogc, has been a productivity disaster
>
> I don't really understand this. Just don't use @nogc?
If he's talking about what I've discussed with him previously, the issue really relates to attributes in general rather than @nogc specifically. The big problem comes with refactoring code in a large codebase. The analogy that John Colvin came up with for the issue is that putting attributes on your codebase ends up being like putting concrete on it, making it incredibly difficult to refactor anything.
For instance, because function doX requires some set of attributes, everything it calls even indirectly then requires a compatible set of attributes. So, when you go to make a change to anything that doX ends up calling (even indirectly), you're then restricted by the (often arbitrary) attribute requirements that doX has. And of course, since you're dealing with a large codebase, it's not just one function that's the issue. It's a whole bunch of functions, many of which call each other. So, making changes that seem like they should be simple can then become incredibly difficult because of all of the compilation errors you get related to attributes - most of which you really don't care about in practice, but you need to make the compiler happy.
Attributes in general can be useful in a fairly restricted set of code, but once a lot of code is involved, they can become quite problematic - to the point that we're trying to rip out many of the attributes on the codebase I work on at Symmetry. And in some cases, the fact that some third party library developer decided that a function should require a specific set of attributes then becomes problematic when making code changes. Third party code that we could use just fine before suddenly becomes problematic, because we need to make a change to a type which in most languages would have been localized and encapsulated, but attributes cause the changes to cascade out all over the place and potentially make it so that we can't use third party code that worked just fine before. And even if the problems are in our own code, it becomes a time sink to figure out how to rework it all so that it works with the changed attributes.
So, for instance, a piece of code could require that the types and functions used with it be pure. The developer decided at the time that that's what they thought would make sense with what they were trying to do, and the code that was written at the time worked that way. But some time later, a change needs to happen in some other part of the codebase, and something that was pure before can no longer be pure. Suddenly, that part of the code that required pure doesn't compile anymore, and the change could be in a completely different part of the codebase to a type that just so happens to end up being used (maybe even indirectly) by the code requiring pure. So, suddenly, a bunch of code has to be refactored just because of an attribute that really doesn't do much in practice. Realistically, no optimizations were being done based on pure, and at best, they told the developers that nothing like global variables were being used (which realistically weren't being used anyway, because almost no one ever does that, because it's not maintainable). But because some piece of code was doing something simple that didn't work wtih pure but which would have been a perfectly fine and small change in C++, a bunch of code has to be changed. So, the attribute was likely adding zero value, and it just cost a ton of time to make changes because of it.
If @nogc had been used, the situation would have largely been the same. Some other attributes (like @safe) would also cause issues, but would be easier to solve, because there are backdoors. But too often, refactoring the code is then going to end up with stuff like @trusted being slapped on code just to shut the compiler up. And for the ones without easy backdoors, casts will sometimes end up being used to shut the compiler up - maybe with the idea that it's a temporary solution, but obviously, that kind of thing can end up sticking around long term. Depending on the situation and the attribute, it could then be a time bomb in the making, or it could actually be fine. It's certainly not great practice, but in practice, it's the sort of thing that the language's design encourages with larger codebases, because it sometimes becomes by far the simplest way to deal with a change in attributes.
Attributes in general simply don't seem to pay for themselves and come at too high a cost once the codebase gets large enough. They often place what are essentially arbitrary requirements on code which may have seemed reasonable to the developer working on that code at the time but which can become incredibly difficult to change later when you need to. And the benefits that they provide are often minimal in practice, making the whole thing that much worse.
I'm increasingly inclined to think that most attributes are primarily of theoretical benefit rather than of actual, practical benefit, and even if they are of practical benefit, if the codebase gets large enough, the way that they make refactoring difficult tends to make their cost too high to be worth it - and unfortunately, by the point that that becomes clear, you're already using them all over the place and wasting a ton of time because of decisions made months or years ago.
Often, attributes are used because they make sense for how a piece of code is currently written, and they work with that version of the code. But as the needs of the code change, the situation changes, and those attributes might not work anymore, which can ultimately cost a _lot_ of time or even make certain refactorings impossible, particularly as the amount of code involved grows. Really, that's the biggest problem here. We can discuss individual attributes and why they do or don't make sense (and @nogc is particularly bad), but to an extent, _all_ attributes are a problem here.
If we were to be designing D from scratch, I would be arguing very strongly that we should be much, _much_ pickier about the attributes that we have in the language (e.g. I would argue strongly against both pure and @nogc), but as it is, it's going to tend to mean that the advice for anyone working on a codebase of any significant size is going to be to minimize the list of attributes they use to those where there's going to be clear and obvious benefit and which will be much less likely to cause issues with refactoring later. Trying to use as many attributes as possibly (which many D developers think is best practice) really does seem to be like you're putting concrete on your code, making refactoring far, far harder than it would be in most languages - or if you'd just minimize the attributes that you're using.
- Jonathan M Davis
|