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