bertrand@eiffel.UUCP (Bertrand Meyer) (05/10/89)
Perfect reusable components are not obtained at the first shot. Yet if one is aiming at a full-fledged industry of reusable software components (as the goal is with Eiffel), perfection is what we should strive for. (Eventually, that is.) This raises the question of what you do when you have produced a first version of a reusable class, or even a second and a third, and you realize that you could have done better. Two spirits are at odds: - The GROP (Great Temptator Of Perfectionism), who says: ``Correct it here and now before it is too late!'' - The GAIB (Guardian Angel of the Installed Base), who says: ``Think of the current users!'' Both have advice worth listening to and it would be nice to be able to placate them both. In Eiffel 2.2 we have devised what I hope will be a long-term solution by recognizing that, careful as they may be, developers of Eiffel libraries (including us, the developers of the basic libraries) will always run into cases in which, after releasing a library class, they suddenly experience what in French is called ``esprit de l'escalier'' or ``wit of the staircase'': a great thought which unfortunately is an afterthought, like a clever reply that would have stunned all the other dinner guests - if only you had thought of it before walking down the stairs after the party is over. If the afterthought is the realization that a significant overhaul of the class would make it considerably better, what you should do is go ahead and rewrite the class. If you want to appease the GAIB, you can leave the old version around but cease to document it in the official documentation. This is what we did in version 2.1 (July 1988) for binary search trees. Here the initial design was not ``wrong'', but the introduction of constrained genericity into the language made it possible to have a much nicer version of the class. For compatibility we left the old class under a different name. The new version (BIN_SRCHTREE) is the only one that is documented in the manuals. Of course, if you uncover a serious design mistake in the original version you shouldn't leave it around. In this case the GROP wins handily. A more subtle case is one in which there is nothing fundamentally wrong with the functions offered but you realize the interface to these functions (in terms of exported routines and their signatures) could be improved. This includes three important example situations: 1. - You can think of a better name for a routine. 2. - You want to advise programmers not to use the routine any more. 3. - You can think of a better signature. Situation 1 may occur as you are doing an after-the-fact cleanup of your library and realize that naming conventions could be made more consistent. Situation 2 may arise when the routine's action is not needed any more (as with a routine which performed some initialization which you later realize can be carried out automatically on object creation). Situation 3 may occur (among other cases) when you realize that a routine has too many arguments and should be split into two or more routines. For example, you may have a procedure adding a subwindow to a window, under the form w.add_subwindow (other_window, horizontal_position, vertical_position) but then you realize that it would have been better to omit the last two arguments and have the subwindow be initially positioned at the top left corner of the parent window, and let clients move it if needed by using a specific ``move'' procedure, which is needed anyway. (This is a hypothetical example, although I believe it corresponds to guidelines that are usually good. In general, a routine should only include among its arguments what may be called ``indispensable arguments'', as opposed to ``options''; an option is recognized by the class's ability to set reasonable default values, as for horizontal_position and vertical_position in this example - whereas there is no reasonable default for other_window, which is thus an indispensable argument. According to these guidelines, options should be set separately, using specific procedures such as ``move''. Again these are only guidelines; exceptions may be justified. Some of these issues were discussed in an old and otherwise rather obsolete article of mine, ``Principles of Package Design'', Communications of the ACM, July 1982.) Cases 1 and 3 above often involve changes small enough that it is tempting to heed the GAIB's advice and resist any change at all. But in the long term this is dangerous. Here the GAIB is really a front-man for the hideous DECHOP (Devil of Eternal Compatibility with the Horrors Of the Past) which, among its many claims to infamy, gave us such programming languages as Fortran 8X. What we want is a way to gently phase out old features without punishing early users. This is what has been attempted with the ``obsolete'' facility in Eiffel 2.2. The simplicity of this facility belies the preceding rather lengthy prologue. Syntactically, it is a notation that applies to routines. The syntax of a routine normally begins with routine_name (arguments) is ... routine body ... A routine may now be declared ``obsolete'' by adding a clause of the form obsolete "Message" just before the ``is''. ``Message'' is any string. [Its presence is optional.] Here is an example taken from the new ARRAY class in the Basic Library: entry (i: INTEGER): T obsolete "Use ``at'' instead" is -- Entry of index i. -- Applicable only if is between currently defined bounds. require index_large_enough: lower <= i; index_small_enough: i <= upper external ... do ... Instructions ... end; -- entry Other than for its ``obsolete'' clause, such a routine is a normal one. It may or may not appear in the ``export'' clause of the class (this one does). Only in two ways do the properties of an ``obsolete'' routine differ from those of a non-obsolete one: - The ``short'' command, an essential tool of the Eiffel environment, producing class interface documentation automatically from the text of a class, will not show obsolete routines in its output, whether or not such routines are exported. - The Eiffel compiler (commands ``es'' or ``ec'') will flag the first use of any obsolete routine in a system by outputing a warning of the form Warning: routine xxxx is now obsolete. Message where ``Message'' is the string that has been given in the ``obsolete'' clause, if any. In the above example, this message tells the programmer of a dependent class (client or descendant) that the routine ``at'' is now the preferred way to access an array element, although ``entry'' remains valid. The compiler messages may be turned off if desired through an option. The use of obsolete routines by dependent classes has no effect other than the above warning. The understanding, however, is that obsolete features will be removed from the library classes in some later release. Programmers are given ample time to update the dependent classes. Because they cease to be documented in the official reference, obsolete routines pose no immediate threat to the complexity of the class as perceived by new users. (This is different from what would occur if both old and new features were kept as mere synonyms.) Several variants to the above syntax were considered during the design of this facility (as well as a convention that would have been outside the language proper, such as keeping a list of obsolete features in a special file). The solution retained seems to be the most convenient. The modification of an existing class to make a routine obsolete is easy: just add the ``obsolete "Message"'' clause, leaving everything else unchanged. (It may also be a good idea to move the routine to the end of the class.) Rather than an explicit ``Message'' it could have seemed preferable just to include a reference to the replacing routine, since in practice many actual ``Messages'' will be of the form exemplified above (``Use xxxx instead''). This, however, would not have taken care of case 3 (where the recommended replacement is a routine with a different signature, as in the add_subwindow example); it is also not applicable to case 2 (a routine for which the recommendation is simply not to use it any more). As a minor drawback of the introduction of the ``obsolete'' facility, the ``short'' command is somewhat slower in version 2.2 than in previous releases; previously it relied on a one-pass analysis of the text, but now it must first check for the presence of any obsolete feature. The penalty should be acceptable in practice. (We considered alternative forms of the facility that would have averted this problem by declaring all obsolete features at the beginning of the class, but this turned out to be inadequate conceptually.) The ``obsolete'' facility is not a panacea. As we have seen, it is not applicable when you find a real design mistake (for which the only solution is to correct the mistake) or for an in-depth redesign of a class (such as has been applied to the entire Eiffel Graphics library for version 2.2). It is also not appropriate when you want to provide two or more synonyms for a given feature, none of which is obsolete, as in the example discussed in a previous message (<137@eiffel.UUCP>) where ``top'' and ``at'' are synonyms in STACK classes. As it stands, however, I hope this facility will help us provide a smooth transition path as we continue to refine every possible detail of the Eiffel Libraries. I also hope that it will prove useful to developers of other libraries of reusable Eiffel components. -- Bertrand Meyer bertrand@eiffel.com
keithc@atlantis.UUCP (Keith Campbell) (05/12/89)
In article <138@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: > Situation 3 may occur (among other cases) when you realize that a >routine has too many arguments and should be split into two or more >routines. > w.add_subwindow (other_window, horizontal_position, vertical_position) So, the feature invocation above must be replaced by two calls: w.add_subwindow( other_window ); other_window.move( horizontal_position, vertical_position ); Is the old version of ``add_subwindow'' (with three arguments) still available? If so, this violates the rule that a class has at most one feature of a given name. If not, it would seem that in this case the ``obsolete'' facility does nothing for the "early" users of this class. -- Keith Campbell Cognos Incorporated S-mail: P.O. Box 9707 Voice: (613) 738-1338 x5222 3755 Riverside Drive FAX: (613) 738-0002 Ottawa, Ontario UUCP: uunet!mitel!sce!cognos!keithc CANADA K1G 3Z4
bertrand@eiffel.UUCP (Bertrand Meyer) (05/14/89)
From <6099@atlantis.UUCP> by keithc@atlantis.UUCP (Keith Campbell): > Is the old version of [a routine being replaced by a version with a > different interface] still available? If so, this violates > the rule that a class has at most one > feature of a given name. If not, it would seem that in this case the > ``obsolete'' facility does nothing for the "early" users of this class. Clearly in this case the new feature should have a different name. This should have been explained clearly in my original note. -- -- Bertrand Meyer bertrand@eiffel.com
keithc@atlantis.UUCP (Keith Campbell) (05/16/89)
In article <143@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
->From <6099@atlantis.UUCP> by keithc@atlantis.UUCP (Keith Campbell):
-> > Is the old version of [a routine being replaced by a version with a
-> > different interface] still available? If so, this violates
-> > the rule that a class has at most one
-> > feature of a given name. If not, it would seem that in this case the
-> > ``obsolete'' facility does nothing for the "early" users of this class.
-> Clearly in this case the new feature should have a different name.
->This should have been explained clearly in my original note.
This is not always possible. In fact, it is not possible to rename the
feature in your window example: Create. What do we do now?
--
Keith Campbell Cognos Incorporated S-mail: P.O. Box 9707
Voice: (613) 738-1338 x5222 3755 Riverside Drive
FAX: (613) 738-0002 Ottawa, Ontario
UUCP: uunet!mitel!sce!cognos!keithc CANADA K1G 3Z4
bertrand@eiffel.UUCP (Bertrand Meyer) (05/26/89)
From <6121@atlantis.UUCP> by keithc@atlantis.UUCP (Keith Campbell): > [It] is not always possible [to let an obsolete feature remain available > under a different name]. In fact, it is not possible to rename the > feature in your window example: Create. What do we do now? Indeed this does not work for Create this there is exactly one Create procedure per class. A later posting on changes planned for Eiffel version 3 (late 1989) will explain plans for removing this requirement on Create procedures. Currently, however, if the interface of Create is changed there is now way to avoid adapting clients. -- -- Bertrand Meyer bertrand@eiffel.com
bertrand@eiffel.UUCP (Bertrand Meyer) (05/26/89)
Article <144@eiffel.UUCP>, line 8, should have read: Indeed this does not work for Create since there is exactly one Create... ^^^^^ Sorry for the typo. -- Bertrand Meyer bertrand@eiffel.com