Modules
Colin Putney
cputney at wiresong.ca
Sun Feb 27 06:11:31 UTC 2005
On Feb 26, 2005, at 11:20 PM, Florin Mateoc wrote:
>> We have two versions of a method, both with complete version history.
>> Because we have the version history, it doesn't really matter that
>> the two versions come from different packages, it's exactly the same
>> as merging two versions of the same package. So instead of one
>> version overriding the other, we do a merge. By comparing the method
>> histories we can decide if one version supersedes the other. That
>> would mean that it's an updated version of the other, which means we
>> can rely on the user's wisdom again. If the user changed the method
>> from one of the versions we have to the other one, he must know what
>> he's doing. Therefore we use which ever version the user has already
>> chosen.
>
>
> I am sorry, but this is simply not true. A developer may choose, in a
> newer version of a class, to ignore some unrelated development, and
> stick to an older protocol, by including some older versions for some
> of the methods. This is not a made up example, I have encountered the
> situation quite often. You can easily have, as a simplistic example,
> PackageA>ClassB>methodC(version1),methodD(version2) and
> PackageE>ClassB>methodC(version2),methodD(version1). The automatic
> resolution will do the wrong thing, and it won't even inform the user.
Ok, let's get into this in excruciating detail, because it's not clear
to me why you think the above example cannot be resolved correctly.
Let's say I have the following program elements, drawn from your
example, with history.
PackageA
ClassB>>methodC.version1 (ancestors: version0)
ClassB>>methodD.version2 (ancestors: version0, version1)
ClassB>>MethodE.version1 (ancestors: version0)
PackageZ
ClassB>>methodC.version2 (ancestors: version0, version1)
ClassB>>methodD.version1 (ancestors: version0)
ClassB>>methodE.version2 (ancestors: version0)
Ok, so let's see what happens if we load both packages into the same
image. PackageA and PackageZ both define methodC, and they have
different definitions. So we've got to decide which version, if any,
will be in the image. The version in PackageA is called version1, and
it was derived from version0. The version in PackageZ is called
version2, and lists version1 as its ancestor. Therefore, version2 was
created by modifying version1. So we'll choose version2, from PackageZ.
MethodD has the reverse situation. PackageA's version is a descendent
of PackageZ's, so we'll choose version2 again, but this time from
PackageA.
MethodE presents a conflict. Both versions descend from a common
ancestor, but neither descends from the other. So we pop up a conflict
resolution window, and let the user decide what methodE should look
like when both packages are present. This results in a new version,
called version3. When we're done, the image looks like this:
ClassB>>methodC.version2 (ancestors: version0, version1)
ClassB>>methodD.version2 (ancestors: version0, version1)
ClassB>>methodE.version3 (ancestors: version0, version1, version2)
Now, you are correct to point out that, say, methodC.version2 might
have been developed in PackageZ without PackageA loaded, and so might
not work as PackageA expects. Perhaps we should indeed log to the
Transcript when a merge automatically resolves overlapping packages.
There is no *guarentee* that version2 will work right. But our chances
of success are better if we follow the intention of the developer of
version2, which was to replace version1. Following the order that
packages are loaded is little different than choosing at random.
> Making independently developed packages work together means
> (intelligent) work, and if there's any overlap, the chances of solving
> the issues automatically are, I believe, very slim, and versioning
> does not help. Even if all the common methods in one of the packages
> are newer versions (and descendants) of the same methods in the other
> packages, it still doesn't mean that they are made to work with the
> older package, it may simply mean that the newer package is supposed
> to work with a newer version of the older package. I think the only
> situation where you can say that there is no conflict is when the
> common methods are all identical, and for this you don't need
> versions. This is why, to my mind, overrides have nothing to do with
> versioning, they are simply a different kind of extension.
I agree that it takes intelligent work to make packages work together,
and I'm not suggesting that the computer can do that. I am suggesting
that, having done the work, we record the results so that we don't have
to do it again everytime we load those packages.
[snip]
>>> This is probably just the memory of a frustration with Envy: because
>>> it stores all these method editions (inluding every time you put a
>>> "halt" in a method), the noise level is pretty high, so I always
>>> wished that I could see at a glance, when looking at the list of
>>> editions for a method, which editions are "real". But even if we
>>> have explicit method versioning, so the noise is reduced, the most
>>> "real" ones are the ones associated with the holder's version,
>>> because there is an implicit minimal testing expectation for
>>> versions.For the method version I would expect something like a
>>> unit-test, for a class, the beginning of some functional testing.
>>> The expectation is even higher for the package, because it usually
>>> groups together classes working in tight coupling, so the testing
>>> done for a package version is more of a functional test, so now
>>> those methods "really" work. I guess it would be fun to disallow
>>> versioning if we detect that testing was not performed :) Seriously
>>> though, it might be interesting if we could link somehow versions to
>>> the tests performed.
>>
>>
>> Ok, I see. You just want to define a group of program elements that
>> should be versioned together. With Monticello you do this explicitly,
>> so there's a lot less noise. Everything is a "real" version, and they
>> correspond to a bunch of other "real" versions that were current at
>> the same time.
>
> Even if you do it explicitely, not all versioning happens at the same
> time.
> I develop a method, it looks good, I test it a little (workspace,
> unit-test, whatever), I am happy with it and I want to keep it. I
> version it (separately, because this is what method-level granularity
> means).
Interesting, because that's not what I mean by "method-level
granularity."
In MC1 (and Store, as far as I can tell), only packages have ancestry.
The ancestry of a method has to be reconstructed by examing all the
versions of the package it appears in and noting how it changes. In MC2
(and Envy, as far as I can tell), methods have individually recorded
ancestry. This is why I say that MC2 versions at method-level
granularity.
But that doesn't mean that you have to version new methods in
isolation, whether explicitly or with every accept as in Envy. If you
do that, you loose the spacial context I mentioned in my last post.
That version is just noise, so why bother? It's not like the method is
going anywhere. You can save your image without versioning it, and even
if you manage to crash the VM you can always pull it out of the change
log.
In MC1 you always version whole packages at a time. MC2 is more
flexible, in that you can specify other ways of separating the code you
are interested in from the rest of the image. But whatever your method
of segregation, you always version all of it at once. So in this sense,
Monticello versions at "project-level granularity," the project being
whatever you're working on, be it a package, change set or whatever.
It's important to do that so as to get the spacial context needed to
merge snapshots correctly.
Colin
More information about the Squeak-dev
mailing list
|