TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

The way we're thinking about breaking changes

55 点作者 luu大约 1 个月前

17 条评论

ivan_gammel大约 1 个月前
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 未加载
steego大约 1 个月前
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 未加载
kmeisthax大约 1 个月前
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.
morsecodist大约 1 个月前
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 未加载
lolinder大约 1 个月前
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>
samus大约 1 个月前
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.
wavemode大约 1 个月前
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.
Guthur大约 1 个月前
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 未加载
oftenwrong大约 1 个月前
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 未加载
bluGill大约 1 个月前
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.
tags2k大约 1 个月前
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.
blixt大约 1 个月前
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.
chromanoid大约 1 个月前
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.
AndrewDucker大约 1 个月前
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.
AnthonBerg大约 1 个月前
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.
esafak大约 1 个月前
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.
lblume大约 1 个月前
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.