www.der-wirtschaftsingenieur.de

- Generalist zwischen Wirtschaft und Technik


Testverfahren: White-Box vs Black-Box

In der Software-Entwicklung müssen Entwicklungsergebnisse immer verifiziert und validiert werden. Dazu werden White-Box- und Black-Box-Tests verwendet.

White-Box-Test (WB-Test):
Hier haben die Tester Zugriff auf und Kenntnis über die Entwicklung der Software (Quellcode, Entwicklungsumgebung, Diagramme [UML etc.] sowie Dokumentation). Da quasi in das Programm “hineingesehen” wird, wird auch seltener vom “Glass-Box-Test” gesprochen.

  1. Stufe: Dieselben Programmier, die die Software programmiert haben, führen den Test durch
  2. Stufe: Andere Programmierer, welche sich jedoch in den Code hineinarbeiten, führen den Test durch

Black-Box-Test (BB-Test):
Hier haben die Tester keinen Zugriff auf und idealerweise auch keine Kenntnis über die Entwicklung der Software.

  1. Stufe: Tester, welche als Kunden auftreten (echter Kunde oder nur “simulierter” Kunde) testet die Software (vor allem Funktionstests)
  2. Stufe: Tester, welche Erfahrung mit Software-Tests (z. B. Penetration Tests) haben und sich in die Auftragsspezifikationen hineinarbeiten, führen den Test durch (vor allem Stresstests) (mehr…)

10 gängige Vorurteile über die Programmierung

  1. Funktionen/Methoden dürfen nur einen Ausstiegspunkt (return) haben
    Dies ist nicht richtig, es wird jedoch von vielen erfahrenen Entwicklern als unschön angesehen, wenn eine Funktion mehrere Ausstiegspunkte hat. Die Funktion kann so aber schneller abgehandelt werden, wenn die Funktion sofort mit “return” verlassen wird, wenn eine Bedingung hierfür gesetzt wurde. Gibt es nur ein “return” am Ende der Funktion, wird die Funktion in die Länge gezogen.
  2. UDP-Pakete brauchen Prüfsummen
    Natürlich nicht. Das tolle an UDP ist ja gerade, dass Pakete quasi blind (und schnell) weggeschickt werden können. UDP sollte sowieso nicht da verwendet werden, wo Daten unbedingt ankommen müssen. Wenn Daten unbedingt ihr Ziel erreichen sollen, sollte der Weg auch frei sein, gleich das verbindungsorientierte TCP statt UDP zu nutzen.
  3. C ist veraltet
    Bis jetzt gibt es viele Anwendungsbereiche, bei welchen C ziemlich alternativlos ist, z. B. teilweise in der Programmierung von Mikrocontrollern und der hardware-nahen Programmierung generell.
  4. Open Source ist Freeware
    Freeware meint nur Software, welche kostenlos zu haben ist, also “frei” im Sinne von “kostenlos”. Dabei kann Freeware auch Open Source sein, muss aber nicht. Freeware könnte zudem auch mit einem Kopierschutz etc. ausgestattet sein. Open Source (auch bezeichnet als “Freie Software”) ist quelloffen und kann zum Nutzen für die Gemeinschaft ausgeführt, verbreitet und verändert werden.
  5. Jede nicht-objektorientierte Software ist nicht auf dem Stand der Zeit
    Objektorientiertheit zahlt sich in höheren Programmiersprachen sehr aus, jedes Skript und auch bestimmte Programme, die auf Geschwindigkeit ausgelegt sind, müssen (oder sollten?) aber nicht unbedingt objektorientiert sein.
    Ein umfangreiches Verwaltungsprogramm sollte schon in z. B. Java/C# rein objektorientiert umgesetzt werden, ein Skript, welches eine Textseite parsen soll oder ein Programm für einen Mikrocontroller muss aber nicht gleich objektorientiert sein.
  6. Globale Variablen sind immer schlecht
    Als ich meine ersten Programme in C/C++ geschrieben habe, habe ich es mit den globalen Variablen schon ein bisschen übertrieben. Das ist ein typischer Anfängerfehler. Aber wenn man mal ein paar Schritte weiter ist oder sich mal Open Source betrachtet, findet man immer wieder ein paar globale Variablen, und das völlig berechtigt. Es gibt Situationen, da müssen neben Konstanten auch ein paar Variablen überall verfügbar sein. Anders als Konstanten können gloibale Variablen verändert werden und so einen aktuellen Stand beschreiben.
  7. Arrays und Zeiger sind dasselbe
    Arrays sind in C mit Zeigern umgesetzt, dasselbe sind Zeiger und Array jedoch lange nicht. In anderen Programmiersprachen entfernen sich Array und Zeiger sogar noch weiter voneinander.
  8. Objektorientierung ist in C oder Assembler unmöglich
    Das stimmt so überhaupt nicht. Allerdings muss man da schon in C und Assembler mehr tun, als einfach eine Klasse zu definieren wie in Java/C#. Jedoch ist weder C und noch Assembler (die Programmiersprache eines Assemblers) auf Objektorientierung ausgelegt, es wird daher aufwändig und undurchsichtig mit der Objektorientierung, möglich jedoch schon.
  9. Rekursion ist immer besser als die Iteration (Schleifen)
    Rekursion wird als schöner empfunden (und einige wenige Programmiersprachen kennen auch nur Rekursion), Rekursion ist aber z. B. in C grundsätzlich langsamer und speicheraufwändiger als Iteration (da immer wieder eine Funktion aufgerufen und geladen werden muss).
  10. Java ist langsam
    Halbes Gerücht. Denn Java ist, das haben viele Tests z. B. von Fachzeitschriften gezeigt, im Vergleich zu Sprachen wie C/C++ schon langsam. Man sollte hier aber auch wirklich auf die Konzepte achten, so auch nur ein rein objektorientiertes Programm in C++ mit einem solchen in Java vergleichen (dass C++ objektorientiert ist, ist übrigens auch ein Gerücht, es handelt sich eher um eine hybride Sprache).
    Das Problem ist doch eher, dass bei Java oft übertrieben wird, was die Langsamkeit betrifft.

Mikroprozessortechnologie – CISC vs RISC

Es gibt zwei maßgebende Architekturprinzipien in der Mikroprozessortechnologie, RISC und CISC. Die Grundunterschiede zwischen beiden Prinzipien werden bereits durch die Namensgebung klar.

RISC (Reduced Instruction Set Computing)

RISC arbeitet mit einem geringeren Grundbefehlssatz (maximal 128) aus weniger komplexen Befehlen, welche maximal vier Befehlsformate haben können. Das macht Programmausführungen mit einem RISC-Prozessor flexibler, da die Befehlsausführungszeit geringer ist. Durch geringe Befehlsausführungen können die Rechenvorgänge des Prozessors schneller und somit auch flexibler unterbrochen werden.

Da sich der Befehlssatz auf das Nötigste beschränkt und die jeweiligen Befehle so (gegenüber den Befehlen der CISC-Architektur) kurz und außerdem einheitlich lang bleiben, wird auch das Dekodieren der Befehle in kürzerer Zeit möglich. RISC-Prozessoren haben gegenüber den CISC-Prozessoren verhältnismäßig viele Register (kleinster, aber schnellst adressierbarer/ansprechbarer Speicher, welcher sich innerhalb des Prozessors befindet), was die Rechengeschwindigkeit weiterhin erhöht.

Der Code für einen RISC-Prozessor ist weniger kompakt als der eines CISC-Prozessors, denn die vielen Befehle, welche nur für eine mittelmäßig komplexe Anweisung notwendig sind, blähen den Code geradezu auf.

CISC (Complex Instruction Set Computing )

CISC-Prozessoren kamen nach den RISC-Prozessoren und begründeten sich vor allem durch eine Zeit des teuren Arbeitsspeichers und des nicht vorhandenen Cache-Speichers. So wurden viele komplexe Befehle (welche selbst aus mehreren effektiven Befehlen bestehen und unterschiedliche Größen haben können) direkt im Mikroprogrammspeicher des Mikroprozessors gespeichert, die Architektur um spezialisierte Register, viele Befehlsformate und Adressierungen erweitert. CISC-Prozessoren hatten sich in der Vergangenheit besonders bei Großrechnern durchgesetzt, aber auch in kleinere (End-)Geräten wurden CISC-Prozessoren integriert, insbesondere durch Hersteller wie IBM, Intel und Motorola.

CISC vs RISC – Keine Frage der Zukunft

Heute sind reine CISC-Prozessoren kaum mehr in Verwendung, aber auch reine RISC-Prozessoren sind heute kein Trend mehr. Heutige Prozessoren sind meistens RISC-Prozessoren, welche sich in ihrer Architektur auch an CISC-Prozessoren anlehnen. Die Prozessoren finden sich heute im Taschenrechner, in der digitalen Kamera, im PC usw. auch das aktuelle IPhone von Apple nutzt einen Prozessor, der mehr RISC- als CISC-Prozessor ist.

Die Differenzierung von RISC und CISC ist aktuell und auch in Zukunft nicht mehr notwendig, da sich die Hersteller nicht mehr nur für eine Philosophie entscheiden, sondern anforderungsgemäß Architekturen entwickeln, die Elemente aus CISC wie auch aus RISC enthalten, wobei die RISC-Eigenschaften meistens überwiegen mögen.


Datenbankmodelle

Datenbankmodelle sind das Gerüst nach welchem implementierte Daten gespeichert werden. Datenbankmodell sind abhängig vom Datenbankmanagementsystem.

Das ER-Modell als abstraktes Datenmodell und Datenmodellierungsinstrument zählt nicht zu den Datenbankmodellen.

Es gibt vier Hauptdatenbankmodelle, welche nach folgenden Adjektiven eingeordnet werden:

  • netzwerkartig bzw. vernetzt
  • hierarchisch
  • objektorientiert
  • relational

Aus diesen vier Hauptdatenbankmodellen sind einige Mischformen bzw. hybride Modelle, z.B. das objektrelationale Datenbankmodell oder relationale Netzwerk-Datenbankmodell, entstanden, welche die Eigenschaften der unterschiedlichen Datenbankmodelle kombinieren.

Das verbreiteste Datenbankmodell ist das relationale Datenbankmodell. Für kleine bis mittelgroße Softwareprojekte wird fast ausschließlich eine oder mehrere relationale Datenbanken verwendet. (mehr…)


.NET-Code, unfreiwilliger Open Source?

Es ist schon ein wenig gewöhnungsbedürftig, der Gedanke, dass der gesamte Code eines mühevoll entwickeltes Programms quasi von jedem Nutzer eingesehen werden kann. “Open Source” ist an dieser Stelle sicherlich zu viel gesagt, denn nur weil der Quellcode (mit Hilfsmitteln) einsehbar ist, heißt dies nicht, dass dieser auch für andere Zwecke als die Programmausführung genutzt werden darf.

Aber ein einsehbarer Quellcode erleichtert die Spionage, Piraterie und sonstigen Missbrauch enorm.
Aber warum kann der Quellcode eines .Net Programms so einfach eingesehen werden? Das Problem existiert bei .NET Sprachen (und auch Java) zum einen, weil die Quellcode in eine Zwischensprache (IL, Intermediate Language) übersetzt wird und zum anderen an Reflections.

Die Zwischensprache ist bereits für den Menschen lesbarer als Assemblercode und lässt sich verhältnismäßig einfach wieder zurück übersetzen, denn die Zwischensprache spricht anders als Assemblercode nicht einzelne Register an (kann sie auch nicht, denn sie ist eine hardware-universelle Sprache), sondern liest sich auf einem viel höheren Abstraktionslevel. (mehr…)


Reflections in C#

Es kann zur Laufzeit eines Programms notwendig sein, dass das Programm sich selbst analysieren und ggf. verändern kann. In C# kann dies mir der Funktionalität von Reflections erreicht werden.

Reflection „reflektiert” zur Laufzeit aufrufbare Assemblys, Module und Typen und bietet so dynamischen Zugriff auf Objekte, Methoden und Metadaten, welche erst zur Laufzeit bekannt werden.
Eine Programmiersprache, welche diese Möglichkeit zur “Reflektion” der eigenen Programminhalte hat, kann ihre eigene Struktur selbst zur Laufzeit analysieren und auf sie Einfluss nehmen.

Reflection als Methode zum dynamischen Aufruf von Klassen, Methoden, Variablen, Ressourcen usw. ist abgesehen von C# (und anderen .NET-Programmiersprachen) auch in Java bekannt und in noch einigen weiteren Programmiersprachen in ähnlicher Form verbreitet. Aber auch die Reflection-Funktionalität von Java und C# sind nicht absolut identisch. Während Java auf Klassenebene „reflektiert” und generell auch Klassen als Informationsempfänger verwendet, kommt C# nicht über Klassen, sondern über Assemblies an die gewünschten Informationen heran.

Grundsätzlich sollte der Einsatz von Reflections vermieden werden, denn der dynamische Aufruf ist wesentlich langsamer, als der direkte Aufruf. Insgesamt ist der Einsatz dieser Funktionalität mit übermäßigen Performance-Einbußen verbunden. Mit der Anwendung von Reflections erkauft sich der Programmierer Flexibilität zur Laufzeit, nimmt damit aber Leistungseinbußen in Kauf.
Nur wenn ein dynamischer Aufruf unbedingt notwendig ist, gibt es für Reflections grundsätzlich keine Alternative. Beispiele für Anwendungsgebiete sind Schnittstellen für Plugins, der Debugger und auch die automatische Textvervollständigung in Entwicklungsumgebungen (z. B. Microsofts IntelliSense). (mehr…)


Überladen von Methoden

Im Laufe eines Software-Projekts sind Algorithmen zu entwerfen und in Methoden zu schachteln, welche zwar prinzipiell das gleiche tun, aber mit Werten von unterschiedlichen Datentypen oder einer verschiedenen Anzahl an Werten. Für solche Fälle bietet C# die Möglichkeit zur Überladung von Methoden.

Die meisten höheren Programmiersprachen erlauben überladene Methoden, neben C# beispielsweise auch C++ und Java.
Überladene Methoden haben zwei oder mehr Signaturen. Die Signatur ist die Kopfzeile einer Methode, welche die Zugriffsmöglichkeit, den Rückgabewert, den Methodennamen und die Parameter (Namen, Anzahl und Typ) beschreibt.
Die Überladung bezieht sich in C# (und den meisten anderen Programmiersprachen auch) jedoch ausschließlich auf die Parameteranzahl und die Datentypen der Parameter, denn nur diese sind bei einem Methodenaufruf maßgebend für eine klare Abgrenzung der verschiedenen Ausführungen einer überladenen Methode. (mehr…)


typeof() vs .GetType()

Um den Typen nach System.Type bestimmen zu können, werden entweder der Operator typeof() oder die Instanzmethode getType() verwendet. Diese beiden Möglichkeiten liefern ein Objekt vom Typ System.Type zurück.

Oft wird die Frage gestellt, welcher Unterschied zwischen diesen beiden Möglichkeiten der Typ-Bestimmung besteht.

Der typeof()-Operator kann nur das Objekt von System.Type eines Typen (eine Klasse) bestimmen.

(Achtung: In C# sind auch die oft als primitiven Typen bezeichneten Wertetypen, z. B. Integer (int) oder Double (double) Klassen!)

Dabei kann der typeof()-Operator auch generische Typen bestimmen. Überladen werden, kann dieser Operator jedoch nicht.

C#:
  1. System.Type type = typeof(double);

getType() ist eine Methode einer Instanz (eines Objekts). getType() kann daher nicht frei verwendet werden, wie der typeof()-Operator, sondern ist an eine Instanz gebunden. getType() liefert den Typen, der eigenen Instanz zurück.

C#:
  1. double d = 5.4;
  2. System.Type type = d.GetType();

getType() kann im Gegensatz zu typeof() auch überladen werden. GetType() kann allerdings nicht (typeof() natürlich auch nicht) überschrieben werden.

Auf den Punkt gebracht: Zur Bestimmung von Typen von Klassen wird der Operator typeof() benutzt. Die Bestimmung von Typen von Objekten ist hingegen die Methode getType() zu benutzen.
(mehr...)


Klassendiagramm (Class Diagramm)

Das Klassendiagramm ist wahrscheinlich eines der Diagramme, welches zum großen Erfolg von UML beigetragen haben.

Mit dem Klassendiagramm lassen sich die für ein Programm entworfenen Klassen mit Atrributen und Methoden sowie den Beziehungen der Klassen zueinander visualisieren.
Ein Klassendiagramm ist daher nur auf die objektorientierte Software-Entwicklung anwendbar.

Ein Klassendiagramm muss nicht den vollständigen Klassenentwurf wiedergeben, sondern kann sich auch nur auf einen Ausschnitt relevanter Klassen beziehen.

Beispiel:

Klassendiagramm

Obiges Klassendiagramm zeigt die Beziehung zweier Klassen. Die linke Klasse Eurofighter steht in Beziehung mit einer anderen Klasse Rakete.

Die Klasse Eurofighter hat Attribute (obere, rote Liste), welche die Eigenschaften der Klasse beschreiben. Außerdem hat die Klasse Operationen (untere, grüne Liste), welche die möglichen Funktionen (Methoden) der Klasse zeigen.

Dass in die Klasse Rakete keine Attribute und Operationen eingetragen wurden ist nicht weiter tragisch. Oft wird der Fokus auf die relevanten Klassen gelegt und nur bedingt relevante Klassen vereinfacht dargestellt; das Abstraktionslevel wird nicht selten bewusst gebrochen.

Ein UML-Klassendiagramm dient primär der Planung und Konzeption, als Hilfestellung für Software-Entwickler.
Es gibt zudem Programme, welche ein, nach den Programmrichtlinien erstelltes, Klassendiagramm in Quellcode umsetzt. (mehr...)


Anwendungsfall-Diagramm (Use-Case)

Komplexe Programme bieten häufig unterschiedlichen Benutzern verschiedene Interaktionen an.
Dass ein Programm nur eine Art von Benutzer kennt, welcher das Programm nur starten muss und somit eine Kette von Aktionen auslöst bis sich das Programm automatisch beendet, kommt sehr selten und früher eher als heute vor (unabhängig davon, dass es immer diese Art von Programmen geben wird).

Bei der Entwicklung z.B. eine Office-Anwendung oder einer Shop-Software können sich die Entwickler schnell in einem "Wirrwar" aus Benutzer-Rechten verlieren, "Wer darf was und was eigentlich nicht?".

Use Case Diagramm

Im sogenannten Anwendungsfall-Diagramm (engl. Use-Case-Diagram) werden Benutzer (engl. User) als kleine Strichmännchen dargestellt und so im Diagramm als Akteur, der auf das Programm Einfluss nehmen kann, symbolisiert. Aktionen, wie z.B. Anzeigen, Drucken oder Suchen, werden i.d.R. als Ovale dargestellt.

Um ein Anwendungsfall-Diagramm besser zu gliedern, kann es mehrere Anwender-Bereiche haben, z.B. Administrator-Bereich und Gäste-Bereich. (mehr...)


« vorherige SeiteNächste Seite »