Fixing "typos" in enums...

A

Andreas Leitgeb

Let's assume, some non native English speaker writes up
a library, that's really useful and gaining broader use.

Now, the author may have once caught up some misspelling
of a common word (like e.g. thinking that "first" was
spelled "furst" and that with a confidence level ways
above "I'd better look this up").

Eventually this misspelling goes into an enum constant,
and by the time the first user reports it, the library
is already too much in use to lightheartedly apply
Plan "A" (namely fixing the typo and thereby inducing a
compatibility issue)

Now, Plan B could be to rename the enum constant, but add
a public static field by the old bad name (initialized
with the reference to the spelling-corrected field), to
alleviate the compatibility issue at least for those who
merely used the enum's field's name in their code.

// public enum Foo { FURST, SECOND }
public enum Foo { FIRST, SECOND; public static final FURST = FIRST; }

Any user having done a switch over that enum will still
need to modify his code. Ditto anyone doing string comparisons
on foo.name(), but that is most probably a bad idea, anyway.

I'd further denote "Just leave the typo in" as "Plan *Z*",
and ask for better strategies. (at least better than Plan Z)

PS: I vaguely remember that even Sun Java had some typos in its
API, but that predated enums, so adding a second constant with
same value but corrected name was a triviality back then...

PPS: a similar problem could arise with misspelled class names,
but that's not currently in my focus.
 
R

Robert Klemme

Let's assume, some non native English speaker writes up
a library, that's really useful and gaining broader use.
Eventually this misspelling goes into an enum constant,
and by the time the first user reports it, the library
is already too much in use to lightheartedly apply
Plan "A" (namely fixing the typo and thereby inducing a
compatibility issue)

Now, Plan B could be to rename the enum constant, but add
a public static field by the old bad name (initialized
with the reference to the spelling-corrected field), to
alleviate the compatibility issue at least for those who
merely used the enum's field's name in their code.

// public enum Foo { FURST, SECOND }
public enum Foo { FIRST, SECOND; public static final FURST = FIRST; }

Any user having done a switch over that enum will still
need to modify his code. Ditto anyone doing string comparisons
on foo.name(), but that is most probably a bad idea, anyway.

Not at all: a String could be read from some configuration file or
database and then the corresponding enum value searched via valueOf() or
values().

Another issue: serialized values. You might be able to remedy that via
the deserialization callbacks. This is at least something that needs to
be checked.

Even if certain practices are "bad ideas": they are applied nevertheless.
PPS: a similar problem could arise with misspelled class names,
but that's not currently in my focus.

A published API is basically set in stone. Keeping the wrong value
might actually be the best option.

Cheers

robert
 
J

Jan Burse

Andreas said:
// public enum Foo { FURST, SECOND }
public enum Foo { FIRST, SECOND; public static final FURST = FIRST; }

Maybe mark FURST also as deprecated.
And put a comment that says what should
be used instead of FURST, which could
go into javadoc.

And of course replacing FURST by FIRST
shouldn't be an issue when your project
and the clients of your project are in
the reach of refactoring tools. But yes
for public APIs, this is annoying.

In as far APIs behave similar as natural
language since for whatever reason synonyms
develop, and even word meanings disappear
over the time. Overloading of methods even
allows a form of polysemy.

Bye
 
A

Andreas Leitgeb

This solution would require duplicating lots of other
code, like methods taking that enum as parameter...

Also, in my case at hand the enum itself already has the
one and only correct name for that task (and it's of course
not really "Foo"). Giving it a different name would be like
replacing one misnomer by another misnomer ;-)

Anyway, thanks for the suggestion. In other situations it
might be just the right thing to do.
 
M

markspace

This solution would require duplicating lots of other
code, like methods taking that enum as parameter...

So you change the old method to a new method that takes the new type.
It's called refactoring. You don't duplicate methods, that's dumb.
Also, in my case at hand the enum itself already has the
one and only correct name for that task (and it's of course

I dislike adding a "2" onto new interface names, so I showed that coming
up with a new name is better. I'm sure if you really think about it,
you can come up with a new name. So far in the Java API, we've had
Date, Calendar and now LocalDateTime to all mean essentially the same
thing. A little creativity and thought is required.
 
A

Andreas Leitgeb

Jan Burse said:
Maybe mark FURST also as deprecated.
And put a comment that says what should
be used instead of FURST, which could
go into javadoc.

Definitely a good idea, which I forgot about. Thanks for reminding!
In as far APIs behave similar as natural
language since for whatever reason synonyms
develop, and even word meanings disappear
over the time. Overloading of methods even
allows a form of polysemy.

It would be nice, if enum had the concept of synonyms:
<dream-mode>
- each synonym is an alias for a particular constant
- synonyms would be recognized by valueOf(String)
- synonyms could be annotated as @Deprecated
- synonyms could be used in a switch, but then the
canonical name (or any other synonym for the same
constant) must not be used in the same switch.
</dream-mode>
Well, I know it's not going to happen. Just wanted it off my chest ;-)
 
E

Eric Sosman

So you change the old method to a new method that takes the new type.
It's called refactoring. You don't duplicate methods, that's dumb.

If you change the method signatures, haven't you already
given up on backwards compatibility? And if you're willing to
sacrifice compatibility, why not just fix the damn' typo and
break for lunch?
 
R

Robert Klemme

Fair enough. De-serialization from some persistent storage sure is
a legit task, that would suffer from such an API-change.

Basic Object-(de)serialization would be less of a problem, as that is
already specified as unsuitable for long-time storage.

Well, but it *can* be used for that purpose which means it *will* be.
And telling them "it's your fault" is often not a good option as there
can be numerous ugly ways that can backfire...
I just don't yet want to accept it, even though I can't refute it. ;-)

:) At the university we had a lecture about exactly this kind of
problem and other issues specific to API changes in OO languages.
Unfortunately I neither remember the professor's nor the lecture's name
to dig up some pointers. What I took away was that sentence ("...set in
stone") and that there is quite a number of surprising and subtle ways
failure can be caused by API changes. That had two effects: it made me
think thrice whenever I am tempted to change an API; and I try to give
extra thought whenever I am designing an API (not only the public ones
as often all of a sudden what has been private becomes public).

Kind regards

robert
 
E

Eric Sosman

Definitely a good idea, which I forgot about. Thanks for reminding!


It would be nice, if enum had the concept of synonyms:
<dream-mode>
- each synonym is an alias for a particular constant
- synonyms would be recognized by valueOf(String)
- synonyms could be annotated as @Deprecated
- synonyms could be used in a switch, but then the
canonical name (or any other synonym for the same
constant) must not be used in the same switch.
</dream-mode>
Well, I know it's not going to happen. Just wanted it off my chest ;-)

It'd make a mess of EnumMaps, too ...

I don't think there's any single Right Way to address your
problem, no one-size-fits-all solution. Elsethread it's written
that a published API is "set in stone," but there are many kinds
of stone. Of all posters to this thread, only you know whether
the stone is obsidian or talc; only you know how hard a tool is
required to scratch it.
 
A

Andreas Leitgeb

markspace said:
So you change the old method to a new method that takes the new type.
It's called refactoring. You don't duplicate methods, that's dumb.

Not quite as simple. I'd need a *copy* of each method (even as an
overload) taking the new Fooing, while the original would be
changed to @Deprecated and left at taking the Foo argument.

@Deprecated void applyRank(Foo rank) { ... blah ... }
void applyRank(Fooing rank) { ... mostly the same blah ... }
I dislike adding a "2" onto new interface names, so I showed that coming
up with a new name is better. I'm sure if you really think about it,
you can come up with a new name. So far in the Java API, we've had
Date, Calendar and now LocalDateTime to all mean essentially the same
thing. A little creativity and thought is required.

We learn from history, that alternatives for canonical names
always get an "Ex" appended... ;) At least, it is then obvious,
which is the older and which the newer... (otoh, consider a
fix for some Color class being called Colour ;-)
 
R

Robert Klemme

I don't think there's any single Right Way to address your
problem, no one-size-fits-all solution. Elsethread it's written
that a published API is "set in stone," but there are many kinds
of stone. Of all posters to this thread, only you know whether
the stone is obsidian or talc; only you know how hard a tool is
required to scratch it.

I like that analogy with the different kinds of stone. That fits really
well here.

Cheers

robert
 
A

Andreas Leitgeb

Robert Klemme said:
Well, but it *can* be used for that purpose which means it *will* be.

Picking some Enum from the JLS (awt): java.awt.Dialog.ModalityType:
typically used as a parameter to some method(or c'tor) in order to
specify a certain behaviour for some object.

Normal applications using it, will most likely merely name such a
constant upon construction of a Dialog.

Then there might be some GUI-Construction tool, that needs to save
options used with Dialogs, but they're more likely to closely follow
development of the library and just release an update with a manual
re-mapping of that one word upon loading back old files. (and they
might even be happy about the change for no longer receiving bug-
reports about what their users may believe to be their typo.)

The enum that triggered my OP was of quite a similar usage pattern.

Considerations of 99%-uses versus 1%-uses may be relevant for the
decision on whether to accept an incompatible change or not - at
least at some major-version bump time...
[...] What I took away was that sentence ("...set in
stone") and that there is quite a number of surprising and subtle ways
failure can be caused by API changes. That had two effects: it made me
think thrice whenever I am tempted to change an API; and I try to give
extra thought whenever I am designing an API (not only the public ones
as often all of a sudden what has been private becomes public).

yeah, yeah, avoiding mistakes in the furst(*) place is always best.

(*) ;-)
 
M

markspace

Not quite as simple. I'd need a *copy* of each method (even as an
overload) taking the new Fooing, while the original would be
changed to @Deprecated and left at taking the Foo argument.

@Deprecated void applyRank(Foo rank) { ... blah ... }
void applyRank(Fooing rank) { ... mostly the same blah ... }

OK I see what you mean. You'd have to duplicate your own API everywhere
for the new type. Maybe

public enum Foo { @Deprecated FURST, SECOND, FIRST }

public void applyRank( Foo rank ) {
if( rank == FIRST || rank == FURST )
//...
else
// second ...

Which I think you will have to do with your solution as well.

My previous solutions allows you to migrate the entire API to the new
type. Yours allows you to keep the old type. I think you're stuck with
"FURST" forever though if you keep the old type.
 
A

Andreas Leitgeb

Eric Sosman said:
It'd make a mess of EnumMaps, too ...

Nope, don't see what you mean here. The synonyms wouldn't be
separate extra instances, of course, so by the time I'm holding
an instance, its .name() is the canonical one.
Also, if "C" is a canonical constant of enum Foo, and "S" a
synonym of "C" in Foo, then (Foo.S==Foo.C) would be true.

Part of that "extension" can be mimicked by creating a static
final field in the enum by synonym's name initialized to its
canonical meaning.

Maybe valueOf() for a particular enum could be patched by
means of bytecode "instrumentation" to fall back to some
HashMap<String,Foo> of synonyms, if canonical name lookup
fails. Not sure, if the bytecode verifier would accept
thusly patched valueOf() implementations upon loading
Foo.class

Unfortunately, it is not feasible to hook into the compiler
to make it accept arbitrary static final fields of the enum
as switch case-labels.
I don't think there's any single Right Way to address your
problem, no one-size-fits-all solution. Elsethread it's written
that a published API is "set in stone," but there are many kinds
of stone. Of all posters to this thread, only you know whether
the stone is obsidian or talc; only you know how hard a tool is
required to scratch it.

Nice parable :) Yep, there's some material analysis ahead.
 
A

Andreas Leitgeb

markspace said:
OK I see what you mean. You'd have to duplicate your own API everywhere
for the new type. Maybe

public enum Foo { @Deprecated FURST, SECOND, FIRST }

public void applyRank( Foo rank ) {
if( rank == FIRST || rank == FURST )
//...
else
// second ...

Which I think you will have to do with your solution as well.

Not with the "extra-static-final-field" approach.
As my FIRST and FURST would both refer to the identical instance,
there'd be no need to check equality with both of them. Instead,
comparing equality with *either* of them does the job in this
case, which is nice w.r.t compatibility (but not sufficient overall).
My previous solutions allows you to migrate the entire API to the new
type. Yours allows you to keep the old type. I think you're stuck with
"FURST" forever though if you keep the old type.

You're right, that the transition would be more smooth for the users your
way (not counting the discomfort of a less than canonically named enum).

In my approach, I might start with creating the extra field with
the corrected name, and leave the constant as is, allowing simple
users (who just use the constants directly) to change their code to
Foo.FIRST immediately, but those who use Foo more intensely will have
to adapt their program in sync with a future library upgrade that will
eventually swap FURST and FIRST.
 
E

Eric Sosman

Nope, don't see what you mean here. The synonyms wouldn't be
separate extra instances, of course, so by the time I'm holding
an instance, its .name() is the canonical one.
Also, if "C" is a canonical constant of enum Foo, and "S" a
synonym of "C" in Foo, then (Foo.S==Foo.C) would be true.

Sorry; I didn't pay close enough attention to the details
of your dream. :) Still, you've got to admit it might be a
wee bit confusing if you did

theMap.clear();
assert theMap.size() == 0; // Okay, it's empty.
theMap.put(Foo.C, "FIRST");
assert theMap.size() == 1; // Okay, one mapping.
theMap.put(Foo.S, "FURST");
assert theMap.size() == 2; // Why does this fail???
Nice parable :) Yep, there's some material analysis ahead.

Perhaps a trifle over-poetic, but there's serious intent.
There'll be some amount of pain no matter which route you take,
and you're better able to assess it than any of us is.
 
A

Arivald

W dniu 2013-11-15 17:25, Andreas Leitgeb pisze:
Let's assume, some non native English speaker writes up
a library, that's really useful and gaining broader use.

Now, the author may have once caught up some misspelling
of a common word (like e.g. thinking that "first" was
spelled "furst" and that with a confidence level ways
above "I'd better look this up").

Eventually this misspelling goes into an enum constant,
and by the time the first user reports it, the library
is already too much in use to lightheartedly apply
Plan "A" (namely fixing the typo and thereby inducing a
compatibility issue)

Now, Plan B could be to rename the enum constant, but add
a public static field by the old bad name (initialized
with the reference to the spelling-corrected field), to
alleviate the compatibility issue at least for those who
merely used the enum's field's name in their code.

// public enum Foo { FURST, SECOND }
public enum Foo { FIRST, SECOND; public static final FURST = FIRST; }

Any user having done a switch over that enum will still
need to modify his code. Ditto anyone doing string comparisons
on foo.name(), but that is most probably a bad idea, anyway.

I'd further denote "Just leave the typo in" as "Plan *Z*",
and ask for better strategies. (at least better than Plan Z)

PS: I vaguely remember that even Sun Java had some typos in its
API, but that predated enums, so adding a second constant with
same value but corrected name was a triviality back then...

PPS: a similar problem could arise with misspelled class names,
but that's not currently in my focus.

Leave current API as is. But with major upgrade of library, change
package name (ex com.me.superlib => com.me.superlib2), and in new
version use correctly spelled enums and other names.

Anyone who switch to new version of library will need to adapt and
refactor code anyway, so fixing misspelling wont be a problem.
 
A

Andreas Leitgeb

Eric Sosman said:
Sorry; I didn't pay close enough attention to the details
of your dream. :) Still, you've got to admit it might be a
wee bit confusing if you did

theMap.clear();
assert theMap.size() == 0; // Okay, it's empty.
theMap.put(Foo.C, "FIRST");
assert theMap.size() == 1; // Okay, one mapping.
theMap.put(Foo.S, "FURST");
assert theMap.size() == 2; // Why does this fail???

This would be true, only if that "feature" got explicitly misused.
(The mere possibility of misuse is often taken as an argument
against a feature, but I don't follow this logic, myself)

Putting it into context of corrected spellings, then it would
look more like this one:

theMap.clear();
assert theMap.size() == 0; // Okay, it's empty.
theMap.put(Foo.SUPERCALIFRAGILISTICEXPIALID0CIOUS, "1st");
assert theMap.size() == 1; // Okay, one mapping.
theMap.put(Foo.SUPERCALIFRAGILISTICEXPIALIDOCIOUS, "1st");
and the resulting size()==1 would be far less surprising.

Of course, no one would write such code, short of for trolling...
Normal code would contain only one variant. Mixtures would only
happen where a transition has been begun but not (yet) completed.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top