Discussion: Language release cadence #2518
Replies: 40 comments
-
Oh crap, that turned into a diatribe. Anyway, this is intended more as an open discussion. I'm not proposing anything specific. However, listening to Brian Goetz discuss this in detail recently left an impression on me. If the lumbering dinosaur that is Java can adopt a CD-like release cadence, and see real benefits from doing so then perhaps we can learn something from this. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Personally, I think numbering language versions based on features makes sense, because it makes it much easier to figure out what's going on. There are already way too many versions in .Net (.Net Core SDK 2.2.106, .Net Core Runtime 2.2.4, C# 7.3, Roslyn 2.10, MSBuild 15.9.20, VS 15.9.11), I'm not sure forcing incrementing the version of C#, seemingly independently of other parts of the ecosystem, would be a good idea.
How come Java's 2 releases per year are "quick and often", but C#'s 3 releases per year in 2017 are not? (Though 2018 had only one release and 2019 is probably not going to have more than 2 at this point.)
How is that different from what's currently happening, where VS 2019/Roslyn 3.0 contain a preview version of C# 8.0? |
Beta Was this translation helpful? Give feedback.
-
The features can still be tied to a version. They're just not tied to that specific version, nor is there an expectation that because a major version number may be incrementing that it has to be accompanied by something major.
Java's are consistent. C# had a burst and it's clogged up again, pending a major release. I expect that after C# 8.0 releases we might get a quick burst of minor releases, but the cycle would likely continue that once work really starts on the next major features that nothing new will be released. Java's train leaves the station on time, regardless of who is on it. C#'s train sits in the station until some combination of features come together that resembles something that can ship.
It's not C# 8.0, nor is it C# 7.3. It's a strange in-between with it's own release cadence and expectations. And while I would fully expect that the team would be willing to delay any of those features if something wrong shook out in the preview, the expectation is that those major features are going to be tied to that major release. The result of the preview isn't going to be C# 7.next, it's going to be C# 8.0, and that sets everyone's expectations, the users, the developers, the project managers, etc. |
Beta Was this translation helpful? Give feedback.
-
Looking at the .NET 5 blog post you'll see that .NET is moving to regular release cycles. That's not just the runtime, it's the suite of tools in the .NET SDK that are moving to this cycle. That includes the compiler. While I can't promise that every release of .NET will have a corresponding C# release it seems like that it will be the norm to do so and such releases will be major ones.
I expect that minor release will be heavily de-emphasized in this model but they're still a valuable tool for us to have around when needed. The way I view minor releases is that we can continue to use them when circumstances call for it. Such as the need to support a critical scenario that comes up in between major releases of .NET. That seems increasingly unlikely with the rapid release plan of .NET. But if it did happen I'd feel fine using a minor release to deliver the feature. |
Beta Was this translation helpful? Give feedback.
-
Here's a thought exercise: Let's say that during this preview that it's found that there are too many issues with NRTs and DIM for it to ship this year. Would the next release of C# still be 8.0? What would the thoughts be on shipping those otherwise major features in minor releases? Let's assume whatever holds up DIM isn't in .NET Core 3.0 as that would complicate the scenario considerably. |
Beta Was this translation helpful? Give feedback.
-
Absolutely. This is a new version of C# that is shipping in tandem with a release of the .NET SDK. Those releases will be major releases, even if a few big features ultimately missed the cut. Features missing the cut is rare but not unheard of. It's happened before, it will happen again. In the past we've adopted the stance I listed above. |
Beta Was this translation helpful? Give feedback.
-
That's pretty cool to hear. Perhaps everything I've described would shake out of the annual release cadence. I, personally, would like to see language releases a little more frequently, like every six months, as that would allow a for faster adoption of smaller features. Perhaps that's where minor releases fit, although I'd prefer that would emphasize the timing and not the size or quality of the features included. Ideally those SDK releases also wouldn't be held up even if features start to slip. |
Beta Was this translation helpful? Give feedback.
-
Forcing release cycles does get stuff out there more quickly- but it can also have converse effect of what you describe- because your dev cycles are relatively short you never ship major features because there's never enough time to develop them properly. IMHO, this is something that happened to Windows when they decided to do a release every 6 months. Windows 10 RTM in 2015, and since then every update has been strictly minor changes. To be honest, I can't name a single feature (other than endless bugs) that has been added since Windows 10's release. Another example- ECMAScript. The committee decided to be "agile" and that they would release yearly versions after 2015. The result? Because of the forced releases the committee only ever agrees on small features for their yearly releases and meanwhile most of the useful features are stuck firmly in committee development hell. I think C#'s recent release cycle has almost hit the sweet spot. Minor releases ship small features, major releases ship major features which may require runtime support. If anything, the minor releases have even happened a little too frequently (the I'd prefer they stick to a "when it's done" model of major/minor versions and not force yearly releases to meet artificial deadlines. |
Beta Was this translation helpful? Give feedback.
-
The answer there is to not tie the development cycle to the release cycle. Projects may span many releases. Whenever they happen to be done they get slated to go out with the next release. The release itself can never be held up, neither can any of the other myriad of changes already ready go out. There have been numerous Java features which have been released in this manner. The new HttpClient took 2-3 releases. There are two GCs currently in experimental mode, as well several language features. There are a number of long term projects still under active development which aren't likely to be included in any releases this year, but may trickle out piecemeal as experimental. Some of those projects have been under active development for over 5 years. The faster release cadence doesn't seem to be discouraging the major projects, they just deemphasize their timing and don't block/slow the constant cycle of other new features. Sure, it could be done wrong. It could also be done right. We're watching the C# language deal with fits and starts when it comes to releasing language features. There's lot of small features that are currently stuck waiting on C# 8.0. And with everyone head down on those major features to meet the arbitrary deadline we all experience the relative silence from the team. This happened before C# 6.0 and C# 7.0 as well. With a faster release cadence we'd theoretically see those smaller features sooner and we'd have continuous feedback regarding the development of features into the next cycle. |
Beta Was this translation helpful? Give feedback.
-
Bingo. This already happens today. Consider that we started implementing nullable reference types in 2015 for C# 7.0. Spent ~3 months on the initial parsing and symbol model. After that though the C# 7.0 release began to take shape, as did our costing for NRT and we decided to delay it because it obviously did not fit. Once C# 7.2 was wrapping up we shifted resources back into NRT and are pushing it to completion for C# 8.0. Another example to site here is pattern matching. The feature set we're releasing for C# 8.0 is actually fairly close to the original vision that we had back in the C# 7.0 time frame. Once we got into the implementation though we realized there wasn't enough time to fit it in. Hence we split it into two pieces, each of which is a large feature onto itself. If there hadn't been a logical split then most likely we would've just kept working on it and targeted it all for C# 8.0. |
Beta Was this translation helpful? Give feedback.
-
I think that's easier said than done depending on the pressures on the team. Often when you're forced to plan releases to meet some arbitrary deadline, it means you are constantly doing exactly work: re-prioritizing so that something is in your releases as well as all the inevitable extra work that comes with having a release. Which means the big things tend to get pushed aside so you can complete a bunch of little things on schedule. I agree there are lot of minor features in C# 8 that could have been shipped sooner. There is an argument that maybe there should have been a C# 7.4 in the interim. But I assume the reason they didn't do that is because it would have required lots of release planning and whatnot that would have distracted from the big features they wanted to do for C# 8- exactly my point. We may have gotten |
Beta Was this translation helpful? Give feedback.
-
I think the goals expressed here by @HaloFour are, as @jaredpar says, the C# teams goals.
And we are trying to do these things. The reality is that the gravitational pull of large features will always have some effect on smaller features. In perfect world we would just have Chuck working on nullable references constantly and that would not effect anyone else, but in reality some things just need variable amounts of brain power over their development cycle to complete. We are always trying to find better ways to work/schedule things and I love this conversations. I just don't know if its possible to live in a world were large features have no effect on the development of smaller ones... |
Beta Was this translation helpful? Give feedback.
-
The point is to not plan the features around the arbitrary deadline. The releases will happen either way. What features end up in that release depend only and entirely on what features are "done".
And if the releases happen either way the release planning becomes infinitely easier. You don't have to gauge and schedule the release based the perceived amount of work remaining to be done. That kind of estimation is near impossible in this kind of work. Instead the release planning is simply, "what's done?" Brian Goetz very explicitly stated that the amount of time he had to spend in release planning meetings dropped considerably (paraphrasing, almost to none) compared to when they did big-bang releases. Either way, it is certainly a question of balance. My experience with C# is that things tend to cram for the big releases. Even with minor versions in the mix the majority of the work seems to be focused on the larger features and towards these arbitrary deadlines we end up with relative silence on these forums. That's been my impression since CodePlex, and it's certainly the case now leading up to C# 8.0. And from the stories outlined it seems that when a big feature slips it doesn't do so to the next release, it does so to the next major version, so we're already seeing slippage as a result of this arbitrary alignment. What I read into the two anecdotes you provide regarding NRTs and pattern matching is that when two big features didn't make it into a major release that their work become deprioritized to realign them with the next big release. Perhaps I'm interpreting that wrong, but that's precisely what I think should be avoided. Ideally, the work on both projects would continue on and if either happened to be done by C# 7.1 or C# 7.2 or whatever then they would be released at that time. Granted, I understand that there are a lot of reasons to stop work on a particular project, including reprioritization. I don't know that I have any "goals" per se. I am sharing what I learned listening to Brian Goetz discuss the changes to Java's development cycle, and what I perceive to be the dividends of that change as a Java programmer. I found a video of Brian Goetz discussing this topic at a different conference. I will post a link to the relevant portion above. |
Beta Was this translation helpful? Give feedback.
-
While this is definitely a meta discussion worth having, I can say that from the trenches, the current approach is working quite well. I would be interested to see a more formal C# release cadence announcement sometime around the NET 5 release. But I am also very happy to have the C# releases to have its own timeline. Having a release every X weeks/months is a very good Agile practice worth adopting. Only ship what's actually done. Have an active project dashboard that lists what's done and what's in progress. That keeps the community in the loop on what to expect in the next release ahead of time. Have a build that includes only the done stuff, abd also have a preview build that includes work in progress of mixed quality that insiders can experiment with and provide feedback on. That's the best of all scenarios. |
Beta Was this translation helpful? Give feedback.
-
I certainly recognize that that has been the case. But I for one would like to see some breaking changes around the .NET 5.0 timeframe. I would love to see things the community wants removed flagged as obsolete and then eventually removed from the language. As long as the underlying CLI is backwards compatible, that wouldn't affect old assemblies already in the wild -- it would just affect active builds. For instance, I would love to see the team use that approach to solve the NRTs conundrum, and eventually end up in a place where nullability is always explicit like TypeScript and Kotlin. Creating a formal mechanism, such as semantic version, for making breaking changes known would provide path to actually accomplish that goal. One method could be to increase the major version when things are marked as obsolete (but will still compile with a compiler flag), and obsolete things can only be removed in the subsequent major version. So if major versions are spaced out fairly widely, that would provide the community ample time to adapt their active code bases. Moreover, automated conversion tools for the new obsolete features could be included in the major releases -- for example, an automated tool for converting all legacy reference variables/fields to be explicitly nullable would be a way to bridge the gap to explicit NRTs. |
Beta Was this translation helpful? Give feedback.
-
I can say definitively this will never happen. C# has too many users. We'd just create a new language if we were going to create sweeping breaking changes.
Internally we want the process to be that there are well understood shipping vehicles and things just move into them when they are ready. Visual Studio and .NET Core (which are still factors in planning C#) are also trying to get better and better at shipping this same way. I think we are all in agreement that this is a good thing. I just don't know if we can avoid big features sucking all the oxygen out of the room when they ramp up. |
Beta Was this translation helpful? Give feedback.
-
I agree that old code should still compile. I don't see any reason that goal would be compromised by also depreciating obsolete language features over time. The legacy constructs for C# should still compile if the project is pegged to a specific legacy version of C#. That seems like the best of both worlds to me. |
Beta Was this translation helpful? Give feedback.
-
That's a larger maintenance burden. And, if the language has to keep supporting these constructs, why not just allow people to keep using them?
Or, it will keep organizations never moving forward. See, for example, python2 and 3. These types of approaches risk large schisms in teh ecosystem. |
Beta Was this translation helpful? Give feedback.
-
Deprecating unwanted language features seems to be working for TypeScript. Not all communities are equal. In C#'s case, I think the few things we'd want to jettison can easily be addressed with automatic conversion tooling (e.g. explicit NRTs). Sure there would be some maintenance burden on the compiler to support compiling old language versions -- but how much effort would it actually take quantitatively? Shouldn't be much. (I'm saying that with the authority of being someone who provides deep backwards compatibility for the frameworks I ship, and I also deprecate things using the strategy described above. So I do know a bit about what would be involved...) |
Beta Was this translation helpful? Give feedback.
-
I'd have to see which features were even on hte list for deprecation. Are there ones in particular that we think would even be up for this sort of thing? |
Beta Was this translation helpful? Give feedback.
-
Which features in particular are negatively affecting the ability of the language to move forward. For example, shorthand delegate syntax is now a bit pointless, but it's in, doesnt cause any harm, and it's not worth the pain of removing. Then there's user defined casts, which make the whole language more complex, and which some language designers definitely wish hadn't been added. But they are to deeply embedded in the ecosystem to be removed. So what's left? |
Beta Was this translation helpful? Give feedback.
-
I bet the team would love to deprecate and remove method overloading. 😀 |
Beta Was this translation helpful? Give feedback.
-
I am pretty happy with C# overall, and the things on the table for C# 8.0. The main use case I can perceive deprecation helping with would be a transition to full explicit NRT support, where nullability was always explicit. TypeScript and Kotlin have both provided syntax concepts for a lot of the necessary mechanisms -- like late initialization, etc. I know the C# community is currently wrestling with C# 8.0's various proposals about how to shoe horn NRTs into existing C# code. And I agree with you, generally, that the cost/benefit needs to be "just right" if there aren't going to be any breaking changes to C#. However, I would be fine with C# 8.0 containing an intentionally "transitionary" NRT implementation that is "friendlier" to existing code bases. Then perhaps C# 9.0 could make the hard break that I'm describing here, and provide tooling to auto-port old code to the new explicit syntax. However, this discussion about deprecation doesn't have to be a scenario that forces old code not to compile. Rather, one way of implementing semantic versioning is to have a set of quirks, and the new version would come with certain quirks enabled/disabled by default. That's how the .NET framework handles functional deprecation and it is extremely pragmatic. Projects can upgrade to the newer framework and then opt in or out of the various quirks relevant to ensuring their code continues to function properly. Consumers are advised to upgrade their code to avoid the uses of quirks modes -- but that is a pragmatic decision left to the consumers. The same general approach can be used for language design as well to provide another option between the two extremes of never deprecating anything and hard deprecation. (Moreover, if certain legacy quirks fade into obscurity they could be removed.) Another glaring option to improve C# would be improving the code that the compiler generates for async scenarios. For instance, the F# and C++ teams had the privilege of working on their async implementations later in time, and there are now well known ways of doing it better. Although @stephentoub and company have been making head way on providing pathways to avoid allocation in async scenarios using Third, I've discussed in other threads that I would like to see C# compiler treat non-capturing delegates and lambdas expressions as immutable and single instance. If there are certain concerns the team has about the underlying data structures wrt immutability, those could be addressed via breaking changes as well (e.g. compilation controlled by quirks mode, etc.). The community might have other ideas that are better than the ones I've thought up. |
Beta Was this translation helpful? Give feedback.
-
At that point you might as well make a clean break and start a new language. Then you have no need for "quirks mode" or varying dialects. You're going to end up with a schism either way, you might as well make the most of it. In any case, my opinion is that the idea of deprecation of off-topic and orthogonal to this particular issue. I'd like to avoid the notion of major/minor language versions in general, not overload them to mean even more than they do today. |
Beta Was this translation helpful? Give feedback.
-
Shorter answer: I think we'd need a survey. |
Beta Was this translation helpful? Give feedback.
-
I am working on a language of my own, and I'm planning on going the semantic versioning + quirks mode route. Each file in my language begins with a simple version instruction, which also allows a set of flags to opt in/out of quirks. The version instruction is optional and if not specified the lexer/parser assumes the latest version. As a language designer, that gives me the freedom to make mistakes and correct them. Do I plan on deprecating things very often once I start shipping the tooling? Hopefully not. But in my prerelease iterations I have already made heavy use of that capability while I've been experimenting with various syntax formalizations, etc. I don't always want to rewrite my existing working code when I introduce a breaking change, so I'm using this capability in my own codebase to enable rapid cycles for the language itself. (For my language, when I get ready for a first release, I'll squash all of the deprecated features and update all of my code to establish a consistent baseline. But I know I will appreciate having the glue already in place to enable pivoting for later releases, precisely because I've already been using it with great success.) I like TypeScript's boldness of just releasing breaking changes when they seem like the right thing to do, but I don't like the idea of actually breaking working code. (Before I officially release a 1.0 of the new language, I'll open it up to the community to discuss whether to keep the plumbing for the semantic versioning in place. I think it's a really nice capability, but I'm willing to be persuaded otherwise.) |
Beta Was this translation helpful? Give feedback.
-
@marksmeltzer Sounds fascinating! Would def love to hear how it works out for you :) |
Beta Was this translation helpful? Give feedback.
-
This is the key question and the answer is really ... nothing. There are occasions where existing behaviors cause us a bit of pain when working on the design for new features but it's on the level of annoyance, not a blocker. The most recent example is using the |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi I like it so far. Because I am lazy, I made the lexer/parser capable of doing an optional "walk-back" regression if a file fails to parse using the latest language version: if it finds a language version that successfully parses the file, it automagically inserts/updates the language version instruction on the file (typically that will be the previous version, but it depends on how recently I have worked on a given codebase). I can easily grep the codebase to find the files that need to be manually updated to the latest version. My workflow consists of updating old files as I have time, updating the legacy syntax issues, and then removing the version instruction to bring the file back to latest. So far that workflow has proven itself quite economical. It allows me to try funky new ideas for syntax in specific use case in a feature branch without affecting the rest of my codebase. Often what seemed like a good idea reveals itself to be a bad idea once I start retrofitting it other places. In those cases, I'll either deprecate the new feature using the same mechanism or I revert the relevant changes in the feature branch (whichever is cheaper to do). I think a concept like that is of general utility. When I go live, I'll probably shift and have new features trickle down from explicit opt-in to a graded set of "insiders" or "futures" release candidates, and then shift the walk-back mechanism to also support downgrading or removing the version instruction when a given file that used to require opting in to future support becomes part of latest, etc. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I've been thinking a little about the C# release cadence lately and how it relates to how and when features get shipped. I recently listened to a talk from Brian Goetz where he discussed Java's recent adoption of a consistent release cadence, why they decided to do this and the benefits they found from doing so and I think it's worth having the discussion as to whether or not the same could apply to C#.
Previously Java would tie major features to major releases. Those releases would be relatively infrequent, on the order of one every few years, and because those major language features were the driving force behind the release it created a complex project management cycle. It also led to, and I'm going to paraphrase this very poorly, a "constipation of smaller features", as the big release-driving features would get all of the attention. The releases also tended to be big which increased risk and complexity for any user choosing to update.
As of Java 9, they now follow a strict six-month release cycle. There's no such thing as a "major" release anymore, each release is an increment of the major version number. Java 9 released in Sept 2017, Java 10 released in March 2018, Java 11 released in Sept 2018, Java 12 released in March 2019 and Java 13 will release in Sept 2019.
As described, any language features ship with the compiler when they're ready. For smaller features that are not controversial that means as soon as they're done they are ready and included in the next release. For larger features that may be developed over the span of multiple releases, the feature will ship behind an "experimental" flag. If that feature is well received as is it will be ungated in the next release, or it could be tweaked and left behind the flag for another release, or it could be pulled altogether.
The benefits here should be pretty obvious to any team who has worked in a continuous delivery environment. Features come quick and often. There isn't a constant focus on the big-bang features that define a release. And there is a lot less of a focus on the release in general. Project management is still a thing, of course, but it's less tied to and obsessed about proposed release dates. Brian Goetz specifically mentioned that after adopting this cadence the time he had to spend in release planning meetings dropped to almost nil and he could spend most of his energy on the language, where it belongs. I don't know what @MadsTorgersen does all day right now, but it wouldn't surprise me if meetings were towards the top of the list.
So, what I propose, is that the C# team drop the notion of major and minor releases. Exactly how the numbering would work can be up for discussion, but drop the idea that big features are a part of major releases and small features or tweaks are a part of minor releases. Each release should stand on its own, and each release should come on a fairly consistent schedule. If that's at all possible I would also like to see the adoption of a release process like that mentioned for Java: a feature releases when it's ready into the next release, not when enough features can be bundled together with a big feature to justify incrementing the number. And, importantly, drop the notion of "preview" releases leading up to a major release. Allow "preview" versions of upcoming features to flow into the normal release cadence.
I do acknowledge that there are numerous differences between C# and Java which will complicate these ideas. C# has dependencies on separate teams building the CLR whereas Java always evolves in lockstep with its runtime. C# also has different concerns around the IDE and tooling. I don't remotely pretend to have answers around how these issues can be resolved.
Link to Brian Goetz discussing the changes to Java release cadence, starting at the relevant bit:
https://youtu.be/4r2Wg-TY7gU?t=475
Beta Was this translation helpful? Give feedback.
All reactions