I recently had some experience that motivated me to write about the ØMQ C4, currently available as 4.2, and why it worth reading. I focus here a little bit on the part with the interface, but I think the whole C4 is a good document that developers should know.
A problem arrives
The other day I got a notification that the samples to my BitBake tutorial stopped working and running them does produce an error. The reported error message was something like: A function expects an additional parameter.
I downloaded the recent yocto/bitbake release, run the first example, as in the bug report specified, and I had no error.
I checked the official documentation of the functions, they seemed to be unchanged. There was a change in the usage of the function, but this was just the usage, not the function itself.
So I was a little bit astonished, yocto and bitbake are hobbies, nothing I use on a daily base as a developer, so I was not aware of any changes and the examples worked for about 2 years.
So I asked for more feedback. While waiting for additional info I got an other bug report reporting the same issue.
Finding the problem
So I finally tested the examples to the other chapters, something I should have done immediately, and indeed, I got the mentioned error messages for all other examples.
So I checked out the bitbake repo and had a look if and when the function was changed.
And, there it was, in a git commit message, the info I was looking for:
bitbake: data_smart: Drop default expand=False to getVar [API change] At some point in the future, getVar should expand by default. To get there from the current position, we need a period of time where the expand parameter is mandatory. This patch starts that process. Clear errors will result from any code which doesn't provide this. Layers can be fixed with an expression like: sed -e 's:\(\.getVar([^,()]*\)):\1, False):g' -i `grep -ril getVar *`
So the first thing to notice is that I did not get a clear error message as the developers expected.
No idea why, possible in the first tutorial building such a minimal example in not a 'standard use case' and I had also old cache information in the build folder.
Note: If you think you know what your users are doing thing again, do not make assumptions.
The second thing I did was check again if I over read something, but no. The official documentation was unchanged, at the time of writing this blog post it still says:
Returns the value of variable "X". Using "expand=True" expands the value.
No warning at all that things have changed, but I hope this will change with the time.
Fixing the problem
My first intention was to change the text in the tutorial and give a hint that a version smaller yocto 2.1 should be used, because who knows what other changes might come in future.
It’s about a private spare time/hobby project which is basically in the state of 'done' for me.
And having some unexpected effort like this is not my preferred way to spend my weekend time, there are project that have the state 'TODO' or 'in progress' on my list.
But finally I decided to use the provided sed/regexp fix and at least I can say that this worked for me.
The requirement to change an interface
Lets look what the change actually was. I’ll do some speculation and simplify things, but I think I will get the point.
Let us assume I have a function that takes 2 paramters, for example something like
getVar (which, expand = FALSE)
The first parameter is some value.
The second parameter is a boolean flag and has a default value.
This flag tells the function how parameter 1 shall be handled. Since it has a default value it actually says: 'If not different specified, behave the way A, but the user can decide to chose way B'.
Now, for some good reason, maybe changing the coffee brand or stop drinking coffee at all, I decide that I do not like the current implementation.
Therefore I would like to change it so that the default behaviour is not handling parameter in way A but in way B.
Now how would you do such an interface change?
The old version has behaviour A as default since the second parameter is set to False.
In the current version the user need the specify if behaviour A or B is wanted.
The next version will have behaviour B as default.
People that change from old to current will have short moment of WTF, but hey,
it might be a new major version, so what?
And don’t panic, you have clear error messages and please trust the sed/regex fix provided
(of course only if you find it).
It will hopefully work also in all edge cases that might exist or not.
People that change from an old version to the next without going over the current one, you might not get what you expect.
But trust me, I will possible wait long enough so that you do not step over the WTF event I created for you.
Add a new function, name it, just for example expand.
expandVariable(which) will be the new call.
No default parameter, no flag at all. Make it clear what it does this way.
The original function and behaviour stays unchanged. No need to change anything that is currently working.
Still, for the reason of consistency, I might want to add a second function, say
getVariable(which) that takes the second path.
So you have 2 functions, each with a clear name, no problems with unwanted default behaviour, and code becomes more easy to write and, especially, to read.
Mark the existing, original getVar function as deprecated, what in case of bitbake it would need documentation and write warning log messages on usage.
After a while, about 10 years or 100 versions later, or so, remove the deprecated function.
How to do it?
I think it should be pretty obvious to decide which way is better and what a developer who cares about the user of his/her components shall choose.
Of course way B is the preferred one, especially with a problem like this.
Add a new method with a clear name and, if required, mark the old one as deprecated.
So the user interface stays unchanged and nothing just breaks.
Btw, not using flag parameters is not a new idea.
This article refers to an older book about code design which self might refer to some even older literature. The above, real live problem, description is a prove why such flags are a bad idea, but this just as a side note.
Please do not shock your user this way
I know teams and companies that will, after the WTF moment, fall in starvation, have meeting without end and possible require an external expert to point to the fix.
And as a result of this become more conservative than ever and resist harder against any changes and version updates as before, what could deserves a own blog entry but not today.
I mean we are talking about bitbake, a tool used in the embedded field, a field that is not that well known for it’s progressive attitude, and I really can not think about a target audience where such changes are less a problem.
But even if you make software for early adopters, if some interface is not clearly marked as experimental, do not change it and break your users work. Except you do not want to have users for your work, because usually users do not like see their work breaking for no good reasone.
Please developers, think about your users.
When you plan to change interfaces, read the ØMQ C4.2, especially the section Evolution of Public Contracts
(and please get the term 'overriding consensus' right, it’s not a personal flavour after changing a coffee brand)
- You can find the C4 here
Thanks for listening/reading!
If you have any comment, please feel free to use the comment section below.