this post was submitted on 07 Sep 2023
61 points (96.9% liked)

Programming

17509 readers
7 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities [email protected]



founded 1 year ago
MODERATORS
 

This might seem obviously "yes" at first, but consider a method like foo.debugRepr() which outputs the string FOO and has documentation which says it is meant only to be used for logging / debugging. Then you make a new release of your library and want to update the debug representation to be **FOO**.

Based on the semantics of debugRepr() I would argue that this is NOT a breaking change even though it is returning a different value, because it should only affect logging. However, if someone relies on this and uses it the wrong way, it will break their code.

What do you think? Is this a breaking change or not?

top 23 comments
sorted by: hot top controversial new old
[–] [email protected] 49 points 1 year ago (1 children)

This is https://www.hyrumslaw.com/.

Basically there are two types of breaking changes:

  1. The change may break something.
  2. The change breaks a contract of the code.

What you are experiencing with debugRepr() is that you have triggered 1. You have made a chance that may break a user. But you have not triggered 2 because the new output is still within the previous contract. What level of stability you want to uphold is up to you.

[–] [email protected] 3 points 1 year ago* (last edited 1 year ago)

Which one is important is going to depend on the context for sure.

If it's an open source library, they probably won't care about 1.

If you're working on internal software used by other developers within the company, management probably really does care about 1 because it's going to impact their timelines.

If you're working on a proprietary user-facing API, then even if it doesn't cost your company anything management might still care because it could piss off valuable customers.

I think that, for what ever decision OP is trying to make, looking at that context is more important than quibbling over what exactly constitutes a "breaking change."

[–] [email protected] 25 points 1 year ago

https://xkcd.com/1172/

People depend on your program behavior. If you change how the interface works you're going to break people's programs. No way around it. Even if you warn them

[–] 0x0 22 points 1 year ago

You're clearly stating this is debug code, so the user should read that as Here be dragons. Otherwise you may break your user's code, e.g., sysadmins that rely on the logging for monitoring.

[–] [email protected] 17 points 1 year ago

has documentation which says it is meant only to be used for logging / debugging

No, it's not a breaking change IMO. The method contract (the "debug" name, the comment) heavily implies the output may change and should not be relied upon.

[–] atheken 16 points 1 year ago* (last edited 1 year ago)

This one is a bit tricky, because you have to think about logging as an output or a side-effect. And as an industry, we’ve been learning that we should limit the amount of side-effects that our code generates.

If logging is getting ingested by downstream systems like CloudWatch, or other structured logging systems, it is potentially going to be used to detect service issues and track overall service health. These are logs that are serving a functional purpose that is not purely a side-effect, or for debugging forensics.

If this is the case, then you should have a unit test asserting that a log entry is emitted when a method is called. If writing that test is a low or non-priority, then even if it’s a “breaking change,” then that’s a sign that it’s not actually going to break anyone.

I’m sure there’s some monadic view of how to package up the “side-effect” logging as part of a function’s output, but it’s probably annoying to implement in most languages.

[–] [email protected] 14 points 1 year ago (2 children)

Edge case: If you call foo.version() and it returns a different string in version 1.02 than in version 1.01, that is not a defect.

[–] [email protected] 13 points 1 year ago (1 children)

That's not a functionality change. The method still does the same thing: "outputs the current version of the software".

[–] [email protected] 2 points 1 year ago

I think that's what they're saying.

[–] [email protected] 1 points 1 year ago

Very interesting. Good point!

[–] [email protected] 14 points 1 year ago

Only if it's specified and documented as part of a contract with the user. If they're relying on internal implementation details, well that's a good lesson for them not too do that.

[–] o11c 10 points 1 year ago

As a practical matter it is likely to break somebody's unit tests.

If there's an alternative approach that you want people to use in their unit tests, go ahead and break it. If there isn't, but you're only doing such breakage rarely and it's reasonable for their unit tests to be updated in a way that works with both versions of your library, do it cautiously. Otherwise, only do it if you own the universe and you hate future debuggers.

[–] [email protected] 7 points 1 year ago

It's your project, do whatever you want.

If changing any observable behavior meant a breaking change, then you couldn't ever change anything. Even a bug fix changes observable behavior. Some people don't seem to be considering that here...

[–] OffByOneError 7 points 1 year ago (1 children)

"if someone relies on this and uses it the wrong way"

The wrong way for you might be the perfect solution for someone else. Once things are being used, you have no idea how people will use them, and they will likely use them in ways you didn't anticipate.

[–] [email protected] 10 points 1 year ago

If you go using code in a way contrary to its documentation, you can't expect semantic versioning to have semantic value to you.

Nothing in there stops you. You are perfectly free to hack anything in your code. But it's completely outside of your relationship with the author, and frees him from any problem an update may cause you.

[–] [email protected] 7 points 1 year ago

If I don't break the contract and your code breaks that's your problem imo

[–] [email protected] 6 points 1 year ago* (last edited 1 year ago)

For debug code, anything goes. For API, it should be versioned if you're worried about this kind of thing. Such as /v2/ in the path, or a version property on a returned object.

[–] samus7070 4 points 1 year ago

Breaking change. It’s gone from plain text to a markdown formatted text (possibly). There’s changing an interface (obviously a breaking change) and then there’s changing the semantics of a function. I just dealt with a breaking change where a string error value changed for an account registration api call. Previously it returned EMAIL_IN_USE and now it returns EMAIL_TAKEN. Same data type but it broke the client code. Changing values or formats is a breaking change. In your case the documentation says don’t rely on this function for anything but once the output is in the wild any monkey can start using it for anything and it can’t be certain that some code documentation will be consulted before deciding to depend on it.

[–] [email protected] 4 points 1 year ago (1 children)

IMO it doesn’t really matter what you said the method was for. If you change the format of a string that is returned by a method that returns a string, there’s a risk of breaking user code, even if it’s just in the context of their dev environment.

Philosophically, whether or not the behavior of your API has changed is completely disconnected from whether or not others are using it “right”. If I can depend on a function to return a certain type of value when given certain arguments, and if it doesn’t produce other side effects, then it doesn’t matter what the docs say or what the function is named, I can use it in any context where I need that type of return value and have this type of arguments available. This type of function is just mapping data to other data. If you modify the function in such a way that the return value changes after being given the same arguments, that’s a breaking change in my book.

[–] [email protected] 2 points 1 year ago (1 children)

What about version()? Every minor / patch update would be a breaking change.

[–] [email protected] 2 points 1 year ago (1 children)

The way I do it, patches are backward-compatible bug fixes. Minor versions are additional features that don’t change existing functionality. Major versions include breaking changes. I totally get that it seems crazy to bump to another major version just over a string format change. But overall the philosophy works well IMO.

[–] [email protected] 1 points 1 year ago

Works really well with npm. You can get security updates without changing the app.

[–] agilob 1 points 1 year ago

My tests that observe output from the method are failing so it's a breaking change. Did you not test the printed output?