Wie mit API-Änderungen umgehen?

  • VB.NET
  • .NET 4.0

SSL ist deaktiviert! Aktivieren Sie SSL für diese Sitzung, um eine sichere Verbindung herzustellen.

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von petaod.

    Wie mit API-Änderungen umgehen?

    Ich suche Denkanstöße, wie ich bei Verwendung einer Anwendungs-API am besten vorgehen kann, um auf Änderungen an Funktionen zu reagieren, die "Object"s zurückgeben, die von unterschiedlichen Klassen stammen können.

    Das war die Kurzfassung, in der Langfassung muss ich leider etwas ausholen:

    Ich nutze zurzeit eine Anwendungs-API, also eine Programmierschnittstelle zur Automation einer Anwendung, ähnlich z.B. wie die MS Office API. Die Anwendung wird jährlich in einer neuen Version herausgegeben, Änderungen an der API inklusive. Ja... immerhin achtet der Hersteller darauf, dass bereits früher veröffentlichte Signaturen von Methoden unverändert bleiben, d.h. rein syntaktisch ist die API immer abwärtskompatibel - ich kann also eine eigene Applikation, die z.B. Version 18 der API nutzt, auch mit Version 22 der Anwendung zusammen verwenden.

    Trotzdem hat es kürzlich ein Problem damit gegeben, mein Programm ist mit einer bestimmten Änderung an der API bzw. dem darunterliegenden Kern nicht zurecht gekommen. Lasst es mich versuchen zu erläutern, indem ich etwas "Fiktives" im Office-Umfeld konstruiere, das die Situation beschreibt:

    Nehmen wir eine Excelzelle, die neben einem Textinhalt auch "aktive Inhalte" enthalten kann. Die Klasse Cell hat nun eine Methode GetType() und eine Funktion GetDefinition(). GetDefinition() liefert ein Objekt vom Typ Object zurück, was alles Mögliche sein kann. Und dieses "alles Mögliche" hat kein fest definiertes Interface oder eine abstrakte Basisklasse - es kann wirklich "alles" sein.
    Was man tatsächlich zurückbekommt, kann man vorher mit GetType() erfragen. Allerdings liefert GetType() nicht den exakten Namen der Klasse sondern mehr so eine Art Typkennzeichen oder einen Sprechenden Namen. Zwei bestimmte Typen (sagen wir mal "MP3-Data" und "Video-Data") resultieren aber nicht in zwei verschiedenen Objekten, die GetDefinition() zurückgibt, stattdessen bekommt man in beiden Fällen ein Objekt vom Typ "VideoData". Sollte es sich eigentlich um MP3-Daten handeln, erhält man also im Grunde eine Art kastrierten Video-Stream ohne Bild.
    Natürlich ist das nicht so dolle, bestimmte Funktionalitäten die MP3 bietet ließen sich so nicht nutzen, also gibt es in einer späteren Version ein Update. Es kommt eine neue Klasse AudioData hinzu, alle anderen bleiben unverändert, nur dass jetzt GetDefinition() im Fall, dass in der Zelle MP3-Daten stecken, eben nicht mehr ein VideoData- sondern ein AudioData-Objekt zurückgibt.
    (Ja, ich weiß, ziemlich konstruiert, aber ich hoffe es bleibt dennoch verständlich.)

    Bis hierher ist eigentlich alles logisch, und man sollte annehmen, dass man das locker handhaben können sollte. Wenn ich versuche das in der neuen Version zurückgegebene Objekt vom Typ AudioData in ein VideoData-Objekt zu casten (weil meine Anwendung wegen Nutzung einer älteren API die neue Klasse noch gar nicht kennen kann), sollte man eigentlich davon ausgehen können, dass eine Exception auftritt, die mir unmissverständlich mitteilt, dass hier gar kein VideoData-Objekt vorliegt. Das könnte ich abfangen und entsprechend reagieren.

    Dummerweise wäre das zu schön um wahr zu sein. In meinem konkreten Fall geschah etwas völlig Seltsames, für das ich keine richtige Erklärung, höchstens Hypothesen habe. Der Cast in ein VideoData-Objekt warf nämlich mitnichten eine Exception, die Objektvariable war auch nicht Nothing - ich hatte es also augenscheinlich weiterhin mit einem ganz normalen Objekt zu tun. Sobald ich jedoch versuchte, auf einen Member (Property, Methode, egal) zuzugreifen, gab es Ausnahmefehler der Art "ungültiger Speicherzugriff".

    Mein Erklärungsversuch:
    Die eigentliche Anwendung ist vermutlich keine .NET-Anwendung, sondern nativ programmiert, sehr wahrscheinlich mit C++. Die API für den Zugriff unter .NET auf die Programmfunktionen ist dementsprechend vermutlich ein C++/CLI Wrapper. Wie auch immer der programmiert ist, ich habe den Verdacht, dass hier munter mit Pointern auf die eigentlichen C++-Klassen herumhantiert wird, ohne zu prüfen, ob die auch wirklich passen. Ich weiß nicht, ob das überhaupt gehen kann oder wie man das programmieren könnte, aber scheinbar gibt das oben genannte GetDefinition() einfach ein .NET-Platzhalter-Objekt zurück, in dem der Wrapper intern einfach einen Pointer auf das tatsächliche Objekt speichert. Der Versuch, dieses Objekt nun in ein VideoData-Objekt zu casten sorgt dann wohl dafür, dass der Wrapper einfach ein neues Objekt der Zielklasse erstellt und ihr dann den zuvor gemerkten Pointer unterschiebt, ohne zu prüfen, ob Pointer und Klasse wirklich zueinander passen.
    Das würde erklären, warum ich zwar ein gültiges .NET-Objekt der "gewünschten" Klasse, bei Property- oder Methoden-Zugriffen darauf aber Zugriffs-Verletzungen erhalte.

    Ich musste nun zur Problemlösung für Kunden, die neuere Versionen der automatisierten Anwendung einsetzen, dem Projekt einen Verweis auf die aktualisierte API hinzufügen (damit es die neue Klasse kennt) und sie dann an der passenden Stelle benutzen. Das Unschöne daran ist, dass das Projekt bzw. das daraus resultierende Produkt damit nicht mehr abwärtskompatibel zu älteren Versionen ist. Jeder weitere Kunde, der unsere Software einsetzen will, ist gezwungen, ebenfalls die neueste Version der fernzusteuernden Anwendung anzuschaffen. Böse Zungen in unserer Firma behaupten bereits, dieser Nebeneffekt des API-Updates sei volle Absicht des Herstellers. Ich neige dazu ihnen zu glauben.

    Ich sehe keinerlei Möglichkeit, mein Projekt so zu gestalten, dass es auch in neueren Versionen der Anwendung mit der älteren API mit MP3-Data-Zellen zurecht kommt, schließlich erhalte ich auf keinem Weg mehr aus der Zelle ein (wenn auch verkapptes) VideoData-Objekt, sondern fortan nur noch Objekte einer für die alte API unbekannten Klasse zurück.

    Was ich aber mindestens tun möchte, für weitere ähnlicher "Erweiterungen" vorzubeugen und wenigstens abzufangen, ob das zurückgegebene Objekt überhaupt ordentlich funktioniert, nachdem das Casten in den erwarteten Typ schon keine Ausnahme wirft, obwohl das Objekt dahinter "eigentlich" gar nicht mehr von diesem Typen ist.

    Was würdet ihr vorschlagen, welchen Tests ich das zurückgegebene Objekt unterziehen kann, dass Zugriffe auf seine Member wegen eines vermutlich ungültigen Zeigers im .NET-Wrapper keine Zugriffs-Ausnahmen erzeugen bei denen mir der ganze Kram um die Ohren fliegt? Damit ich als mögliche Reaktion z.B. die Verarbeitung so einer Zelle mit MP3-Daten mit einer entsprechenden Info an den User einfach auslassen und weitermachen kann, als wäre der Inhalt für den weiteren Programmablauf irrelevant.

    Sorry für die Textwand, ich hoffe, ihr konntet meinen Ausführungen bis zum Ende folgen.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Arby schrieb:

    indem ich etwas "Fiktives" im Office-Umfeld konstruiere
    Bis dahin hab ich gelesen.
    Bau Dir einen Wrapper um diese API, die dafür sorgt, dass Dein Overhead gleich bleiben kann.
    Wenn doch Änderungen erforderlich sind, ändere einen Parameter oder den Namen einer Prozedur, da meckert der Compiler alle Aufrufstellen an und Du kannst dies offline beheben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Lege dir alle bekannten GetType-Werte mit zugehoeriger Klasse in nem Dictionary ab. Existiert mal ein Wert dort nicht, kannst du ueberspringen.
    Ich gehe jetzt natuerlich davon aus, dass sich der Wert von GetType mit aendert, wenn sowas passiert, war jetzt nicht ganz eindeutig.

    RodFromGermany schrieb:

    Bis dahin hab ich gelesen.

    Das ist schlecht. Der wichtige Teil kommt nämlich erst danach. Trotzdem danke dass wir drüber gesprochen haben...

    Artentus schrieb:

    Ich gehe jetzt natuerlich davon aus, dass sich der Wert von GetType mit aendert, wenn sowas passiert, war jetzt nicht ganz eindeutig.


    Genau das passiert nicht. GetType() hatte schon vorher wie nachher darauf hingewiesen, dass da "MP3-Daten" in der Zelle sind, und in der alten Version gabs dann halt ein Video-Objekt ohne Bild und in der neuen Version ein Audio-Objekt.

    Schon beim Schreiben gestern Abend ist mir der Gedanke gekommen, den @WhitePage anspricht und mich gefragt, ob ich da in dem Moment einfach nicht dran gedacht hatte oder warum ich es sonst nicht ausprobiert hatte. Jetzt ist es zu spät, weil ich durch das Update der API im Projekt jetzt sowieso nicht mehr zum alten Zustand zurückkomme um die Machbarkeit zu testen. Ich weiß auch nicht mehr genau ob es tatsächlich eine fangbare .NET-Exception gab oder nicht doch etwas "schlimmeres" passiert war (sowas wie "Dieses Programm funktioniert nicht mehr"). Der Vorfall ist schon ein paar Monate her und ist jetzt nochmal angesprochen worden wg. zukünftiger Aussagen zur Versionskompatibilität.

    Edit:

    RodFromGermany schrieb:

    Bau Dir einen Wrapper um diese API

    Nein, ich werde mit Sicherheit KEINEN zusätzlichen eigenen Wrapper bauen. Erstens besteht die API aus hunderten Klassen mit tausenden Funktionen. Bis ich das benutzbar fertig habe, vergehen Wochen. Zweitens ist die API bereits ein .NET Wrapper, ich sehe also nicht wo der Vorteil sein soll, weil ich am Ende das Problem lediglich von meiner Anwendung in den Wrapper verlagere.
    Es hätte wirklich mehr Sinn gehabt zu antworten, wenn du vorher den ganzen Text gelesen hättest. Ich hab mir die Mühe nicht ohne Grund gemacht.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Arby“ ()

    Ach so, was ich vorhin vergessen habe: Ich denke, du hast Recht mit Speicherzugriffen, dass da einfach der Pointer gegeben wird, ohne große Prüfung...

    Mir fällt aber auf die Schnelle nichts ein, womit man sonst testen könnte, ob die Umwandlung wirklich funktioniert hat, außer einfach im TryCatch auszuprobieren. Wenn der API-Hersteller keine Methoden oder Eigenschaften bereitstellt, um sie auseinander zu halten, wie sollst du es sonst feststellen?
    @WhitePage
    Ja, das ist wirklich ein Kreuz. Das wirklich bekloppte daran ist, dass es nichtmal eine vernünftige Beschreibung gibt.
    Um beim oben konstruierten Beispiel zu bleiben: Ich wusste zwar, dass in der Zelle "MP3-Daten" enthalten sind, es gabe aber keine Klasse, die so oder so ähnlich hieß um die Inhalte von zu kapseln. Ich musste tatsächlich beim Debuggen einfach ausprobieren und versuchen, das erhaltene Objekt in mögliche passende andere Klassen zu casten, bis ich dann irgendwann rausfand dass "VideoData" das richtige war. Ziemlich frustrierend. Einziger Anhaltspunkt für die Suche war, dass die meisten Klassen, die bei GetDefinition() rauskommen können, im Namen auf "Data" enden.

    Da fällt mir grad was ein... ich muss mal was ausprobieren...
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Arby schrieb:

    vernünftige Beschreibung
    Kannst Du da bei der dortigen Entwicklung jemanden per Telefon oder Mail kontaktieren?
    Üblicherweise sind die froh, wenn sie von einem Entwickler Response bekommen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Hab grad nochmal was ausprobiert. Zur Info: Mit Benutzung der aktualisierten API schlägt der Cast in die früher zu erwartende Klasse tatsächlich fehl. Aber das hilft nicht wirklich weiter, denn das war mit der API der älteren Version im Zusammenspiel mit der Anwendung in neuerer Version definitiv nicht der Fall. Und ich will ja eigentlich vermeiden, wegen sowas immer die aktuellste API zu nutzen und damit nur noch Kunden bedienen zu können, die ebenfalls jeden Versionssprung bei der fernzusteuernden Anwendung mitmachen.

    Na ja, ich hab jetzt erstmal alle entsprechenden Stellen mit auf diesen Fall bezogenen Try-Catch-Blöcken gekapselt, so wie auch @WhitePage es vorschlug, in der Hoffnung dass bei zukünftig ähnlich gelagerten Fällen wirklich eine fangbare Exception auftritt und nicht gleich der ganze Prozess stirbt.

    @RodFromGermany
    Ja, der Hersteller ist bereits informiert. Rückmeldung steht allerdings noch aus und nach bisherigen Erfahrungen verspreche ich mir davon ehrlich gesagt nicht viel.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.