TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

The way we're thinking about breaking changes

55 pointsby luuabout 2 months ago

17 comments

ivan_gammelabout 2 months ago
This post has clickbait vibe unfortunately and is really shallow regarding the analysis of the problem. No, it is not silly how we think about breaking changes. The idea of using some change log isn’t new and there are reasons why it’s not implemented by most build systems.<p>The elephant in the room is changing business requirements. If the change requires the code to consume new parameter without reasonable defaults, no automatic migration would solve it reliably. Let’s say you deal with the money and you introduce currency field. The compiler of the client code cannot just assume the currency, neither it can take it from anywhere by just applying the migration. It is a manual operation to fix the broken code.
评论 #43524102 未加载
评论 #43524582 未加载
steegoabout 2 months ago
I think this proposal ultimately introduces more complexity than it solves. By automatically inserting migrations (whether at compile time or via “migration files”), you end up with a form of hidden control flow that’s arguably worse than traditional overloading or type coercion. In normal type coercion, the transformation is explicit and visible in the language syntax, whereas these “migrations” happen magically behind the scenes.<p>Second, database migrations are notoriously tricky for developers to manage correctly. They often require significant domain knowledge to avoid breaking assumptions further down the line. Applying that paradigm directly to compiler-managed code changes feels like it would amplify the same problems—especially in languages that rely on strong type inference. The slightest mismatch in inferred types could ripple through a large codebase in ways that are far from obvious.<p>While it’s an interesting idea in theory, I think the “fix your old code with a macro-like script” approach just shifts maintenance costs elsewhere. We’d still be chasing edge cases, except now they’re tucked away behind code-generation layers and elaborate type transformations. It may reduce immediate breakage, but at the expense of clarity and predictable behavior in the long run.
评论 #43524462 未加载
kmeisthaxabout 2 months ago
The way you handle breaking changes in SQL is <i>views</i>, not migrations; the latter is only appropriate single applications abusing SQL as a data store[0].<p>In code, if you want to handle a breaking change transparently, you don&#x27;t need the moral equivalent of a linker fix-up table. You need to keep the old definition around and redirect it to the new function. This can be as simple as having, say, the old version of your code wrap something in a list or closure and then call the new version. In languages with overloading, this can be done transparently; in others you&#x27;d have to give the new function a new name. Maybe API versioned symbols and a syntax for them is what you would want?<p>[0] The original idea with SQL is that multiple applications would store data in the same place in a common format you could meaningfully interchange.
morsecodistabout 2 months ago
I am a bit confused by this. Typically when you make a breaking change it is because the call site has to make a new sort of decision so you may not want all calls to be refactored the same way and there is no way of determining which you want at runtime.<p>The example the author uses is modifying a function to return an int or null instead of an int. Let&#x27;s say you implemented the function a bit naively and in the null case your function would crash the program. Now you are going to refactor your codebase so the caller gets to decide what happens in the null case. Some callers may be unable to handle the situation and will need to implement the crash&#x2F;exception some may use some sort of fallback behavior.<p>I think haskell&#x27;s type system probably would probably prevent someone from having the issue I described above but the problem still stands. Let&#x27;s say you had a function that returns a union type and you have areas in the code that are supposed to handle every case of that type. If you had a case you need to handle it everywhere and the compiler can&#x27;t know how you want to handle it. A lot of type systems will catch the missing case which is awesome but you still need to handle each one.
评论 #43525805 未加载
lolinderabout 2 months ago
It&#x27;s not perfect, but Kotlin already has a limited form of this with its ReplaceWith annotation [0]. It allows you to mark something as @Deprecated and to specify what should be used instead. IDEs and other tooling can pick that up and apply it automatically.<p>This assumes that you&#x27;re approaching the breaking change problem from the perspective of immutable names and deprecations: don&#x27;t make breaking changes to existing names, create a new name for the new behavior and communicate that the old behavior is no longer going to be supported.<p>[0] <a href="https:&#x2F;&#x2F;kotlinlang.org&#x2F;api&#x2F;core&#x2F;kotlin-stdlib&#x2F;kotlin&#x2F;-replace-with&#x2F;" rel="nofollow">https:&#x2F;&#x2F;kotlinlang.org&#x2F;api&#x2F;core&#x2F;kotlin-stdlib&#x2F;kotlin&#x2F;-replac...</a>
samusabout 2 months ago
Databases have empathically <i>not</i> solved this problem. I have never heard of an DBS with first-class support of migrations; they are usually layered on top using something like Flyway or Liquibase. A query still working after a migration is still either an accident or due to a deliberate backwards compatibility effort.<p>The proposal essentially expects the dependency to provide a migration path. However, to the extent described this is only necessary in languages with no overloading. And it doesn&#x27;t help for cases when the change is <i>not</i> as simple as providing a few rewrite rules or things like that.<p>Also, the jab about static type systems is misplaced. Their purpose is specifically to reject certain invalid programs, at the cost of rejecting a few valid. You can&#x27;t have one without the other. With dynamic typing, backwards compatibility breakages fly under the radar until they gleefully blow up in production.
wavemodeabout 2 months ago
The code equivalent of a &quot;migration&quot; is that you just write a new function that takes your new desired argument types, and then have the old function perform whatever type conversion is necessary and call the new.<p>If writing such a function is not possible (because there is no automated way to convert from the old function call style to the new) then a database-style automated migration isn&#x27;t going to be possible either.
Guthurabout 2 months ago
If one believes that migrations have solved this for databases one is very sorely mistaken. Sure, you can migrate the data using some often brittle form of SQL migration log, but this doesn&#x27;t help if some code happens to be reading that table and expected the previous data shape, and this is rarely caught before the caller tries to access the data. This might be solved in some language that is some how taking full control of the data model such as an ORM, but that then pretty much breaks and additional language interoperability.
评论 #43524283 未加载
oftenwrongabout 2 months ago
Alternatively, we could use a model in which functions are immutable, and therefore make such breaking changes impossible. This is the approach taken by Unison:<p><a href="https:&#x2F;&#x2F;www.unison-lang.org&#x2F;docs&#x2F;the-big-idea&#x2F;" rel="nofollow">https:&#x2F;&#x2F;www.unison-lang.org&#x2F;docs&#x2F;the-big-idea&#x2F;</a>
评论 #43530379 未加载
评论 #43524187 未加载
bluGillabout 2 months ago
That only works if your tools have planned ahead and so when an &#x27;old call&#x27; it somehow goes to a translation system now. It also assumes you have good translation for the changee function and the performance and memory costs of it are acceptable.<p>none of the above as always true.
tags2kabout 2 months ago
I&#x27;m entirely unsure how database migrations aren&#x27;t breaking changes - you migrate to a new version of your schema, queries that use an older schema aren&#x27;t going to work. Database server functions can be changed through migrations too.
blixtabout 2 months ago
I think interactions between many types and functions would be harder to migrate, especially if you go so granular as to target individual types independently. Maybe if it was a migration from some checkpoint (timestamp &#x2F; version &#x2F; commit &#x2F; whatever you like that describes an entire state of the code) to another, then you could migrate indirect code patterns that need to change as well.<p>I think most of all one must have discipline to thoroughly think of old code and how it should behave now. I found that one can already do this by versioning code and types, similar to how an API endpoint might go from &#x2F;v2&#x2F;… to &#x2F;v3&#x2F;…. But again, it requires discipline and it’s not something I’d do for anything but very critical code.
chromanoidabout 2 months ago
OpenRewrite is quite nice and has a great collection of recipes for migrating, e.g. from javax to jakarta: <a href="https:&#x2F;&#x2F;docs.openrewrite.org&#x2F;recipes&#x2F;java&#x2F;migrate" rel="nofollow">https:&#x2F;&#x2F;docs.openrewrite.org&#x2F;recipes&#x2F;java&#x2F;migrate</a><p>There are some quirks you have to work around for big projects since the free tooling has some limitations.
AndrewDuckerabout 2 months ago
In order to do this the compiler needs to know what the previous compilation did so that it can apply these changes purely to things that changed between then and now. Which doesn&#x27;t seem impossible, but does seem like an interesting problem to solve.
AnthonBergabout 2 months ago
Hear hear!<p>—and—<p>From the hip: I think this is encompassed by dependent types.<p>The stuff that just falls naturally out of dependent types includes tools that grasp… this. And other concerns.<p>If I put it like so?:<p>Software is automation.<p>Dependent types is the automation of the automation.
esafakabout 2 months ago
Products could version each interface as well as their aggregate, so you know if the parts you are using are breaking. It does introduce complexity, I know.
lblumeabout 2 months ago
Good article, although the wider applications should likely be considered more. One small mistake seems to be defining Maybe (like in Haskell) as Nothing | Just but then still using Some (like in Rust) to initiate it.