13

What is a good design for allowing backwards compatibility of a file type between different versions of software?

For instance, how does microsoft get word 2007, 2010 and 2013 etc... to all open docx files, but different editions can save more / less data and save the data in slightly different ways, all to the same file type, and a file that is saved in one version can be opened in another, but certain elements of the file might not be available in older versions?

I mean, the really obvious way to do it is to have something like

private string openfile(string filename)
{
    File.Open(filename)

    ... some logic that gets a header from the file that will never change

    switch (fileversion)
        case 2007:
            .....
        case 2010
            .....
        case 2013
            .....
}

but that seems incredibly monolithic, not very extensible, and likely to lead to a lot of copy / pasted code.

So I was thinking of using a base interface for all versions which defines the immutable structures, such as the header, that need to be present in the file, and methods that need to be available for serialisation / deserialisation, then multiple inheritance so that each new version's class that implements the interface inherits the old version, and only overrides stuff that has changed, since the file will be the same, for the most part.

I'm not really bothered about the structure of the file, since it's already decided that we'll be using XML, and the initial schema is, by and large, already decided. However there will no doubt be changes to it in the future, and I just want to be able to design the code in a way that makes it easy to accommodate these changes.

  • 6
    You should design the file format so that not only it ignores information that is missing because the source is from an earlier version, but also information that it doesn't expect because the source is from a newer version. If you're starting from scratch, please please please do forward compatibility as well. It's almost no extra effort and doubles the usefulness of your software. – Kilian Foth Jul 3 '15 at 8:08
  • On an open, will you always know up front (e.g., from the header) which file version you are dealing with? Also, to make another request, please please please check for corrupt or malicious files and don't let them cause problems. Your sysadmins will thank you :) . – cxw Jul 3 '15 at 10:11
  • 1
    Yes, the version number will always be in the file header, and the header format will never change. We're going with the idea that files created between minor software revisions should be compatible, i.e. a file created in v1.1 can be opened in v1.2 and vice versa, although some functionality from 1.2 might be missing in 1.1 but major revisions will break forwards compatibility, so stuff written in v2 won't open in v1, but stuff written in v1 will open in v2. – JJBurgess Jul 3 '15 at 10:28
  • And as for the corruption thing, the files contain DSL, and the program opening / closing them is a custom in-house IDE / compiler. These won't be going anywhere near a production environment, so the admin's needn't worry. – JJBurgess Jul 3 '15 at 10:30
10

You might have a look at the PNG file format and how it handles version compatibility. Every block has an id describing what kind of block it is, and it has some flags that tell the software what to do if it cannot understand that id. For example "you cannot read the file if you don't understand this block", or "you can read the file but not modify it", or "you can modify the file but you have to delete this block". For backward compatibility, your software just needs to handle the situation when any expected data is not present.

  • Great idea! The PNG format relies on features and not on versions. It does mean, however, that the basic format must never change. (i.e. the header defining the feature.) – Florian Margaine Jul 3 '15 at 12:45
  • That's interesting. I'm reading through the file specification at the moment. I like the idea of critical and ancillary chunks, and might try and work this in. – JJBurgess Jul 3 '15 at 13:19
3

A way of doing this can be by using a base class and interface with the basic functions for your file handling. Then use classes for each version that extend from the base class to handle all version specific cases. Functions that can change can be virtual in you base class of abstract if there are only version specific implementations. When you need a class to handle the file, use a factory that gets the version specific implementation of the file handling interface.

  • My only issue with this is that you'd end up duplicating version specific implementation for each subsequent revision. Lets say you have three base class methods: ReadNames(), ReadAges() and ReadAddresses() and in V2 of the class, you make a change to ReadAges(). If in V3, you then decide to make a change to ReadNames(), if all of your version specific classes are inheriting from the base, you'd lose your V2 changes, or you'd need to copy/paste the changes from V2 into the V3 implementation as well. – JJBurgess Jul 3 '15 at 9:05
  • 1
    The implementation of readages can call a different class which holds the actual implemention on how to read ages for this version. Making your class will be more configuration of interfaces/factories than actual programming. – peer Jul 3 '15 at 9:08
2

I have done this with XML and it works well:

Simply allow any element in your document, to have any attributes and any subelements (and when order is not important - in any order). Beginning from first version of program - when reading document, ignore attributes and sub-elements that you dont know in current version.

In future when you are adding new feature to new version of program then add attribute or sub-element. Older versions will ignore it. New version should check pressence of attribute or sub-element and handle with it.

For example you have some items with texts:

<item text="Hello, world!"/>

And in newer version you would like to add color to item so you add attribute color:

<item text="Hello, world!" color="008000"/>

Older version will simply ignore color attribute when opening document. New versions check for pressence of color attribute and if does not exist assigns default color.

With this simple solution you will have both backward and forward compatibility.

  • The slight issue with this as a "simple" option is that you'll strip all unexpected attributes (or keep them unchanged) when saving the document. As mentioned in other answers, a better solution at least determines in some version agnostic way as to whether an attribute should be dropped, kept or cause the document to become read-only for the versions that do not understand it. – Mark Hurd Jul 8 '15 at 1:58
  • @Mark Hudr: Yes, I am silently assuming that backward compatibility is must have and forward compatibility is bonus. When someone opens new document in old version of applications they should not be surprised that when they saves it, they lost something what is not already visible in old app. Additional logics seems overengineered to me. – user3123061 Sep 29 '15 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.