AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

DLL/Lib: Parameter Validierung

Offene Frage von "Zacherl"
Ein Thema von Zacherl · begonnen am 28. Mär 2018 · letzter Beitrag vom 28. Mär 2018
Antwort Antwort
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#1

DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 16:43
Hallo zusammen,

mal ein etwas kontroverses Thema: Ich schreibe eine Library (in C; also keine Exceptions) mit einigen Funktionen, die exportiert werden (also als API für Entwickler; nicht als User-Interface). Wie gehe ich vor?
  1. Parameter durch Assertions validieren
  2. Parameter validieren und ggfls. mit Error-Code returnen
  3. Parameter zuerst mit Assertion validieren und als Fallback mit Error-Code returnen

Zu 1.:
Pro:
  • (Programmier-)Fehler während der Entwicklung schmeißen unmittelbar eine Exception
  • Kein Runtime Overhead im Release Modus
Neutral:
  • Validierung von (Benutzer-)Eingaben obliegt hier vollständig dem Nutzer der Library (müsste beim Fuzzen dann beispielsweise manuell vorgenommen werden)
Contra:
  • Assertions lassen sich abschalten
  • Fehlerhafte Parameter könnten zum Crash/Data-Corruption im Produktivbetrieb führen, wenn sie zur Entwicklungszeit z.B. einfach nicht aufgetreten sind
  • Für manche Entwickler bedeutet ein Crash/Assertion innerhalb einer Library = Bug

Zu 2.:
Pro:
  • Fehler, die während der Entwicklung nicht aufgetreten sind, könnten im Produktivbetrieb keinen großen Schaden anrichten
  • Fehler können im Produktivbetrieb sehr einfach geloggt werden
Contra:
  • Error-Codes können ignoriert werden, was dazu führt, dass (Programmier-)Fehler während der Entwicklung ggfls. nicht auffallen und behoben werden
  • Runtime Overhead durch - teilweise etliche redundante - Checks

Momentan fahre ich mit Runtime-Checks für die exportierten Funktionen und verwende Assertions nur intern. Funktioniert für mich so eigentlich, aber auf der anderen Seite machen sich die Checks zur Laufzeit bei Zeitkritischen Operationen teilweise doch bemerkbar. Außerdem wird der Code hierdurch unnötig komplex und wenn man den Gedanken mal weiterspinnt: Eine weitere Library verwendet intern meine Library, validiert aber in ihrem Public Interface auch erst noch Parameter, etc. dann werden sich die redundanten Checks irgendwann ziemlich häufen.

Eine weitere Überlegung zu diesem Thema sind Zeiger. Prüft man die explizit auf NULL /nil ? Oder lässt man es gleich sein, da ein Null-Pointer nicht schlimmer ist als irgendein anderer ungültiger Zeiger (auf den man eh nicht gescheit testen kann)?

Mich würde mal interessieren, wie ihr da vorgeht. Insbesondere bei professioneller (nicht Endanwender-Software).

Viele Grüße
Zacherl
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)

Geändert von Zacherl (28. Mär 2018 um 17:40 Uhr)
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

AW: Library: Parameter Validierung

  Alt 28. Mär 2018, 16:50
Das hängt doch auch stark von der Art und Weise ab wie deine Sachen konsumiert werden. Über REST? Mit Dingen wie "nil" und Assertions hört sich das so an als gehe es direkt um eine DLL.

Wenn Geschwindigkeit dir so wichtig ist dann biete doch einfach zwei Varianten an: Eine mit Validierung, eine ohne. So etwas sieht man ja sogar in der Delphi-RTL, beispielsweise TBitConverter.From<T> vs. TBitConverter.UnsafeFrom<T> (DocWiki).

Dann kann jeder nehmen was er für richtig hält und sich selbst braucht man auch keine Vorwürfe machen dass man die Eingaben nicht vernünftig geprüft hätte.
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#3

AW: Library: Parameter Validierung

  Alt 28. Mär 2018, 17:07
Mit Dingen wie "nil" und Assertions hört sich das so an als gehe es direkt um eine DLL.
Ja, bzw. statische Libraries (primär in C).

Wenn Geschwindigkeit dir so wichtig ist dann biete doch einfach zwei Varianten an: Eine mit Validierung, eine ohne
Das ist leider nicht so einfach bei komplexen Funktionen, bei denen die Parameter teilweise erst konditional ausgewertet werden (müssen). Ohne den Code dann praktisch zu duplizieren, ist das nicht machbar - außer man regelt das über einen Compiler-Switch global für die ganze Lib.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#4

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 17:16
Kannst du denn ein abstraktes Beispiel geben? Mir fehlt dazu die Vorstellungskraft. Holt man sich bei deiner APIs denn auch Bezeichner wie z.B. Handles ab und übergibt die später anderen Routinen?
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.116 Beiträge
 
Delphi 12 Athens
 
#5

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 17:33
Zitat:
1. Parameter durch Assertions validieren
2. Parameter validieren und ggfls. mit Error-Code returnen
3. Parameter zuerst mit Assertion validieren und als Fallback mit Error-Code returnen
Ich baue grundsätzlich immer nur eine Art der Fehlerprüfung ein und mische nicht. (also fast wie 2)

Maximal wird intern zusätzlich nochmal ein Fehlerflag (ErrorCode/-Text) gespeichert, welchen man nachträglich nochmal prüfen kann, so ala GetLastError.
Dafür sind grade Assertions da, dass man sie deakivieren kann, damit es schneller geht. Aber wenn man sie deaktiviert, dann hat man auch mit Fehlerverhalten zu rechnen, wenn die Eingaben doch ma nicht stimmen.
Also ist es IMHO sinnlos und kontraproduktiv, wenn zusätzlich noch eine zweite Eingangsprüfung vorhanden wäre. (3)

Und meistens nehme ich inzwischen Exceptions, zur Fehlerbehandlung, anstatt einem Fehlercode-Result.
Dazu definiere ich mir meistens mindestens eine eigene Exceptionklasse, je Library.
Diese Klassen können dann auch Zusatzinfos enthalten, siehe ErrorCode in EOSError oder EInOutError.

Zitat:
Contra:
* Assertions lassen sich abschalten
Das ist doch eher ein Pro, für den Entwickler.


Gerade bei Komponenten sollte man nie Fehler dierekt anzeigen, also bleiben da nur
* Exceptions
* Boolean-Result und interner Statusspeicher ala GetLastError
* oder Fehlercodes

Und Fehlercodes, also quasi MagicNumbers findet ich etwas blöd, darum habe ich mich für Exceptions entschieden.
Sind die Exceptions aber lokalisierbar (ResourceStrings oder sonstwie), dann sollte die Exceptionklasse auch statische Fehlerinfos beinhalten (z.B. immer englischer Text oder Fehlercode).
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests

Geändert von himitsu (28. Mär 2018 um 17:38 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#6

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 17:40
Kannst du denn ein abstraktes Beispiel geben? Mir fehlt dazu die Vorstellungskraft. Holt man sich bei deiner APIs denn auch Bezeichner wie z.B. Handles ab und übergibt die später anderen Routinen?
Ja, im Grunde genau so.

Wobei das auch nicht wirklich einen Unterschied macht, da die Fragestellung z.B. auch auf eine einfache Wurzel-Funktion anwendbar ist. Sagen wir ich habe einen Algorithmus, der die Wurzel einer Zahl berechnet und der auch für negative Zahlen ein (sinnloses) Ergebnis liefert. Prüfe ich jetzt per Assertion auf Zahlen < 0 oder implementiere ich einen "richtigen" Check, der ggfls. einen Error-Code zurückgibt? Oder kombiniere ich sogar beide Varianten?

Im Falle der Assertion kann es sein, dass der Programmierer meine Funktion immer nur mit positiven Zahlen füttert, der Endanwender aber etwas Negatives übergibt und somit ein unerwartetes Ergebnis bekommen würde (wenn der Entwickler nicht manuell die Benutzereingabe vorher validiert, bevor er sie meiner Funktion übergibt).

Mit Runtime-Checks würde die - zur Entwicklungszeit nicht getestete - Eingabe dazu führen, dass eine Fehlermeldung angezeigt wird (vorrausgesetzt der Entwickler prüft den Error-Code).

Dafür sind grade Assertions da, dass man sie deakivieren kann, damit es schneller geht. Aber wenn man sie deaktiviert, dann hat man auch mit Fehlerverhalten zu rechnen, wenn die Eingaben doch ma nicht stimmen.
Also ist es IMHO sinnlos und kontraproduktiv, wenn zusätzlich noch eine zweite Eingangsprüfung vorhanden wäre.
Mhh, aber dass Assertions im Release Modus aktiviert bleiben, ist (aus gutem Grunde denke ich) in praktisch keiner Standardkonfiguration gegeben. Die zusätzlichen Checks (3) wären ausschließlich hierfür gedacht, um undefiniertes Verhalten im Produktivbetrieb zu vermeiden, auch wenn mal ein Fehler während der Entwicklung nicht erkannt wurde (siehe z.B. das obige Beispiel der Wurzelfunktion).

Und meistens nehme ich inzwischen Exceptions, zur Fehlerbehandlung, anstatt einem Fehlercode-Result.
Ah, ja guter Punkt. Ich hätte vielleicht im Anfangspost erwähnen sollen, dass es sich im Speziellen grade um eine C-Library handelt (also keine Exceptions existieren).

Zitat:
Contra:
* Assertions lassen sich abschalten
Das ist doch eher ein Pro, für den Entwickler.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#7

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 17:45
Was ich immer noch nicht richtig zuordnen kann ist deine Sorge um Return values die der Nutzer vielleicht nicht auswertet und glaubt ein richtiges Ergebnis zu haben. Und glauben die Library sei fehlerhaft und "nicht gut". So etwas ist mir persönlich völlig fremd.

Ich würde, beispielsweise bei einem negativen Handle einen ordentlich dokumentierten Fehlercode zurückgeben. Wer eine Routine nimmt, ohne Doku zu lesen glaubt zu wissen was sie tut und sich dann freut dass es immerhin kompiliert - Damit hätte ich kein Mitleid. Wer dann undefinierte Werte in Ausgabeparametern hat und damit weiterarbeitet ist klar selber schuld.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.116 Beiträge
 
Delphi 12 Athens
 
#8

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 17:56
Exceptions gibt es im C auch, nur kennt C natürlich die Delphi-Exceptionklassen nicht und kann da dann nichtmal den im unbekannten Message-Text auswerten.

Nja, du kannst natürlich auch LongBool-Results nehmen (in C++ ist der BOOL gern 32 Bit groß),
definierst dir eine Reihe Fehlercodes, nach dem Muster der WinAPI, wie z.B. HRESULT,
und nutzt fleißig MSDN-Library durchsuchenSetLastError. (natürlich nur wenn es auch einen Fehler gab, also nur bei Result=False)

FACILITY ist ein Code für deine Komponente/Library.

Delphi-Quellcode:
{------------------------------}
{     OLE Error Codes          }
{------------------------------}

(*
  The return value of OLE APIs and methods is an HRESULT.
  This is not a handle to anything, but is merely a 32-bit value
  with several fields encoded in the value.  The parts of an
  HRESULT are shown below.

  HRESULTs are 32 bit values layed out as follows:

  3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  +-+-+-+-+-+---------------------+-------------------------------+
  |S|R|C|N|r|    Facility        |              Code            |
  +-+-+-+-+-+---------------------+-------------------------------+

  where

      S - Severity - indicates success/fail
          0 - Success
          1 - Fail (COERROR)

      R - reserved portion of the facility code, corresponds to NT's
              second severity bit.

      C - reserved portion of the facility code, corresponds to NT's
              C field.

      N - reserved portion of the facility code. Used to indicate a
              mapped NT status value.

      r - reserved portion of the facility code. Reserved for internal
              use. Used to indicate HRESULT values that are not status
              values, but are instead message ids for display strings.

      Facility - is the facility code

      Code - is the facility's status code
*)


const
  SEVERITY_SUCCESS = 0;
  SEVERITY_ERROR = 1;

function Succeeded(Status: HRESULT): BOOL; inline;
function Failed(Status: HRESULT): BOOL; inline;
function IsError(Status: HRESULT): BOOL; inline;
function HResultCode(hr: HRESULT): Integer; inline;
function HResultFacility(hr: HRESULT): Integer; inline;
function HResultSeverity(hr: HRESULT): Integer; inline;
function MakeResult(sev, fac, code: Integer): HResult; inline;

const
  { HRESULT value definitions }
  { Codes $4000-$40ff are reserved for OLE }

  S_OK = $00000000;
  S_FALSE = $00000001;

  NOERROR = 0;

  E_UNEXPECTED = HRESULT($8000FFFF);
  E_NOTIMPL = HRESULT($80004001);
  E_OUTOFMEMORY = HRESULT($8007000E);
  E_INVALIDARG = HRESULT($80070057);
  E_NOINTERFACE = HRESULT($80004002);
  ...
oder
Delphi-Quellcode:
{ DOS and OS/2 Compatible Error Code definitions returned by the Win32 Base
  API functions. }



{ Translated from WINERROR.H }
{ Error code definitions for the Win32 API functions }

(*
  Values are 32 bit values layed out as follows:
  3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  +---+-+-+-----------------------+-------------------------------+
  |Sev|C|R|    Facility          |              Code            |
  +---+-+-+-----------------------+-------------------------------+

  where
      Sev - is the severity code
          00 - Success
          01 - Informational
          10 - Warning
          11 - Error

      C - is the Customer code flag
      R - is a reserved bit
      Facility - is the facility code
      Code - is the facility's status code
*)


{ Define the facility codes }

const
  FACILITY_WINDOWS = 8;
  {$EXTERNALSYM FACILITY_WINDOWS}
  FACILITY_STORAGE = 3;
  {$EXTERNALSYM FACILITY_STORAGE}
  FACILITY_RPC = 1;
  {$EXTERNALSYM FACILITY_RPC}
  FACILITY_SSPI = 9;
  {$EXTERNALSYM FACILITY_SSPI}
  FACILITY_WIN32 = 7;
  {$EXTERNALSYM FACILITY_WIN32}
  FACILITY_CONTROL = 10;
  {$EXTERNALSYM FACILITY_CONTROL}
  FACILITY_NULL = 0;
  {$EXTERNALSYM FACILITY_NULL}
  FACILITY_INTERNET = 12;
  {$EXTERNALSYM FACILITY_INTERNET}
  FACILITY_ITF = 4;
  {$EXTERNALSYM FACILITY_ITF}
  FACILITY_DISPATCH = 2;
  {$EXTERNALSYM FACILITY_DISPATCH}
  FACILITY_CERT = 11;
  {$EXTERNALSYM FACILITY_CERT}

{ Define the severity codes }

  ERROR_SUCCESS = 0;
  NO_ERROR = 0;

  ERROR_INVALID_FUNCTION = 1;
  ERROR_FILE_NOT_FOUND = 2;
  ERROR_PATH_NOT_FOUND = 3;
  ERROR_TOO_MANY_OPEN_FILES = 4;
  ...
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests

Geändert von himitsu (28. Mär 2018 um 17:59 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#9

AW: DLL/Lib: Parameter Validierung

  Alt 28. Mär 2018, 18:28
Was ich immer noch nicht richtig zuordnen kann ist deine Sorge um Return values die der Nutzer vielleicht nicht auswertet und glaubt ein richtiges Ergebnis zu haben. Und glauben die Library sei fehlerhaft und "nicht gut". So etwas ist mir persönlich völlig fremd.
Das nicht-Auswerten von Fehlercodes ist leider ziemlich häufig - wobei dieser Punkt auch eher nur ein kleines Contra ist. Der zweite Teil bezieht sich auf Assertions. Hier würde es ja dann innerhalb der Library crashen und da gehen manche Entwickler hin und behaupten einfach, dass alles was nicht in ihrem eigenen Code crasht, ein Bug in der Library sein muss. Absolut dämlich, ich weiß.

Ich würde, beispielsweise bei einem negativen Handle einen ordentlich dokumentierten Fehlercode zurückgeben.
Okay danke, das entspricht ja dann meiner Variante (2), die ich auch momentan verwende.

Exceptions gibt es im C auch, nur kennt C natürlich die Delphi-Exceptionklassen nicht und kann da dann nichtmal den im unbekannten Message-Text auswerten.
Leider nein. Der C Standard sieht keine Exception-Behandlung vor. CPU-Exceptions existieren natürlich weiterhin, aber man kann sie nicht Plattform-/Architektur-unabhängig fangen. Im Falle von C++ hast du Recht (ich verwende allerdings tatsächlich pures C im konkreten Falle).

Nja, du kannst natürlich auch LongBool-Results nehmen (in C++ ist der BOOL gern 32 Bit groß),
definierst dir eine Reihe Fehlercodes, nach dem Muster der WinAPI, wie z.B. HRESULT,
und nutzt fleißig MSDN-Library durchsuchenSetLastError. (natürlich nur wenn es auch einen Fehler gab, also nur bei Result=False)
Danke dir, das entspricht ja auch meiner Variante (2). Die konkrete Umsetzung ist mir noch gar nicht so wichtig im Moment. Mir geht es eher um die Grundsatzdiskussion Assertions vs. "richtigen" Checks und den damit verbundenen (theoretischen) Vor- und Nachteilen.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 05:06 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz