Copyright IBM Corp. 1987, 2004. Alle Rechte vorbehalten.

Der Begriff "Rational" und Rational-Produkte sind Marken der Rational Software Corporation. Namen anderer Unternehmen und Produkte können Marken der jeweiligen Unternehmen sein und dienen nur als Referenz.

Inhalt

Zu diesem Dokument

Einführung

Grundlegende Prinzipien
Voraussetzungen
Klassifizierung der Richtlinien
Wichtigste Richtlinie

Codelayout

Allgemeines
Groß-/Kleinschreibung von Buchstaben
Einrückung
Zeilenlänge und Zeilenumbrüche
Ausrichtung

Kommentare

Allgemeines
Richtlinien für die Verwendung von Kommentaren

Namenskonventionen

Allgemeines
Pakete
Typen
Ausnahmen
Unterprogramme
Objekte und Unterprogrammparameter (oder Eingangsparameter)
Generische Einheiten
Strategien für die Benennung von Subsystemen

Deklaration von Typen, Objekten und Programmeinheiten

Aufzählungstypen
Numerische Typen
Reale Typen
Satztypen
Zugriffstypen
Private Typen
Abgeleitete Typen
Objektdeklarationen
Unterprogramme und generische Einheiten

Ausdrücke und Anweisungen

Ausdrücke
Anweisungen
Hinweise zur Codierung

Transparenzprobleme

Überladung und Homographen
Kontextklauseln
Umbenennung
Anmerkung zu USE-Klauseln

Programmstruktur und Kompilierungsprobleme

Dekomposition von Paketen
Struktur von Deklarationsabschnitten
Kontextklauseln
Reihenfolge der Ausarbeitung

Parallelität

Fehlerbehandlung und Ausnahmen

Low-Level-Programmierung

Darstellungsklauseln und -attribute
Unchecked_Conversion

Zusammenfassung

Referenzen

Glossar


Kapitel 1

Zu diesem Dokument

Diese RUP-Richtlinien für die Programmierung mit Ada sind eine Vorlage, aus der Sie einen Codierungsstandard für Ihre eigene Organisation ableiten können. Das vorliegende Dokument gibt an, wie Ada-Programme geschrieben werden müssen. Es richtet sich an alle Designer und Entwickler von Anwendungssoftware, die Ada als Implementierungssprache oder als Designsprache, beispielsweise für die Angabe von Schnittstellen oder Datenstrukturen, verwenden.

Die in diesem Dokument beschriebenen Regeln decken die meisten Aspekte der Codierung ab. Für das Programmlayout, Namenskonventionen und die Verwendung von Kommentaren gelten allgemeine Regeln. Für ausgewählte Ada-Features gelten spezifische Regeln, die verbotene Konstrukte angeben, Verwendungsmuster empfehlen und generelle Hinweise zur Verbesserung der Programmqualität enthalten.

Diese Richtlinien für die Programmierung überschneiden sich teilweise mit den Designrichtlinien für ein Projekt. Diese Überschneidungen sind jedoch gewollt. Viele Codierungsregeln wurden aufgestellt, um einen objektorientierten Ansatz für das Softwaredesign aktiv zu unterstützen und zu fördern. Dies gilt insbesondere für Regeln im Bereich der Namenskonventionen.

Ursprünglich wurden die Richtlinien für Ada 83 geschrieben. Es gibt Regeln für die Kompatibilität mit Ada 95, jedoch keine spezifischen Richtlinien für die Verwendung der neuen Features der Sprache nach überarbeitetem Sprachstandard, z. B. für die Verwendung von erweiterbaren Typen, untergeordneten Einheiten oder von DECIMAL-Typen.

Der Aufbau des Dokument ist an die Struktur des Ada-Referenzhandbuchs angelehnt [ISO 8052].

In Kapitel 2, Einführung, sind die grundlegenden Prinzipien erläutert, auf denen die Richtlinien basieren. Dieses Kapitel stellt auch eine Klassifizierung der Richtlinien vor.

Kapitel 3, Codelayout, beschäftigt sich mit der generellen visuellen Organisation von Programmtexten.

Kapitel 4, Kommentare, enthält eine Anleitung für die Verwendung von Kommentaren zur strukturierten, sinnvollen und verwaltbaren Dokumentation des Codes.

In Kapitel 5, Namenskonventionen, finden Sie einige allgemeine Regeln für die Benennung von Sprachentitäten und Beispiele. Dieses Kapitel müssen Sie an die Anforderungen Ihres spezifischen Projekts bzw. Ihrer Organisation anpassen.

Kapitel 6, Deklarationen, und Kapitel 7, Ausdrücke und Anweisungen, enthalten weitere Ratschläge zu den einzelnen Sprachkonstrukten.

In Kapitel 8, Transparenzprobleme, und Kapitel 9, Programmstruktur und Kompilierungsprobleme, finden Sie Anleitungen zur globalen Strukturierung und Organisation von Programmen.

Kapitel 10, Parallelität, beschäftigt sich mit dem speziellen Thema der Verwendung von Features zur Task-Verarbeitung und von zeitbezogenen Features.

In Kapitel 11, Fehlerbehandlung und Ausnahmen finden Sie eine Anleitung für die Verwendung bzw. Nichtverwendung von Ausnahmen für eine einfache und systematische Fehlerbehandlung.

Kapitel 12, Low-Level-Programmierung, beschäftigt sich mit Fragen zu den Darstellungsklauseln.

In Kapitel 13, Zusammenfassung, sind noch einmal die wichtigsten Richtlinien aufgeführt.

Dieses Dokument ersetzt die Veröffentlichung Ada Guidelines: Recommendations for Designers and programmers, Application Note 15, Rational, Santa Clara, CA, 1990.


Kapitel 2

Einführung

Grundlegende Prinzipien

Ada wurde explizit entworfen, um die Entwicklung qualitativ hochwertiger, zuverlässiger, wiederverwendbarer und portierbarer Software zu unterstützen [ISO 87, Abschnitt 1.3]. Dies kann jedoch nicht allein mit einer Programmiersprache erreicht werden. Die Programmierung ist als Teil eines Prozesses zu verstehen, der viel Disziplin erfordert.

Die meisten hier angegebenen Richtlinien haben klaren und verständlichen Ada-Quellcode zum Ziel, der einen wichtigen Beitrag zur Zuverlässigkeit und Wartungsfreundlichkeit leistet. Was mit klarem und verständlichem Code gemeint ist, geht aus den drei folgenden grundlegenden Prinzipien hervor.

Möglichst wenig Überraschungen

Quellcode wird während seiner Lebensdauer häufiger gelesen als geschrieben. Dies gilt insbesondere für Spezifikationen. Code lässt sich im Idealfall wie eine englische Beschreibung der erforderlichen Schritte lesen, hat jedoch den Vorteil, dass er diese Schritte auch gleich ausführt. Programme werden eher für Menschen als für Computer geschrieben. Das Lesen von Code ist ein komplexer mentaler Prozess, der durch Uniformität unterstützt werden kann. Dieses Prinzip der Uniformität sorgt für möglichst wenig Überraschungen. Ein einheitlicher Stil für ein Projekt ist ein wichtiger Grund, aus dem sich ein Team von Softwareentwicklern auf Programmierungsstandards einigt. Solche Standards sind nicht als Strafe oder als Kreativitäts- und Produktivitätshemmnis zu verstehen.

Zentrale Wartung

Ein weiteres wichtiges Prinzip, das diesem Leitfaden zugrunde liegt, ist das Prinzip der zentralen Wartung. Eine Designentscheidung sollte nach Möglichkeit nur an einem Punkt der Ada-Quelle ausgedrückt werden. Die Konsequenzen dieser Entscheidung sollten größtenteils programmatisch von diesem Punkt abgeleitet werden. Eine Verletzung dieses Prinzips kann sich sehr nachteilig auf die Wartungsfreundlichkeit und Zuverlässigkeit, aber auch auf die Verständlichkeit des Codes auswirken.

Möglichst wenig Schickschnack

Zur Verbesserung der Lesbarkeit gilt schließlich noch das Prinzip, möglichst wenig Schnickschnack aufzunehmen. Dieses Prinzip soll helfen, eine Überhäufung des Quellcodes mit visuellem Schnickschnack zu vermeiden. Dazu gehören Balken, Fenster und Texte mit geringem Informationsgehalt oder mit Informationen, die nicht zum Verständnis des Verwendungszwecks der Software beitragen.

Die Portierbarkeit und die Wiederverwendbarkeit gaben Anlass für viele der Richtlinien. Der Code muss für verschiedene Zielcomputer auf mehrere verschiedene Compiler und schließlich auf eine verbesserte Version von Ada, "Ada 95", portiert werden [PLO92, TAY92].

Voraussetzungen

Die hier vorgestellten Richtlinien gehen von einigen wenigen Grundvoraussetzungen aus:

Der Leser kennt Ada.

Programmierer werden ermuntert, die innovativen Ada-Features zu verwenden, wo immer dies sinnvoll erscheint. Dass einige Programmierer sich nicht mit diesen Features auskennen, sollte kein hinreichender Grund sein, auf die Verwendung der Features zu verzichten. Nur so kann das Projekt tatsächlich von dem Einsatz von Ada profitieren. Verwenden Sie Ada nicht wie Pascal oder FORTRAN. Es wird davon abgeraten, den Code in Kommentaren zu umschreiben. Ganz im Gegenteil, Sie sollten überall, wo es machbar ist, Ada anstelle von Kommentaren verwenden.

Der Leser kann Englisch.

Viele der Namenskonventionen basieren auf dem Vokabular und der Syntax der englischen Sprache. Ada-Schlüsselwörter sind sogar allgemein gebräuchliche englische Wörter, die nicht mit anderen Sprachen kombiniert werden sollten, um die Lesbarkeit nicht zu beeinträchtigen.

USE-Klauseln werden nur stark eingeschränkt verwendet.

Die Namenskonventionen und einige andere Regeln gehen davon aus, dass keine USE-Klauseln verwendet werden.

Das zu bearbeitende Projekt ist sehr umfangreich.

Die meisten Regeln kommen in großen Ada-Systemen am besten zum Tragen. Sie können natürlich auch auf kleine Systeme angewendet werden, um sich besser mit Ada vertraut zu machen und Uniformität auf Projekt- oder Unternehmensebene zu erreichen.

Der Quellcode wird in der Rational-Umgebung entwickelt.

Wenn Sie in der Rational-Umgebung arbeiten, können Sie Probleme wie das Codelayout oder Bezeichner in schließenden Konstrukten dem Ada-Editor und dem Ada-Formatierungstool überlassen. Die Layoutempfehlungen in diesem Dokument können Sie jedoch auf jeder Entwicklungsplattform anwenden.

Die Codierung folgt einem objektorientierten Design.

Viele Regeln unterstützen eine systematische Abbildung objektorientierter Konzepte in Ada-Features und spezifischen Namenskonventionen.

Klassifizierung der Richtlinien

Nicht alle hier genannten Richtlinien sind von gleicher Wichtigkeit. Sie können vielmehr grob den folgenden Kategorien zugeordnet werden:

Hinweis: Zeigefingersymbol

Diese Richtlinien sind lediglich Vorschläge. Ob Sie sie anwenden oder nicht, bleibt Ihnen überlassen. Es entsteht kein Schaden, wenn Sie sie nicht beachten. Hinweise sind in diesem Dokument mit dem obigen Symbol gekennzeichnet.

Empfehlung: OK-Symbol

Für diese Richtlinien gibt es eher technische Gründe. Sie können sich auf die Portierbarkeit oder Wiederverwendbarkeit, in einigen Implementierungen auch auf die Leistung auswirken. Empfehlungen sollten, abgesehen von den in diesem Dokument genannten Ausnahmen, nur aus gutem Grund außer Acht gelassen werden. Empfehlungen sind in diesem Dokument mit dem obigen Symbol gekennzeichnet.

Einschränkung: Handsymbol 'Achtung'

Die Verwendung des betreffenden Features ist gefährlich, jedoch nicht grundsätzlich verboten. Die Entscheidung für oder gegen die Verwendung sollte auf Projektebene getroffen werden und in hohem Maße transparent sein. Einschränkungen sind in diesem Dokument mit dem obigen Symbol gekennzeichnet.

Anforderung: Zeigefingersymbol

Die Nichtbeachtung einer Anforderung führt definitiv zu fehlerhaftem, unzuverlässigem oder nicht portierbarem Code. Anforderungen müssen erfüllt werden. Sie sind in diesem Dokument mit dem Symbol einer zeigenden Hand gekennzeichnet.

Die vorliegenden Richtlinien verbieten im Gegensatz zu vielen anderen Ada-Codierungsstandards einige wenige Ada-Features grundsätzlich. Der Schlüssel zu guter Software liegt hier in folgenden Faktoren:

Wichtigste Richtlinie

Zeigefingersymbol Lassen Sie sich vom gesunden Menschenverstand leiten.

Wenn Sie keine geltende Regel oder Richtlinie finden können oder eine Regel offensichtlich nicht zutrifft, halten Sie sich an die grundlegenden Prinzipien und bauen Sie auf den gesunden Menschenverstand. Diese Regel gilt vor allen anderen. Gesunder Menschenverstand ist unabdingbar.


Kapitel 3

Codelayout

Allgemeines

Das Layout eines Programms wird vollständig vom Rational Environment Formatter gesteuert, so dass sich der Programmierer, abgesehen von Kommentaren und Leerstellen, nicht viele Gedanken über das Layout machen muss. Die Formatierungskonventionen des Tools sind in Anhang E des Referenzhandbuchs zur Programmiersprache Ada beschrieben [ISO87]. Diese Konventionen sehen vor, dass die Schlüsselwörter am Anfang und Ende eines strukturierten Konstrukts vertikal ausgerichtet werden und dass der Bezeichner eines Konstrukts systematisch am Ende des Konstrukts wiederholt wird.

Das konkrete Verhalten des Formatierungstools wird von mehreren Bibliotheksschaltern gesteuert, die während des Projekts ausgehend von einer gemeinsamen Modellwelt eine einheitliche Gruppe von Werten empfangen. Die relevanten Schalter sind nachfolgend mit ihrem aktuellen Wert für die hier empfohlene Modellwelt aufgelistet.

Groß-/Kleinschreibung von Buchstaben

Format . Id_Case : Letter_Case := Capitalized

Gibt die Groß-/Kleinschreibung von Bezeichnern in Ada-Einheiten an. Der allererste Buchstabe und jeweils der erste Buchstabe nach einem Unterstreichungszeichen wird groß geschrieben. Diese Art der Großschreibung ist bei den Schriftarten, die von den meisten modernen Anzeigegeräten und Laserdruckern verwendet werden, die für Menschen am besten lesbare Form.

Format . Keyword_Case : Letter_Case := Lower

Gibt die Groß-/Kleinschreibung von Ada-Schlüsselwörtern an, die die Schlüsselwörter geringfügig von den Bezeichnern unterscheidet.

Format . Number_Case : Letter_Case := Upper

Gibt die Groß-/Kleinschreibung des Buchstabens "E" in Gleitkommaliteralen und der Basen ("A" bis "F") in Literalen mit Basis.

Einrückung

Eine Ada-Einheit wird gemäß der allgemeinen Konventionen im Anhang E des Ada-Referenzhandbuchs formatiert [ISO87]. Demnach werden die Schlüsselwörter am Anfang und Ende eines strukturierten Konstrukts untereinander ausgerichtet. Dies gilt beispielsweise für "loop" und "end loop" bzw. für "record" und "end record". Elemente innerhalb strukturierter Konstrukte werden rechts eingerückt.

Format . Major_Indentation : Indent_Range := 3

Gibt die Anzahl der Spalten an, um die das Formatierungstool strukturierte (übergeordnete) Konstrukte einrückt, z. B. die Anweisungen "if", "case" und "loop".

Format . Minor_Indentation : Indent_Range := 2

Gibt die Anzahl der Spalten an, um die das Formatierungstool untergeordnete Konstrukte einrückt, z. B. Datensatzdeklarationen, Typendeklarationen, Ausnahmebehandlungsroutinen, Alternativen, Anweisungen zur Groß-/Kleinschreibung sowie benannte und gekennzeichnete Anweisungen.

Zeilenlänge und Zeilenumbrüche

Format . Line_Length : Line_Range := 80

Gibt die Anzahl der Spalten an, nach der das Formatierungstool Zeilen in Ada-Einheiten für die Ausgabe umbricht. Diese Umbrüche ermöglichen die Anzeige formatierter Einheiten mit traditionellen Terminals wie VT100.

Format . Statement_Indentation : Indent_Range := 3

Gibt die Anzahl der Spalten an, um die das Formatierungstool die Zeilen einer Anweisung ab der zweiten Zeile einrückt, wenn die Zeile länger als Line_Length ist und umgebrochen werden muss. Das Formatierungstool führt die Einrückung um die von Statement_Indentation angegebene Anzahl von Spalten nur aus, wenn es kein lexikalisches Konstrukt gibt, an dem der eingerückte Code ausgerichtet werden kann.

Format . Statement_Length : Line_Range := 35

Gibt die Anzahl der Spalten an, die in jeder Zeile für die Anzeige einer Anweisung reserviert ist. Falls der aktuelle Grad der Einrückung für eine Zeile weniger als die von Statement_Length angegebene Anzahl Spalten zulässt, geht das Formatierungstool zur Einrückung um die von Wrap_Indentation angegebene Anzahl Spalten über. Auf diese Weise wird verhindert, dass die Ausgabe tief verschachtelter Anweisungen über den rechten Rand hinaus geht.

Format . Wrap_Indentation : Line_Range := 16

Gibt die Spalte an, bei der das Formatierungstool mit der nächsten Einrückungsebene beginnt, wenn der derzeitige Grad der Einrückung nicht die von Statement_Length angegebene Anzahl Spalten zulässt. Auf diese Weise wird verhindert, dass die Ausgabe tief verschachtelter Anweisungen über den rechten Rand hinaus geht.

Ausrichtung

Format . Consistent_Breaking : Integer := 1

Steuert die Formatierung von Listen der Form (xxx:aaa; yyy:bbb), die in formalen Abschnitten von Unterprogrammen und als Diskriminanten in Typendeklarationen vorkommen, und die Formatierung von Listen der Form (xxx=>aaa, yyy=>bbb), die in Aufrufen und Aggregaten von Unterprogrammen vorkommen. Wenn eine Liste nicht in eine Zeile passt, beginnt jeder Eintrag der Liste in einer neuen Zeile, da diese Option ungleich null (true) ist.

Format . Alignment_Threshold : Line_Range := 20

Gibt die Anzahl der Leerstellen ein, die das Formatierungstool einfügen kann, um lexikalische Konstrukte wie Doppelpunkte, Zuweisungen und Pfeile in benannter Schreibweise in aufeinander folgenden Anweisungen auszurichten. Falls für die Ausrichtung eines Konstrukts mehr Leerstellen erforderlich sind, wird das Konstrukt nicht ausgerichtet.

Zeigefingersymbol Der Programmierer kann ein bestimmtes Layout erzwingen, indem er durch Eingabe von <Leerzeichen> <Leerzeichen> <CR> ein Zeilenende oder einen Zeilenumbruch einfügt, das bzw. den das Formatierungstool nicht entfernt.

OK-SymbolMit diesem Verfahren sollten Listen mit Ada-Elementen, die mehr als drei Elemente enthalten oder nicht in eine Zeile passen, so umgebrochen werden, dass jedes Element in einer neuen Zeile steht. Die Listen sind so besser lesbar und können leichter gewartet werden. Dies gilt insbesondere für die folgenden Ada-Konstrukte (siehe Anhang E des Ada-Referenzhandbuchs [ISO87]):

Argumentzuordnung

pragma Suppress (Range_Check,
                 On => This_Type,
                 On => That_Type,                 On => That_Other_Type);      

Bezeichner- und Komponentenlisten

Next_Position,
Previous_Position,
Current_Position : Position;
type Some_Record is 
    record
        A_Component,
        B_Component,
        C_Component : Component_Type;
    end record;      

Aufzählungstypdefinition

type Navaid is 
       (Vor, 
        Vor_Dme, 
        Dme, 
        Tacan, 
        VorTac, 
        NDB);      

Diskriminantenvorgabe

subtype Constrained is Element 
        (Name_Length    => Name'Length,
         Valid          => True,
         Operation      => Skip);      

Reihenfolge von Anweisungen (vom Formatierungstool sortiert)

Formaler Abschnitt, formaler generischer Abschnitt, aktueller Parameterabschnitt, aktueller generischer Parameterabschnitt

procedure Just_Do_It (This     : in Some_Type;
                      For_That : in Some Other_Type;
                      Status   : out Status_Type);
Just_Do_It (This     => This_Value;
            For_That => That_Value;
            Status   => The_Status);      

Kapitel 4

Kommentare

Allgemeines

Es ist ein weit verbreiteter Irrglaube, dass gute Programmierer an der Anzahl der Kommentare zu erkennen sind. Tatsächlich kann man sie an der Qualität ihrer Kommentare erkennen.

Kommentare sollten den Ada-Code ergänzen und nicht umschreiben. Ada ist an sich eine sehr gut lesbare Programmiersprache, was sich noch deutlicher zeigt, wenn gute Namenskonventionen angewendet werden. Kommentare sollten eine Ergänzung zum Ada-Code sein und das erklären, was nicht offensichtlich ist. Eine Wiederholung von Ada-Syntax oder -Semantik sollte vermieden werden. Kommentare können dem Leser etwas über die zugrunde liegenden Konzepte und die Abhängigkeiten vermitteln und ihm helfen, besonders komplexe Datencodierungen oder Algorithmen zu verstehen. In Kommentaren sollten Abweichungen von Codierungs- oder Designstandards, die Verwendung eingeschränkter Features und spezielle "Tricks" hervorgehoben werden. Kommentarrahmen oder -formulare, die systematisch für jedes größere Ada-Konstrukt (wie Unterprogramme und Pakete) erscheinen, haben den Vorteil der Einheitlichkeit und erinnern den Programmierer daran, den Code zu dokumentieren. Häufig verleiten sie jedoch zu einem umschreibenden Stil. Der Programmierer sollte zu jedem Kommentar ohne Umschweife angeben können, welchen Nutzen er bringt.

Ein falscher oder irreführender Kommentar ist schlimmer als ein weggelassener Kommentar. Kommentare werden nicht vom Compiler überprüft (es sei denn, sie sind wie bei RDF (Rational Design Facility) Teil formaler Ada Design Language (ADL) oder PDL Program Design Language (PDL)). Deshalb sollten Designentscheidungen gemäß dem Prinzip der zentralen Wartung in Ada und nicht in Kommentaren ausgedrückt werden, auch wenn dafür etwas mehr Deklarationen erforderlich sind.

Schauen Sie sich die folgende (nicht so gute) Beispieldeklaration an:

------------------------------------------------------------
-- procedure Create
------------------------------------------------------------
--
   procedure Create
              (The_Subscriber: in out Subscriber.Handle;
               With_Name     : in out Subscriber.Name);
--
-- Zweck: Diese Prozedur erstellt einen Subskribenten mit einem bestimmten
-- Namen.
--
-- Parameter:
-     The_Subscriber    :mode in out, type Subscriber.Handle
-               Die interne Kennung des erstellten Subskribenten.
-     With_Name         :mode in, type Subscriber.Name
-               Der Name des zu erstellenden Subskribenten.
-               Der Name hat folgende Syntax:
--                 <Buchstabe> { <Buchstabe> | <Ziffer> }
-- Ausnahmen:
--    Subscriber.Collection_Overflow, wenn kein Platz für das Erstellen
--    eines neuen Subskribenten verfügbar ist
--    Subscriber.Invalid_Name, wenn der Name leer oder falsch
--    formatiert ist
--
-------------------------------------------- end Create ----      

Zu diesem Beispiel gibt es einiges anzumerken.

- Procedure Create: Falls eine Namensänderung erforderlich ist, muss der Name an mehreren Stellen geändert werden. Mit dem Compiler kann keine konsistente Namensänderung in den Kommentaren erreicht werden.
- Parameter müssen in Kommentaren nicht mit Namen, Modus und Typ wiederholt werden.
- Die gut ausgewählten Namen für die einzelnen Ada-Entitäten machen Zweck- und Parametererklärungen überflüssig. Dies gilt allerdings nur für ein einfaches Unterprogramm wie das oben dargestellte. In komplexeren Unterprogrammen müssen Zweck und Parameter erklärt werden.

Der folgenden Version, die knapper und sinnvoller ist, wäre der Vorzug zu geben:

procedure Create (The_Subscriber : in out Subscriber.Handle;
                  With_Name      : in    Subscriber.Name);
--
-- löst Subscriber.Collection_Overflow aus
-- löst Subscriber.Invalid_Name aus, wenn der Name leer
-- oder falsch formatiert ist (siehe Syntaxbeschreibung
-- nach der Deklaration von type Subscriber.Name) 

Richtlinien für die Verwendung von Kommentaren

OK-Symbol Kommentare sollten in der Nähe des Codes stehen, auf den sie sich beziehen, und dieselbe Einrückung wie dieser Code haben. Der Kommentarblock sollte durch jeweils eine leere Kommentarzeile vom Ada-Konstrukt abgehoben werden:

procedure First_One;
--
-- Dieser Kommentar bezieht sich auf First_One.
-- Dieser Kommentar gilt jedoch für Second_One.
-- 
procedure Second_One (Times : Natural);      

OK-Symbol Zusammengehörige Quellcodeblöcke (Kommentare und Code) sollten durch leere Kommentarzeilen und nicht durch Kommentarzeilen mit gestrichelten Linien oder doppelten Linien voneinander abgesetzt werden.

OK-Symbol Trennen Sie die einzelnen Absätze eines Kommentarblocks nicht durch Leerzeilen, sondern durch leere Kommentarzeilen.

-- Eine Erläuterung, die in einem weiteren Absatz fortgesetzt
-- werden muss.
--
-- Die leere Kommentarzeile an dieser Stelle verdeutlicht,
-- dass es sich um einen Kommentarblock handelt. 

OK-Symbol Sie können Kommentare über oder unter dem betreffenden Ada-Konstrukt angeben. Kommentare mit Abschnittsüberschriften oder wichtigen Informationen, die für mehrere Ada-Konstrukte gelten, sollten Sie jedoch über den Konstrukten angeben. Kommentare mit Anmerkungen oder zusätzlichen Informationen sollten unter dem Ada-Konstrukt stehen, auf das sie sich beziehen.

OK-SymbolFassen Sie Kommentare am Anfang des Ada-Konstrukts zusammen. Nutzen Sie die volle Seitenbreite aus. Schreiben Sie Kommentare nicht in dieselbe Zeile wie ein Ada-Konstrukt, da die Ausrichtung solcher Kommentare oft verloren geht. Bei langen Deklarationen, z. B. für Literale des Aufzählungstyps, können solche Kommentare jedoch in den Beschreibungen der einzelnen Elemente toleriert werden.

OK-Symbol Verwenden Sie eine kleine Hierarchie von Standardkommentarblöcken für Abschnittsüberschriften. Dies gilt allerdings nur für sehr große Ada-Einheiten (>200 Deklarationen oder Anweisungen).

--===========================================================
--
--               HIER HAUPTÜBERSCHRIFT
--
--===========================================================


-------------------------------------------------------------
--               Hier Untertitel
-------------------------------------------------------------


--             --------------------
--             Kopfzeile des Unterabschnitts
--             --------------------      

Oberhalb solcher Kommentarüberschriften sollten mehr Leerzeilen als unterhalb eingefügt werden. Fügen Sie beispielsweise zwei Zeilen davor und eine Zeile danach ein. Dadurch wird die Überschrift visuell enger mit dem nachfolgenden Text verbunden.

OK-Symbol Vermeiden Sie Kopfzeilen mit Informationen wie Telefonnummern, Erstellungs- und Änderungsdatum und Speicherposition (oder Dateiname) der Einheit, da solche Informationen schnell veralten. Stellen Sie Hinweise zum Eigentumsrecht oder den Copyrightvermerk an das Ende der Einheit, insbesondere, wenn Sie in der Rational-Umgebung arbeiten. Wenn der Benutzer die Quelle einer Paketspezifikation aufruft, in der Rational-Umgebung beispielsweise durch Klicken auf [Definition], möchte er nicht erst zwei oder drei Seiten mit Text durchblättern, der für das Verständnis des Programms nicht von Bedeutung ist und/oder überhaupt keine Programminformationen enthält, wie es beim Copyrightvermerk der Fall ist. Vermeiden Sie vertikale Balken, geschlossene Rahmen oder Kästchen, die nur mit Mühe in Form gehalten werden können und im Grunde nichts als optischer Schnickschnack sind. Verwenden Sie zum Protokollieren der Einheit Anmerkungen in Rational CMVC (oder eine andere Form von Softwareentwicklungsdateien).

OK-Symbol Replizieren Sie keine Informationen, die an anderer Stelle verfügbar sind, sondern zeigen Sie auf diese Informationen.

OK-Symbol Verwenden Sie anstelle von Kommentaren weitestgehend Ada. Dies lässt sich durch bessere Namen, zusätzliche temporäre Variablen, Qualifikation, Umbenennung, Subtypen, statische Ausdrücke und durch Attribute erreichen, die (zumindest bei Verwendung eines guten Compilers) allesamt keinen Einfluss auf den generierten Code haben. Sie können auch kleine Inline-Prädikatfunktionen verwenden und den Code in mehrere Prozeduren ohne Parameter unterteilen, deren Namen gleichzeitig die Überschriften für mehrere separate Codeabschnitte sind.

Beispiele:

Dieser Code sollte ersetzt werden:

exit when Su.Locate (Ch, Str) /= 0; 
-- Suchschleife nach erfolgreicher Suche verlassen 

Search_Loop : loop

Found_It := Su.Locate (Ch, Str) /= 0;

exit Search_Loop when Found_It

end Search_Loop;

Dieser Code sollte ersetzt werden:

if Value < 'A' or else Value > 'Z' then 
-- Sofern keine Großbuchstaben angegeben sind 

subtype Uppercase_Letters is Character range 'A' .. 'Z';
if Value not in Uppercase_Letters then ...      

Dieser Code sollte ersetzt werden:

X := Green;         -- Dies ist das Green für den
                         -- Status, nicht für die Farbe
raise Fatal_Error;  -- Vom Paket Outer_Scope
delay 384.0;        -- Entspricht 6 Minuten und
                    -- 24 Sekunden 

The_Status := Green;      

X := Status'(Green);
raise Outer_Scope.Fatal_Error;
delay 6.0 * Minute + 24.0 * Second;      

Dieser Code sollte ersetzt werden:

if Is_Valid (The_Table (Index).Descriptor(Rank).all) then
-- Dies ist der aktuelle Wert für die Iteration. Ist er gültig,
-- werden Elemente an die enthaltene Liste angefügt.
   Append (Item,           To_List => The_Table (Index).Descriptor(Rank).Ptr);|      

declare
    Current_Rank : Lists.List renames The_Table 
                    (Index).Descriptor (Rank);
begin
    if Is_Valid (Current_Rank.all) then
        Append (Item, To_List => Current_Rank.Ptr);
    end if;
end;      

OK-Symbol Achten Sie in Kommentaren auf Stil, Syntax und Rechtschreibung. Verwenden Sie keinen kryptischen Telegrammstil. Nutzen Sie die Rechtschreibprüfung. (In der Rational-Umgebung können Sie Speller.Check_Image aufrufen.)

Zeigefingersymbol Verwenden Sie keine Buchstaben mit Akzenten oder Sonderzeichen aus anderen Sprachen. Möglicherweise unterstützen einige Entwicklungssysteme und Ada-Compiler in Kommentaren Zeichen, die nicht zum englischen Alphabet gehören (Ada Issue AI-339). Dies ist jedoch nicht auf alle Systeme übertragbar und könnte zu Fehlern führen.

OK-Symbol Für Unterprogramme sollten Sie mindestens Folgendes dokumentieren:

OK-Symbol Dokumentieren Sie für Typen und Objekte alle Invarianten oder zusätzlichen Einschränkungen, die nicht in Ada ausgedrückt werden können.

Zeigefingersymbol Vermeiden Sie Wiederholungen in Kommentaren. Der Abschnitt zum Zweck sollte beispielsweise kurz angeben, was ausgeführt wird, nicht aber wie es ausgeführt wird. In der Übersicht sollte das Design kurz vorgestellt werden. Die Beschreibung sollte sich nicht mit den verwendeten Algorithmen beschäftigen, sondern die Verwendung des Pakets erläutern.

Der Abschnitt Data_Structure und der Abschnitt mit den Algorithmen sollten genug Informationen enthalten, um die Hauptimplementierungsstrategie verständlich zu machen (damit das Paket ordnungsgemäß verwendet werden kann). Die Abschnitte sollten jedoch nicht alle Details der Implementierung und keine Informationen enthalten, die für die korrekte Verwendung des Pakets nicht von Relevanz sind.


Kapitel 5

Namenskonventionen

Allgemeines

Ordnen Sie Ada-Entitäten (Programmeinheiten, Typen, Subtypen, Objekten, Literalen, Ausnahmen) gute Namen zu. Dies ist in allen Softwareanwendungen einer der heikelsten Punkte. In mittleren und großen Anwendungen taucht darüber hinaus das Problem von Namenskonflikten auf bzw. die Schwierigkeit, genug Synonyme für ähnliche, aber dennoch verschiedene Beschreibungen desselben realen Konzepts (oder Namen für einen Typ, einen Subtyp, ein Objekt und einen Parameter) zu finden. Hier können Sie sich die Regel, keine (oder nur sehr stark eingeschränkt) USE-Klauseln zu verwenden, zunutze machen. In vielen Situationen können Sie dann einen Namen abkürzen und ohne das Risiko von Unklarheiten dieselben beschreibenden Worte wiederverwenden.

OK-Symbol Wählen sie verständliche, lesbare und aussagekräftige Namen.

Im Gegensatz zu anderen Programmiersprachen beschränkt Ada die Länge von Bezeichnern nicht auf 6, 8 oder 15 Zeichen. Dass kurze Namen schneller geschrieben werden können, ist keine akzeptable Begründung für die Verwendung kurzer Namen. Bezeichner, die nur aus einem Buchstaben bestehen, sind in der Regel keine gute Wahl und ein Zeichen von Faulheit. Es kann einige wenige Ausnahmen geben, z. B. den Buchstaben E für die Basis der natürlichen Logarithmen, Pi und eine Handvoll weiterer allgemein bekannter Fälle.

OK-Symbol Trennen Sie mehrere Wörter eines Namens durch ein Unterstreichungszeichen:

Is_Name_Valid ist besser als IsNameValid.

OK-Symbol Verwenden Sie vollständige Namen und keine Abkürzungen.

OK-Symbol Verwenden Sie nur Abkürzungen, die für das Projekt genehmigt wurden.

Wenn Abkürzungen verwendet werden, muss es sich um hinlänglich bekannte Abkürzungen des jeweiligen Anwendungsbereichs handeln (z. B. SFT für Schnelle Fourier-Transformation) oder um eine Abkürzung aus einer für das Projekt bestätigten Liste. Andernfalls werden sehr wahrscheinlich an der einen oder anderen Stelle ähnliche, aber nicht ganz identische Abkürzungen auftauchen, die für Unklarheiten sorgen und zu Fehlern führen können (z. B. die Abkürzungen Tr_Id, Trck_Id, Tr_Iden, Trid, Tid, Tr_Ident usw. für Track_Identification).

OK-Symbol Gehen Sie sparsam mit Suffixen zur Angabe der Kategorie von Ada-Konstrukten um. Sie tragen nicht zur Verbesserung der Lesbarkeit bei.

Suffixe für die Kategorie von Ada-Entitäten wie _Package für Pakete, _Error für Ausnahmen, _Type für Typ und _Param für Unterprogrammparameter sind in der Regel nicht geeignet, wenn es um die Lesbarkeit und Verständlichkeit des Codes geht. Suffixe wie _Array, _Record und _Function sind noch schlimmer. Sowohl der Ada-Compiler als auch der Leser des Codes kann eine Ausnahme anhand des Kontextes von einem Unterprogramm unterscheiden. In einer Anweisung raise oder in einer Ausnahmebehandlungsroutine kann ganz offensichtlich nur der Name einer Ausnahme erscheinen. Derartige Suffixe sind daher nur in den folgenden Situationen sinnvoll:

Generischer formaler Typ mit dem Suffix _Constrained

Zugriffstyp mit dem Suffix _Pointer oder einer anderen Form des indirekten Verweises wie _Handle oder _Reference

Ein Unterprogramm, das einen potenziell blockierenden Eingangsaufruf verdeckt, mit dem Suffix _Or_Wait

OK-Symbol Drücken Sie Namen so aus, dass sie gut zum Verwendungszweck passen.

Denken Sie über den Kontext nach, in dem deine exportierte Entität verwendet werden wird, und wählen Sie den Namen unter diesem Gesichtspunkt aus. Eine Entität wird nur einmal deklariert, jedoch häufig verwendet. Dies gilt insbesondere für die Namen von Unterprogrammen und deren Parameter. Die Namensassoziationen für die resultierenden Aufrufe sollten sich möglichst nah an natürlicher Sprache orientieren. Vergessen Sie nicht, dass die Abwesenheit von USE-Klauseln für die meisten deklarierten Entitäten den qualifizierten Namen zwingend erforderlich macht. Für formale generische Parameter, die eher in der generischen Einheit als auf der Clientseite verwendet werden, gilt es einen guten Kompromiss zu finden. Bei formalen Parametern von Unterprogrammen sollten Sie das Hauptaugenmerk jedoch definitiv darauf legen, dass der Name auf dem Client gut aussieht.

OK-Symbol Verwenden Sie englische Wörter ohne Rechtschreibfehler.

Ein Mix aus mehreren Sprachen (z. B. Französisch und Englisch) erschwert das Lesen des Codes und führt manchmal zur Mehrdeutigkeit von Bezeichnern. Da die Ada-Schlüsselwörter bereits englisch sind, sollte der Rest auch englisch sein. Die US-Schreibweise sollte bevorzugt werden, damit die integrierte Rechtschreibprüfung der Rational-Umgebung genutzt werden kann.

Zeigefingersymbol Erstellen Sie keine neuen Definitionen für Entitäten aus dem Paket 'Standard'. Dies ist absolut untersagt.

Die Missachtung dieser Regel kann Unklarheiten und dramatische Fehler nach sich ziehen. Diese Regel ließe sich auch auf andere vordefinierte Bibliothekseinheiten wie Calendar und System ausdehnen und schließt natürlich den Bezeichner Standard selbst mit ein.

OK-Symbol Vermeiden Sie, Bezeichner anderer vordefinierter Pakete wie System oder Calendar neu zu definieren.

Zeigefingersymbol Verwenden Sie Wide_Character und Wide_String nicht als Bezeichner. Beide werden im Paket 'Standard' von Ada 95 eingeführt. Verwenden Sie keine Kompiliereinheit mit dem Namen Ada.

Zeigefingersymbol Verwenden Sie die Wörter abstract, aliased,protected, requeue, tagged und until nicht als Bezeichner. Sie werden in Ada 95 als Schlüsselwörter verwendet.

Nachfolgend sind einige Namensvorschläge für verschiedene Ada-Entitäten angegeben. Vorausgesetzt wird ein generell am objektorientierten Stil angelehntes Design. Weitere Erläuterungen hierzu finden Sie in Anhang A.

Pakete

OK-Symbol Wenn mit einem Paket eine Objektklasse eingeführt wird, benennen Sie das Paket nach der Objektklasse. Der Name einer Objektklasse ist in der Regel ein normales Substantiv im Singular, an das ggf. (wenn eine parametrisierte Klasse definiert wird) das Suffix _Generic angefügt werden kann. Die Pluralform sollten Sie nur verwenden, wenn die Objekte immer in Gruppen vorkommen. Beispiele:

package Text is
package Line is
package Mailbox is
package Message is
package Attributes is
package Subscriber is
package List_Generic is      

OK-Symbol Wenn ein Paket eine Schnittstelle oder eine Funktionsgruppe angibt und keinen Bezug zu einem Objekt hat, sollten Sie dies im Namen ausdrücken:

package Low_Layer_Interface is
package Math_Definitions is      

OK-Symbol Wenn ein "logisches" Paket durch unstrukturierte Dekomposition als mehrere verschiedene Pakete ausgedrückt werden muss, verwenden Sie Suffixe aus einer für das Projekt vereinbarten Liste. Ein logisches Paket Mailbox könnte beispielsweise wie folgt implementiert werden:

package Mailbox_Definitions is
package Mailbox_Exceptions is
package Mailbox_Io is
package Mailbox_Utilities is
package Mailbox_Implementation is
package Mailbox_Main is      

Weitere annehmbare Suffixe:

_Test_Support 
_Test_Main 
_Log 
_Hidden_Definitions 
_Maintenance 
_Debug      

Typen

OK-Symbol Verwenden Sie in einem Paket, das eine Objektklasse definiert, Folgendes, sofern es ein Paket mit impliziter Kopiersemantik ist (d. h. der Typ instanziierbar und eine Form der Zuweisung möglich ist):

type Object is ...      

 

Beachten Sie, dass der Name der Klasse im Bezeichner nicht wiederholt werden sollte, da er immer in seiner vollständig qualifizierten Form verwendet wird:

Mailbox.Object
Line.Object      

Zeigefingersymbol Wenn referenzielle Semantik impliziert ist, d. h. der Typ mit Zugriffswerten (oder einer anderen Form der Dereferenzierung) implementiert wird und eine Zuweisung, sofern verfügbar, das Objekt nicht kopiert, verwenden Sie Folgendes:

 

Die Elemente werden als Suffixe verwendet, wenn ihre Verwendung ohne Suffix und nur mit dem Paketnamen als Präfix unklar oder mehrdeutig ist.

Zeigefingersymbol Wenn mehrere Objekte impliziert sind, können Sie eine der folgenden Angaben verwenden:

Zeigefingersymbol Wenn ein Objekt mit einer Zeichenfolge bezeichnet werden soll, verwenden Sie Folgendes:

type Name

OK-Symbol Aus Gründen der besseren Lesbarkeit sollte im gesamten definierenden Paket der qualifizierte Typname verwendet werden. In der Rational-Umgebung führt dies auch zu einem besseren Verhalten, wenn die Funktion [Complete] in einem Unterprogrammaufruf verwendet wird.

Schauen Sie sich dazu Subscriber.Object (vollständiger Name) im folgenden Beispiel an:

package Subscriber is
    type Object is private;
    type Handle is access Subscriber.Object;
    subtype Name is String;
    package List is new List_Generic (Subscriber.Handle);
    Master_List : Subscriber.List.Handle;
    procedure Create (The_Handle : out Subscriber.Handle;
                      With_Name  : in  Subscriber.Name);
    procedure Append (The_Subscriber : in     Subscriber.Handle;
                      To_List        : in out Subscriber.List.Handle);
    function Name_Of (The_Subscriber : Subscriber.Handle) return
            Subscriber.Name;
    ...
private
    type Object is
        record
            The_Name : Subscriber.Name (1..20);
                    ...
end Subscriber;    

Zeigefingersymbol Unter anderen Bedingungen müssen Sie für Substantive oder Qualifier+Substantiv für Typnamen verwenden. Sie können für Typen die Pluralform verwenden, damit die Singularform Objekten (Variablen) vorbehalten bleibt:

type Point is record ...
type Hidden_Attributes is ( ...
type Boxes is array ...    

Verwenden Sie Mode, Kind, Code usw. für Aufzählungstypen, ggf. auch als Suffix.

Für Array-Typen können Sie das Suffix _Table verwenden, wenn der einfache Name bereits für den Komponententyp verwendet wird. Verwenden Sie Namen oder Suffixe wie wie _Set und _List nur, wenn das Array mit der implizierten Semantik gewartet wird. Reservieren Sie _Vector und _Matrix für die entsprechenden mathematischen Konzepte.

Zeigefingersymbol Da Einzel-Task-Objekte (aus später erklärten Gründen) zu vermeiden sind, sollten Sie auch dann einen Task-Typ einführen, wenn es nur ein Objekt dieses Typs gibt. Im folgenden Fall ist eine einfache Suffixstrategie wie die Verwendung von _Type ausreichend:

task type Listener_Type is ...
for Listener_Type'Storage_Size use ...
Listener : Listener_Type;    

Zeigefingersymbol Ähnlich verhält es sich, wenn die Verwendung eines Substantivs oder eines (substantivischen Ausdrucks) für einen Typnamen und einen Objekt- oder Parameternamen zu Mehrdeutigkeit führt. Hängen Sie in diesem Fall an den Typnamen das Suffix _Kind an und verwenden Sie den Objektnamen ohne Suffix.

type Status_Kind is (None, Normal, Urgent, Red);
Status : Status_Kind := None;    

Zeigefingersymbol Für alle Elemente, die immer mehrfach vorkommen, können Sie die Pluralform für den Typ verwenden.

OK-Symbol Da Zugriffstypen nicht ohne Risiken sind, sollte der Benutzer auf die Gefahren aufmerksam gemacht werden. In der Regel enthalten Sie 'Pointer' in ihrem Namen. Verwenden Sie das Suffix _pointer, wenn der Name ohne Suffix mehrdeutig ist. Als Alternative kommt _Access in Frage. ;

Zeigefingersymbol Manchmal wird die Benennung durch ein verschachteltes Unterpaket zur Einführung einer zweiten Abstraktion vereinfacht:

package Subscriber is    ...
    package Status is
        type Kind is (Ok, Deleted, Incomplete, Suspended, 
                      Privileged);
        function Set (The_Status    : Subscriber.Status.Kind;
                      To_Subscriber : Subscriber.Handle);
    end Status;
    ...    

Ausnahmen

OK-Symbol Da Ausnahmen nur für Fehlersituationen gedacht sind, sollten Sie ein Substantiv oder einen substantivischen Ausdruck mit negativer Wertung verwenden:

Overflow, Threshold_Exceeded, Bad_Initial_Value    

Wenn der Bezeichner in einem Klassenpaket definiert wird, muss er nicht den Namen der Klasse enthalten (z. B. Bad_Initial_Subscriber_Value), da als Ausnahme immer Subscriber.Bad_Initial_Value verwendet wird.

OK-Symbol Nutzen Sie die Wörter Bad, Incomplete, Invalid, Wrong, Missing oder Illegal, anstatt systematisch auf das Wort Error zurückzugreifen, das keinen spezifischen Informationsgehalt hat.

Illegal_Data, Incomplete_Data    

Unterprogramme

OK-Symbol Verwenden Sie für Prozeduren (und Task-Einträge) Verben. Für Funktionen wollten Sie Substantive mit den Attributen oder Merkmalen der Objektklasse verwenden. Adjektive (oder Vergangenheitspartizipien) sollten für Funktionen verwendet werden, die Boolesche Werte zurückgeben.

Subscriber.Create
Subscriber.Destroy
Subscriber.List.Append
Subscriber.First_Name          -- Gibt eine Zeichenfolge zurück
Subscriber.Creation_Date       -- Gibt ein Datum zurück
Subscriber.List.Next
Subscriber.Deleted             -- Gibt einen Booleschen Wert zurück
Subscriber.Unavailable         -- Gibt einen Booleschen Wert zurück
Subscriber.Remote    

Zeigefingersymbol In manchen Fällen kann es sinnvoll sein, für Prädikate vor einem Substantiv das Präfix Is_ oder Has_ anzugeben. Achten Sie bei der Angabe auf Genauigkeit, Konsistenz und auf die Zeitform.

function Has_First_Name ...
function Is_Administrator ...
function Is_First...
function Was_Deleted ...    

Dies ist sinnvoll, wenn der einfache Name bereits als Typname oder Aufzählungsliteral verwendet wird.

Verwenden Sie Prädikate in der bejahenden Form, d. h. geben Sie die Namen nicht mit "Not_" an.

Für allgemeine Operationen sollten Sie Verben aus einer Auswahlliste für das Projekt verwenden (die erweitert werden kann, wenn sich neue Erkenntnisse zum System ergeben).

Create
Delete
Destroy
Initialize
Append
Revert
Commit
Show, Display    

Zeigefingersymbol Verwenden Sie für Prädikatfunktionen und Boolesche Parameter bejahende Namen. Verneinende Namen können zu doppelter Verneinung führen (z. B. Not Is_Not_Found) und beeinträchtigen die Lesbarkeit des Codes.

function Is_Not_Valid (...) return Boolean
procedure Find_Client (With_The_Name : in  Name;
                       Not_Found     : out Boolean)    

Dieser Code sollte wie folgt definiert werden:

function Is_Valid (...) return Boolean;
procedure Find_Client (With_The_Name: in Name;
                       Found: out Boolean)    

Der Client kann wie erforderlich die Verneinung des Ausdrucks testen. (Dies hat keine negativen Auswirkungen auf die Laufzeit.)

if not Is_Valid (...) then ....    

In einigen Fällen lässt sich mit einem Antonym aus einem negativen Prädikat ein positives machen, ohne die Semantik zu ändern. Sie könnten beispielsweise "Is_Invalid" anstatt "Is_Not_Valid" verwenden. Bejahende Namen sind jedoch besser lesbar. "Is_Valid" ist leichter zu verstehen als "not Is_Invalid".

OK-Symbol Verwenden Sie dasselbe Wort, wenn dieselbe allgemeine Bedeutung impliziert ist. Versuchen Sie nicht, Synonyme oder Varianten zu finden. Die Überladung von Namen wird im Interesse einer größeren Uniformität und unter Beachtung des Prinzips 'Möglichst wenig Überraschungen' empfohlen.

Zeigefingersymbol Falls Unterprogramme als "Oberflächen" oder "Wrapper" für Eingangsaufrufe verwendet werden, kann es sinnvoll sein, dies im Namen widerzuspiegeln. Zu diesem Zweck kann das Verb mit dem Suffix _Or_Wait versehen werden oder ein Ausdruck wie Wait_For_, gefolgt von einem Substantiv, verwendet werden.

Subscriber.Get_Reply_Or_Wait
Subscriber.Wait_For_Reply    

Einige Operationen sollten immer konsistent (mit denselben Namen) definiert werden.

Zeigefingersymbol Für Typkonvertierungen von und in Zeichenfolgen, die folgenden symmetrischen Funktionen:

    function Image und function Value    

Zeigefingersymbol Für Typkonvertierungen von maschinennaher Sprache und in maschinennahe Sprache (wie beispielsweise Byte_String für den Datenaustausch):

    procedure Read und Write    

Zeigefingersymbol Für zugeordnete Daten:

    function Allocate (besser als Create)
    function Destroy (oder Release, um auszudrücken, dass das Objekt verschwindet) 

Wenn Sie diesen Ansatz systematisch verfolgen und Namen konsistent verwenden, wird die Typkomposition viel einfacher.

OK-Symbol Für aktive Iteratoren müssen die folgenden Basiselemente (Primitives) immer definiert werden:

Initialize
Next
Is_Done
Value_Of
Reset
Werden in einem Geltungsbereich mehrere Iteratortypen eingeführt, sollten diese Basiselemente überladen werden, anstatt für jeden Iterator eine gesonderte Gruppe von Bezeichnern einzuführen (siehe [BOO87]).

OK-Symbol Wenn Sie vordefinierte Ada-Attribute als Funktionsnamen verwenden, achten Sie auf eine einheitliche allgemeine Semantik: 'First, 'Last, 'Length, 'Image, 'Value usw. Beachten Sie, dass mehrere Attribute (z. B. 'Range und 'Delta) reservierte Wörter sind und daher nicht als Funktionsnamen verwendet werden können.

Objekte und Unterprogrammparameter (oder Eingangsparameter)

OK-Symbol Stellen Sie einem Objektnamen oder Parameternamen das Präfix The_ oder This_ voran, um Eindeutigkeit zu erzielen oder zu zeigen, dass diese Entität den Schwerpunkt der Aktion bildet. Ein Nebenobjekt, temporäres Objekt oder Hilfsobjekt können Sie mit dem Präfix A_ oder Current_ versehen.

procedure Change_Name (The_Subscriber : in Subscriber.Handle;
                       The_Name       : in Subscriber.Name );
            declare
    A_Subscriber : Subscriber.Handle := Subscriber.First;
begin
    ...
    A_Subscriber := Subscriber.Next (The_Subscriber);
end;    

OK-Symbol Verwenden Sie für Boolesche Objekte eine bejahende Prädikatklausel.

Found_It
Is_Available    

 

Is_Not_Available sollten Sie vermeiden.

OK-Symbol Verwenden Sie für Task-Objekte ein Substantiv oder einen substantivischen Ausdruck, das bzw. der eine aktive Entität impliziert.

Listener
Resource_Manager
Terminal_Driver    

OK-Symbol Bei Parametern können Sie die Lesbarkeit verbessern, wenn Sie dem Klassennamen oder einem charakteristischen Substantiv eine Präposition als Präfix voranstellen. Dies gilt insbesondere für den Aufrufenden, wenn benannte Zuordnungen verwendet werden. Weitere hilfreiche Präfixe für Hilfsparameter haben die Form Using_ oder, im Fall eines Parameters in out, der durch einen Nebeneffekt beteiligt ist, Modifying_.

procedure Update (The_List     : in out Subscriber.List.Handle;
                  With_Id      : in     Subscriber.Identification;
                  On_Structure : in out Structure;
                  For_Value    : in     Value);
procedure Change (The_Object   : in out Object;
                  Using_Object : in     Object);    

OK-Symbol Aus Sicht des Aufrufenden ist die Reihenfolge wichtig, in der Parameter deklariert sind:

Auf diese Weise können Sie die Vorteile von Standardwerten nutzen, ohne für die wichtigsten Parameter benannte Zuordnungen verwenden zu müssen.

OK-Symbol Der Modus "in" muss explizit angegeben werden. Dies gilt auch für Funktionen.

Generische Einheiten

OK-Symbol Wählen Sie den besten Namen aus, den Sie für eine nicht generische Version verwenden würden, den Klassennamen für ein Paket oder ein transitives Verb (bzw. einen verbalen Ausdruck) für eine Prozedur (siehe oben), und versehen Sie den Namen mit dem Suffix _Generic.

OK-Symbol Wenn ein generisches Paket eine abstrakte Datenstruktur definiert, verwenden Sie für formale generische Typen Item oder Element und für die exportierte Abstraktion Structure oder ein anderes geeignetes Substantiv.

OK-Symbol Verwenden Sie bei passiven Iteratoren im Bezeichner ein Verb wie Apply, Scan, Traverse, Process oder Iterate.

generic
		with procedure Act	(Upon : in out Element);
procedure Iterate_Generic	(Upon : in out Structure);    

OK-Symbol Die Namen formaler generischer Parameter dürfen keine Homographen sein.

generic
    type Foo is private;
    type Bar is private;
    with function Image (X : Foo) return String;
    with function Image (X : Bar) return String;
package Some_Generic is ...    

Dieser Code sollte ersetzt werden durch:

generic
    type Foo is private;
    type Bar is private;
    with function Foo_Image (X : Foo) return String;
    with function Bar_Image (X : Bar) return String;
package Some_Generic is ...    

Sofern erforderlich, können die formalen generischen Parameter in der generischen Einheit umbenannt werden:

function Image (Item : Foo) return String Renames Foo_Image;
function Image (Item : Bar) return String Renames Bar_Image;    

Strategien für die Benennung von Subsystemen

Wenn ein großes System in Rational-Subsysteme (oder eine andere Form verbundener Programmbibliotheken) partitioniert wird, sollte eine Benennungsstrategie mit folgenden Zielen definiert werden:

Namensunverträglichkeiten vermeiden

In einem System mit mehreren hundert Objekten und Teilobjekten sind Namensunverträglichkeiten auf der Ebene der Bibliothekseinheiten wahrscheinlich. Die Programmierer werden es schwer haben, genug Synonyme für so sinnvolle Namen wie Utilities, Support, Definitions usw. zu finden.

Ada-Entitäten schnell finden

Auf einem Rational-Host sind Entitäten mit Suchfunktionen leicht zu finden. Wenn Code jedoch auf ein Ziel mit eigenen Tools (Debugger, Testtools usw.) portiert wird, kann das Auffinden einer Prozedur Utilities.Get in 2.000 Einheiten, die auf 100 Subsysteme verteilt sind, für einen Projektneuling eine Herausforderung darstellen.

OK-Symbol Versehen Sie die Namen von Einheiten auf Bibliotheksebene mit einem Präfix für das übergeordnete Subsystem (vier Buchstaben umfassende Abkürzung für das Subsystem).

Das Dokument zur Softwarearchitektur (SAD) enthält eine Liste der Subsysteme. Diese Regel gilt nicht für Bibliotheken mit Komponenten, die sehr häufig (und somit wahrscheinlich in einer Vielzahl von Projekten, für im Handel erhältliche Standardprodukte und für Standardeinheiten) wiederverwendet werden.

Beispiel:

Comm - Communication

Dbms - Database management

Disp - Displays

Math - Mathematical packages

Drive - Drivers

Allen Bibliothekseinheiten aus dem Subsystem Disp wird beispielsweise das Präfix Disp_ vorangestellt. Das Team oder Unternehmen, das für Disp zuständig ist, hat ansonsten völlige Freiheit bei der Auswahl von Namen. Falls sowohl Dbms als auch Disp eine Objektklasse mit dem Namen Subscriber einführen müssen, ergeben sich Paketnamen wie die folgenden:

Disp_Subscriber
Disp_Subscriber_Utilities
Disp_Subscriber_Defs
Dbms_Subscriber
Dbms_Subscriber_Interface
Dbms_Subscriber_Defs    

Kapitel 6

Deklaration von Typen, Objekten und Programmeinheiten

OK-SymbolDie strikte Verwendung festgelegter Datentypen in Ada verhindert die Vermischung verschiedener Datentypen. Konzeptionell unterschiedliche Typen müssen als verschiedene benutzerdefinierte Typen realisiert werden. Durch Subtypen können Sie die Lesbarkeit des Programms verbessern und die Effektivität der vom Compiler generierten Laufzeitüberprüfungen erhöhen.

Aufzählungstypen

OK-Symbol Führen Sie in Aufzählungen, wo immer dies möglich ist, einen gesonderten Literalwert für einen nicht initialisierten Wert, einen ungültigen Wert oder keine vorhandenen Werte ein.

type Mode  is (Undefined, Circular, Sector, Impulse);
type Error is (None, Overflow, Invalid_Input_Value,Ill-formed_Name);    

Damit werden die Regeln für die systematische Initialisierung von Objekten unterstützt. Stellen Sie dieses Literal an den Anfang und nicht an das Ende der Liste, um die Wartung zu erleichtern und angrenzende Teilbereiche gültiger Werte zu ermöglichen:

subtype Actual_Error is Error range Overflow .. Error'Last;    

Numerische Typen

OK-Symbol Vermeiden Sie die Verwendung vordefinierter numerischer Typen.

Wenn Sie sich ein hohes Maß an Portierbarkeit und Wiederverwendbarkeit zum Ziel gesetzt haben oder eine Kontrolle des von numerischen Objekten belegten Speicherbereichs erforderlich ist, dürfen keine vordefinierten numerischen Typen (aus dem Paket 'Standard') verwendet werden. Diese Forderung ergibt sich daraus, dass die Merkmale der vordefinierten Typen Integer und Float im Referenzhandbuch zur Programmiersprache Ada [ISO87] (absichtlich) nicht angegeben sind.

Zeigefingersymbol Eine erste systematische Strategie sieht die Einführung projektspezifischer numerischer Typen vor, z. B. in einem Paket System_Types, deren Namen auf die Genauigkeit oder Speichergröße hinweisen:

package System_Types is
        type Byte is range -128 .. 127;
        type Integer16 is range -32568 .. 32567;
        type Integer32 is range ...
        type Float6 is digits 6; 
        type Float13 is digits 13;
...
end System_Types;    

Zeigefingersymbol Erstellen Sie keine neuen Definitionen für Standardtypen (aus dem Paket 'Standard').

Zeigefingersymbol Geben Sie nicht an, von welchem Basistyp die numerischen Typen abgeleitet werden sollen. Überlassen Sie die Auswahl des Typs dem Compiler. Hier sehen Sie ein negatives Beispiel:

type Byte is new Integer range -128 .. 127;    

Zeigefingersymbol Der Name Float6 ist besser als Float32, selbst wenn 32-Bit-Floats auf den meisten Maschinen eine Genauigkeit von sechs Stellen erreichen.

Zeigefingersymbol Leiten Sie in den verschiedenen Abschnitten des Projekts Typen ab, deren Namen aussagekräftiger als die in Baty_System_Types sind. Einige der exaktesten Typen können als nicht öffentliche Typen definiert werden, um eine eventuelle Portierung auf ein Ziel mit beschränkter Genauigkeitsunterstützung zu ermöglichen.

Diese Strategie ist in folgenden Fällen anzuwenden:

In allen anderen Fällen kommt eine andere, einfachere Strategie zum Tragen: Definieren Sie immer neue Typen, indem Sie den erforderlichen Bereich und die erforderliche Genauigkeit angeben. Definieren Sie jedoch nie den Basistyp, von dem diese abgeleitet werden sollen. Deklarieren Sie beispielsweise Folgendes:

type Counter is range 0 .. 100;
type Length is digits 5;    

Diese Deklaration ist der folgenden vorzuziehen.

type Counter is new Integer range 1..100; -- Ein 64-Bit-Integer wäre möglich.
type Length is new Float digits 5; -- 13 Stellen wären möglich. 

Bei dieser zweiten Strategie ist der Programmierer gezwungen, über die für jeden Typ erforderlichen exakten Grenzen und die erforderliche Genauigkeit jedes Typs nachzudenken. Es reicht nicht aus, willkürlich eine bestimmte Anzahl von Bits zu wählen. Beachten Sie jedoch, dass der Compiler bei einer Abweichung des Bereichs von dem des Basistyps systemtische Bereichsprüfungen durchführt. Im obigen Beispiel würde eine solche Prüfung für den Typ Counter durchgeführt werden, wenn der Basistyp ein 32-Bit-Integer ist.

Zeigefingersymbol Falls die Bereichsprüfungen zu einem Problem werden, können Sie sie mit folgender Deklaration vermeiden:

type Counter_Min_Range is range 0 .. 10_000;
type Counter is range Counter_Min_Range'Base'First .. Counter_Min_Range'Base'Last;    

OK-Symbol Vermeiden Sie, dass Standardtypen durch Konstrukte wie Schleifen, Indexbereiche usw. in den Code gelangen.

Subtypen der vordefinierten numerischen Typen werden nur unter folgenden Umständen verwendet:

Beispiel:

for I in 1 .. 100 loop ...     
-- I ist vom Typ Standard.Integer.
type A is array (0 .. 15) of Boolean; 
-- Der Index ist Standard.Integer.    

Verwenden Sie stattdessen dieses Format:

Some_Integer range L ..                     H
for I in Counter range 1 .. 100 loop ...
type A is array (Byte range 0 .. 15) of Boolean;    

Zeigefingersymbol Versuchen Sie nicht, Typen ohne Vorzeichen zu implementieren.

In Ada gibt es keine ganzzahligen Typen mit vorzeichenloser Arithmetik. Gemäß Sprachdefinition werden alle ganzzahligen Typen nicht oder nur indirekt von den vordefinierten Typen abgeleitet, die ihrerseits symmetrisch zu null sein müssen.

Reale Typen

OK-Symbol Aus Gründen der Portierbarkeit sollten Sie nur auf reale Typen zurückgreifen, deren Werte in den folgenden Bereichen liegen:

[-F'Large .. -F'Small]  [0.0]  [F'Small .. F'Large]    

Beachten Sie, dass F'Last und F'First unter Umständen keine Modellzahlen sind und nicht einmal in einem Modellintervall enthalten sein müssen. Die relative Position von F'Last und F'Large hängt von der Typdefinition und der zugrunde liegenden Hardware ab. Ein besonders tückisches Beispiel ist der Fall, bei dem 'Last eines Festkommatyps nicht zum Typ gehört:

type FF is delta 1.0 range -8.0 .. 8.0;    

Streng genommen besagt das Ada-Referenzhandbuch [3.5.9(6)], dass FF'Last = 8.0 nicht zum Typ gehören kann.

Verwenden Sie für die Darstellung großer oder kleiner realer Zahlen die Attribute 'Large oder 'Small (und deren negative Entsprechungen), nicht jedoch 'First und 'Last wie bei ganzzahligen Typen.

OK-Symbol Verwenden Sie für Gleitkommatypen nur <= und >=, in keinem Fall aber =, <, >, /=.

Die Semantik absoluter Vergleiche ist schlecht definiert (Gleichheit der Darstellung und Ungleichheit beim erforderlichen Genauigkeitsgrad). X < Y muss beispielsweise nicht zwingend zu demselben Ergebnis führen wie not (X >= Y). Tests auf Gleichheit (A = B) sollten wie folgt ausgedrückt werden:

abs (A - B) <= abs(A)*F'Epsilon    

Zur Verbesserung der Lesbarkeit und der Wartungsfreundlichkeit wäre es ratsam, einen Ist-gleich-Operator zu verwenden, um den obigen Ausdruck zu kapseln.

Beachten Sie, dass der folgende einfachere Ausdruck nur gültig ist, wenn der Wert von A und B klein ist:

abs (A - B) <= F'Small    

Dieser einfache Ausdruck kann daher nicht allgemein empfohlen werden.

OK-Symbol Vermeiden Sie jeden Verweis auf die vordefinierte Ausnahme Numeric_Error. Gemäß einer als bindend geltenden Interpretation des Ada-Board wird jetzt in allen Fällen, die bisher die Ausnahme Numeric_Error ausgelöst haben, Constraint_Error ausgelöst. In Ada 95 wird die Ausnahme Numeric_Error nicht mehr verwendet.

Zeigefingersymbol Sollte die Implementierung noch immer die Ausnahme Numeric_Error auslösen (wie es beim nativen Rational-Compiler der Fall ist), sollten Sie in derselben Alternative einer Ausnahmebehandlungsroutine eine Überprüfung auf Constraint_Error und Numeric_Error vorsehen:

when Numeric_Error | Constraint_Error => ...    

Zeigefingersymbol Vermeiden Sie Unterlauf.

Unterlauf wird in Ada nicht erkannt. Das Ergebnis ist 0.0, und es wird keine Ausnahme ausgelöst. Sie können eine explizite Überprüfung auf Unterlauf ausführen, indem Sie testen, ob das Ergebnis einer Multiplikation oder Division 0.0 ist, wenn keiner der Operanden 0.0 ist. Sie können auch eigene Operatoren implementieren, um diese Überprüfung automatisch durchzuführen, was jedoch zu Lasten der Effizienz geht.

Handsymbol 'Achtung' Festkommatypen können nur eingeschränkt verwendet werden.

Verwenden Sie weitestgehend Gleitkommatypen. Eine unangemessene Verwendung von Festkommatypen in einer Ada-Implementierung kann zu Problemen bei der Portierbarkeit führen.

Zeigefingersymbol Bei Festkommatypen sollte 'Small gleich 'Delta sein.

Dies muss aus dem Code hervorgehen. Die Tatsache, dass die Standardauswahl für 'Small eine Zweierpotenz ist, führt zu den verschiedensten Problemen. Die folgende Schreibweise ist eine Möglichkeit, die Auswahl für 'Small zu verdeutlichen:

Fx_Delta : constant := 0.01;
type FX is delta Fx_Delta range L .. H;
for FX'Small use Fx_Delta;    

Falls keine Längenklauseln für Festkommatypen unterstützt werden, lässt sich diese Regel nur befolgen, indem explizit ein 'Delta angegeben wird, das eine Zweierpotenz ist. Bei Subtypen kann 'Small von 'Delta verschieden sein. (Die Regel gilt nur für die Typdefinition bzw., laut Terminologie des Ada-Referenzhandbuchs, für den "ersten benannten Subtyp".)

Satztypen

OK Geben Sie für die Komponenten eines Satztyps nach Möglichkeit statische Anfangswerte an. (Häufig können Werte wie 'First oder 'Last verwendet werden.)

Wenden Sie diese Regel jedoch nicht auf Diskriminanten an. Nach den Regeln der Sprache haben Diskriminanten immer Werte. Veränderliche Datensätze (d. h. Datensätze mit Standardwerten für Diskriminanten) sollten nur eingeführt werden, wenn Veränderlichkeit als Merkmal erwünscht ist. In allen anderen Fällen erfordern veränderliche Datensätze zusätzliche Hauptspeicherkapazität (häufig wird die umfangreichste Variante zugeordnet) und mehr Zeit (komplexere Variantenprüfungen).

OK-Symbol Vermeiden Sie Funktionsaufrufe in den Standardanfangswerten aller Komponenten, da diese zu einem Fehler "Access before Elaboration" (Zugriff vor Ausarbeitung) führen (siehe Abschnitt "Programmstruktur und Kompilierungsprobleme").

Zeigefingersymbol Wenn bei veränderlichen Datensätzen (deren Diskriminanten Standardwerte haben) eine Diskriminante für die Dimensionierung einer anderen Komponente verwendet wird, sollte die Diskriminante als Subtyp mit einem angemessen kleinen Bereich angegeben werden.

Beispiel:

type Record_Type (D : Integer := 0) is 
        record
            S : String (1 .. D);
        end record;
A_Record : Record_Type;    

Dieses Beispiel wird in den meisten Implementierung Storage_Error auslösen. Geben Sie für den Subtyp der Diskriminanten D einen angemessenen Bereich an.

OK-Symbol Gehen Sie bezüglich des physischen Layouts von Datensätzen von keinen Annahmen aus.

Komponenten müssen (im Gegensatz zu anderen Programmiersprachen) in der Reihenfolge dargestellt werden, die in der Definition festgelegt ist.

Zugriffstypen

Handsymbol 'Achtung' Verwenden Sie Zugriffstypen nur eingeschränkt.

Dies gilt insbesondere für Anwendungen, die permanent auf kleinen Maschinen ohne virtuellen Speicher ausgeführt werden sollen. Zugriffstypen sind nicht ungefährlich, denn kleine Programmierungsfehler können dazu führen, dass kein Speicher verfügbar ist. Aber selbst bei guter Programmierung ist eine Fragmentierung des Speichers möglich. Zugriffstypen sind außerdem langsamer. Die Verwendung von Zugriffstypen muss im Rahmen einer Strategie für das gesamte Projekt erfolgen. Diese Strategie sollte die Überwachung von Objektgruppen und deren Größe sowie von Zuordnungs- und Freigabepunkten vorsehen. Der für einen Zugriffstyp gewählte Name sollte dessen Natur deutlich machen. Wählen Sie Pointer oder einen Namen mit dem Suffix _Pointer.

OK-Symbol Ordnen Sie Objektgruppen während der Ausarbeitung des Programms zu und geben Sie systematisch die Größe der einzelnen Objektgruppen an.

Der (in Speichereinheiten angegebene) Wert kann statisch sein oder dynamisch berechnet werden (z. B. aus einer Datei gelesen werden). Der Grund für diese Regel ist, dass ein Programm gleich beim Start scheitern sollte und nicht erst ungewisse Zeit später. Für generische Pakete kann zu diesem Zweck eine zusätzliche generische Formel verwendet werden, die die Größe angibt.

Beachten Sie, dass für jedes zugeordnete Objekt oft ein gewisser Mehrbedarf an Speicher besteht. Manchmal ordnen die Laufzeiten auf dem Zielsystem jedem Speicherblock Zusatzinformationen zur internen Verwaltung zu. Für das Speichern einer Objektgruppe mit N Objekten, die jeweils eine Größe von M Speichereinheiten haben, müssen also gegebenenfalls mehr als N * M Speichereinheiten zugeordnet werden, beispielsweise N * (M + K). Den Wert K für diesen zusätzlichen Bedarf können Sie Anhang F der Veröffentlichung [ISO87] entnehmen oder durch Experimente ermitteln.

OK-Symbol Kapseln Sie die Verwendung von Zuordnungen (Ada-Basiselement new) und Freigaben. Sie sollten sich nicht auf Unchecked_Deallocation verlassen, sondern eine interne Liste der freien Blöcke führen, sofern dies machbar ist.

Falls eine rekursive Datenstruktur mit einem Zugriffstyp implementiert wird, wird dieser sehr wahrscheinlich auf einen Satztyp zugreifen, der (als eine Komponente) denselben Zugriffstyp hat. Auf diese Weise können freie Zellen zu einer Liste freier Blöcke zusammengeführt und erneut genutzt werden. Dafür ist kein zusätzlicher Speicherplatz erforderlich (außer für den Zeiger auf den Kopfsatz der Liste).

Von new ausgelöste Storage_Error-Ausnahmen sollten Sie explizit behandeln und eine aussagefähigere Ausnahme exportieren, die angibt, dass die maximale Speichergröße für die Objektgruppe nicht verfügbar ist.

Bei nur einem Zuordnungs- und Freigabepunkt ist im Falle eines Fehlers eine leichtere Trace- und Debug-Verarbeitung möglich.

OK-Symbol Geben Sie nur zugeordnete Zellen derselben Größe (und also mit derselben Diskriminante) frei.

Dies ist wichtig, um die Fragmentierung des Speichers zu vermeiden. Unchecked_Deallocation wird höchstwahrscheinlich keinen Dienst für die Speicherverdichtung bereitstellen. Sie sollten überprüfen, ob das Laufzeitsystem in der Lage ist, freigegebene benachbarte Blöcke zu verschmelzen.

OK-Symbol Stellen Sie systematisch eine primitive Funktion Destroy (oder Free oder Release) für Zugriffstypen bereit.

Dies ist besonders für abstrakte Datentypen wichtig, die mit Zugriffstypen implementiert werden. Sie sollten sich systematisch daran halten, wenn Sie mehrere dieser Typen kombinieren möchten.

OK-Symbol Geben Sie systematisch Objekte frei.

Versuchen Sie, die Aufrufe für Zuordnungen und Freigaben einander zuzuordnen, um sicherzustellen, dass alle zugeordneten Daten wieder freigegeben werden. Versuchen Sie, Daten in dem Bereich freizugeben, in dem Sie zugeordnet wurden. Vergessen Sie nicht die Freigabe beim Auftreten von Ausnahmen. Dies ist einer der Fälle, in denen ein alternatives Konstrukt when others verwendet werden sollte, das mit einer Anweisung raise endet.

Die bevorzugte Strategie lautet "Abrufen - Verwenden - Freigeben". Das Programm ruft die Objekte ab (wobei eine dynamische Struktur erstellt wird), verwendet die Objekte dann und muss sie schließlich wieder freigeben. Stellen Sie sicher, dass diese drei Operationen im Code klar bezeichnet sind und dass an allen möglichen Rahmen-Exits (einschließlich Ausnahmen) eine Freigabe erfolgt.

Zeigefingersymbol Achten Sie darauf, die temporären zusammengesetzten Datenstrukturen freizugeben, die in Datensätzen enthalten sein können.

Beispiel:

type Object is
        record
    Field1: Some_Numeric;
    Field2: Some_String;
    Field3: Some_Unbounded_List;
        end record;

'Some_Unbounded_List' ist hier eine zusammengesetzte verlinkte Struktur (eine Struktur, die sich aus einer Reihe verlinkter Objekte zusammensetzt). Schauen wir uns nun eine typische Attributfunktion an:

function Some_Attribute_Of(The_Object: Object_Handle) return 
Boolean is Temp_Object: The_Object;
begin
    Temp_Object := Read(The_Object);
    return Temp_Object.Field1 < Some_Value;
end Some_Attribute_Of;    

Die zusammengesetzte Struktur, die beim Einlesen des Objekts in Temp_Object implizit im Heap-Speicher erstellt wird, wird nie freigegeben, ist jetzt jedoch unerreichbar. Dies ist ein Speicherleck. Für so aufwendige Strukturen wäre die Implementierung des Paradigmas 'Anfordern - Verwenden - Freigeben' eine saubere Lösung. Ihr Client sollte das Objekt also zunächst anfordern, es dann wie erforderlich verwenden und anschließend freigeben:

procedure Get (The_Object  : out Object;
               With_Handle : in  Object_Handle);
function Some_Attribute_Of(The_Object : Object) 
                        return Some_Value;
function Other_Attribute_Of(The_Object	: Object) 
                        return Some_Value;
...
procedure Release(The_Object: in out Object);    

Der Clientcode könnte wie folgt aussehen:

             declare
    My_Object: Object;
begin
    Get (My_Object, With_Handle => My_Handle);
    ...
    Do_Something
      (The_Value => Some_Attribute_Of(My_Object));
      ...
    Release(My_Object);
end;    

Private Typen

OK-Symbol Deklarieren Sie Typen immer dann als privat, wenn es zum Verbergen von Implementierungsdetails erforderlich ist.

In folgenden Fällen müssen Implementierungsdetails mit einem privaten Typ verdeckt werden:

In der Rational-Umgebung können private Typen in Verbindung mit geschlossenen privaten Komponenten und Subsystemen dazu betragen, die Auswirkungen einer eventuellen Änderung des Schnittstellendesigns so gering wie möglich zu halten.

Zeigefingersymbol Verwenden Sie private Typen nicht, wenn der entsprechende vollständige Typ die bestmögliche Abstraktion ist, wie es bei der so genannten "reinen" objektorientierten Programmierung üblich ist. Seien Sie pragmatisch. Lassen Sie sich von der Frage leiten, ob die Deklaration eines Typs als private einen Vorteil bringt.

Ein mathematischer Vektor sollte beispielsweise als ein Array oder ein Punkt auf einer Ebene als ein Datensatz dargestellt werden und nicht als privater Typ.

type Vector is array (Positive range <>) of Float;
Type Point is 
        record
        X, Y : Float := Float'Large;
        end record;

Die Array-Indizierung, die Auswahl der Satzkomponenten und die Aggregatschreibweise sind weit besser lesbar (und unter Umständen sogar effizienter) als eine Reihe von Unterprogrammaufrufen, die bei unnötigerweise privaten Typen erforderlich wären.

OK-SymbolDeklarieren Sie private Typen als limited, wenn die Standardzuordnung oder der Vergleich der eigentlichen Objekte und Werte ohne Bedeutung, nicht intuitiv oder unmöglich ist.

Dies trifft in folgenden Situationen zu:

OK-Symbol Ein begrenzter privater Typ sollte sich selbst initialisieren.

Eine Objektdeklaration eines solchen Typs muss einen angemessenen Anfangswert empfangen, da die spätere Zuordnung eines Wertes im Allgemeinen nicht möglich ist, ohne das Auslösen einer Ausnahme während eines Unterprogrammaufrufs zu riskieren.

Zeigefingersymbol Geben Sie für begrenzte Typen eine Copy-Prozedur (oder Assign-Prozedur) und eine Destroy-Prozedur an, wo immer dies machbar und sinnvoll ist.

Zeigefingersymbol Sie können den Bedienungskomfort einer generischen Einheit verbessern, indem Sie die formalen Typen der Einheit als begrenzte private Typen angeben, solange intern keine Gleichheit oder Zuordnung erforderlich ist.

In Verbindung mit der vorherigen Regel können Sie dann eine formale generische Copy- und Destroy-Prozedur importieren und, falls sinnvoll, ein Prädikat Are_Equal.

Zeigefingersymbol Geben Sie in der Spezifikation für formale generische private Typen an, ob der entsprechende tatsächliche Typ beschränkt sein muss oder nicht.

Hierzu können Sie eine Namenskonvention und/oder einen Kommentar verwenden:

generic
    -- muss beschränkt sein
    type Constrained_Element is limited private;
package ...    

Alternativ dazu können Sie das von Rational definierte Pragma Must_Be_Constrained verwenden:

generic
    type Element is limited private;
    pragma Must_Be_Constrained (Element);
package ...    

Abgeleitete Typen

Zeigefingersymbol Wenn Sie einen Typ ableiten, werden auch alle (ableitbaren) Unterprogramme abgeleitet, die in demselben Abschnitt wie der übergeordnete Typ deklariert sind. Es ist daher nicht sinnvoll, sie alle im Deklarationsabschnitt des abgeleiteten Typs neu als Oberflächen zu definieren. Generische Unterprogramme können jedoch nicht abgeleitet werden und müssen daher möglicherweise neu als Oberflächen definiert werden.

Beispiel:

package Base is
    type Foo is
        record
            ...
        end record;
    procedure Put(Item: Foo);
    function Value(Of_The_Image: String) return Foo;
end Base;
with Base;
package Client is
    type Bar is new Foo;
    -- An dieser Stelle sind die folgenden Deklarationen
    -- implizit:
    -- 
    -- function "="(L,R: Bar) return Boolean;
    -- 
    -- procedure Put(Item: bar);
    -- function Value(Of_The_Image: String) return Bar;
    -- 
end Client;    

Es ist daher nicht notwendig, diese Operationen als Oberflächen neu zu definieren. Beachten Sie jedoch, dass generische Unterprogramme (wie passive Iteratoren) nicht zusammen mit anderen Operationen abgeleitet werden und daher als Oberflächen neu exportiert werden müssen. Unterprogramme, die nicht zusammen mit der Spezifikation, die die Deklaration des Basistyps enthält, definiert sind, können gleichfalls nicht abgeleitet werden und müssen als Oberflächen neu exportiert werden.

Objektdeklarationen

OK-Symbol Geben Sie in Objektdeklarationen Anfangswerte an, sofern das Objekt kein selbstinitialisierendes Objekt ist und es keinen impliziten Standardanfangswert gibt (wie es beispielsweise bei Zugriffstypen, Task-Typen oder Datensätzen mit Standardwerten für Nichtdiskriminantenfelder der Fall ist).

Der zugeordnete Wert muss ein realer und sinnvoller Wert sein, nicht ein beliebiger Wert für den Typ. Wenn der tatsächliche Anfangswert verfügbar ist, z. B. einer der Eingabeparameter, ordnen Sie diesen zu. Falls es nicht möglich ist, einen sinnvollen Wert zu berechnen, sollten Sie das Objekt später deklarieren oder, sofern verfügbar, einen NIL-Wert zuordnen.

Zeigefingersymbol Ein NIL-Wert bedeutet, dass das Objekt "nicht initialisiert" ist. Er wird für die Deklaration von Konstanten verwendet, die als ein "nicht verwendbarer, aber bekannter Wert" genutzt werden können, der von Algorithmen kontrolliert zurückgewiesen werden kann.

Der NIL-Wert sollte, soweit möglich, ausschließlich für die Initialisierung verwendet werden, so dass sein Auftauchen immer ein Hinweis auf einen Fehler durch eine nicht initialisierte Variable ist.

Es ist nicht immer möglich, für alle Typen einen NIL-Wert zu deklarieren. Dies gilt insbesondere für modulare Typen, z. B. für eine Winkel. Wählen Sie in diesem Fall den am wenigsten wahrscheinlichen Wert.

Zeigefingersymbol Beachten Sie, dass Code zur Initialisierung großer Datensätze aufwendig sein kann, besonders, wenn es Varianten des Datensatzes gibt und ein Anfangswert nicht statisch ist (oder noch präziser, wenn der Wert in der Kompilierzeit nicht berechnet werden kann). Manchmal ist es effizienter, einen immer geltenden Anfangswert festzulegen (z. B. in dem Paket, das den Typ definiert), und diesen explizit zuzuordnen.

R : Some_Record := Initial_Value_For_Some_Record;    

Anmerkung:

Die Erfahrung zeigt, dass nicht initialisierte Variablen eine der Hauptfehlerquellen beim Portieren von Code sind und eine der Hauptursachen für Programmierfehler. Diese Situation verschärft sich, wenn der Entwicklungshost dem Programmierer "entgegenkommt" und zumindest für einige der Objekte Standardwerte bereitstellt (z. B. für den Typ Integer im nativen Rational-Compiler), oder wenn das Zielsystem vor dem Programmladen Nullen in den Speicher schreibt (wie es beispielsweise bei einer DEC VAX der Fall ist). Wenn es Ihnen um Portierbarkeit geht, sollten Sie stets das Schlimmste annehmen.

Zeigefingersymbol Auf die Zuordnung eines Anfangswertes in der Deklaration kann verzichtet werden, wenn sie aufwendig ist und dem Objekt vor dessen Verwendung offensichtlich ein Wert zugeordnet wird.

Beispiel:

procedure Schmoldu is
    Temp : Some_Very_Complex_Record_Type;
 -- später initialisiert
begin
    loop
        Temp := Some_Expression ...
        ...    

OK-Symbol Vermeiden Sie die Verwendung von Literalwerten im Code.

Verwenden Sie Konstanten (mit einem Typ), wenn der definierte Wert an einen Typ gebunden ist. Andernfalls sollte Sie benannte Zahlen verwenden, insbesondere für alle reinen (dimensionslosen) Werte.

Earth_Radius : constant Meter := 6366190.7;   -- in Metern
Pi           : constant       := 3.141592653; -- keine Einheit 

Zeigefingersymbol Definieren Sie zusammengehörige Konstanten mit universellen statischen Ausdrücken.

Bytes_Per_Page :   constant := 512;
Pages_Per_Buffer : constant := 10;
Buffer_Size :      constant := Bytes_Per_Page * Pages_Per_Buffer;
Pi_Over_2   :      constant := Pi / 2.0;    

Hier wird der Fakt genutzt, dass diese Ausdrücke in der Kompilierzeit exakt berechnet werden müssen.

OK-Symbol Deklarieren Sie keine Objekte mit anonymen Typen. Weitere Informationen hierzu finden Sie im Abschnitt 3.3.1 des Ada-Referenzhandbuchs.

Solche Deklarationen senken die Wartungsfreundlichkeit, bewirken, dass Objekte nicht als Parameter übergeben werden können, und führen oft zu Fehlern durch Typkonflikte.

Unterprogramme und generische Einheiten

Zeigefingersymbol Unterprogramme können als Prozeduren oder Funktionen deklariert werden. Anhand der folgenden allgemeinen Kriterien können Sie entscheiden, welche Form der Deklaration angemessen ist.

Funktionsdeklaration:

Prozedurdeklaration:

OK-Symbol Vermeiden Sie Standardwerte für formale generische Parameter, die für Dimensionierungsstrukturen (Tabellen, Objektsammlungen usw.) verwendet werden.

OK-Symbol Schreiben Sie lokale Prozeduren mit möglichst wenig Nebeneffekten und Funktionen ganz ohne Nebeneffekte. Dokumentieren Sie die Nebeneffekte.

Nebeneffekte sind in der Regel Modifikationen globaler Variablen und werden manchmal nur beim Lesen des Hauptteils des Unterprogramms bemerkt. Der Programmierer ist sich unter Umständen nicht darüber im Klaren, dass es an der Aufrufstelle zu Nebeneffekten kommt.

Der Code ist stabiler, besser verständlich und weniger von seinem Inhalt abhängig, wenn die erforderlichen Objekte als Parameter übergeben werden.

Diese Regel gilt vor allem für lokale Unterprogramme. Exportierte Unterprogramme benötigen häufig legitimen Zugriff auf globale Variablen im Pakethauptteil.


Kapitel 7

Ausdrücke und Anweisungen

Ausdrücke

Zeigefingersymbol Verbundausdrücke werden durch paarige runde Klammern deutlicher.

Die Verschachtelungsebene eines Ausdrucks definiert die Anzahl verschachtelter paariger Klammern, die für die Auswertung eines Ausdrucks von links nach rechts erforderlich sind, wenn die Vorrangregeln für Operatoren ignoriert werden.

Zeigefingersymbol Beschränken Sie Ausdrücke auf vier Verschachtelungsebenen.

OK-Symbol Satzaggregate sollten benannte Zuordnungen verwenden und qualifiziert sein.

Subscriber.Descriptor'(Name    => Subscriber.Null_Name,
                       Mailbox => Mailbox.Nil,
                       Status  => Subscriber.Unknown,
                   ...);    

Zeigefingersymbol Die Verwendung von when others ist für Satzaggregate untersagt.

Dies liegt daran, dass Datensätze im Gegensatz zu Arrays von Natur als heterogene Strukturen sind. Eine uniforme Zuordnung ist daher unvernünftig.

Zeigefingersymbol Verwenden Sie für einfache Prädikate an Stelle der Anweisungen "if...then...else" Boolesche Ausdrücke.

function Is_In_Range(The_Value: Value; The_Range: Range)
     return Boolean is
begin
            if The_Value >= The_Range.Min and The_Value <= The_Range.Max then return True; 
        end if;
end Is_In_Range;    

Es wäre besser, folgende Schreibweise zu verwenden:

function Is_In_Range(The_Value: Value; The_Range: Range)
     return Boolean is
begin
    return The_Value >= The_Range.Min 
        and The_Value <= The_Range.Max;
end Is_In_Range;    

Komplexe Ausdrücke mit zwei oder mehr if-Anweisungen, sollten nicht auf diese Weise geändert werden, wenn dadurch die Lesbarkeit beeinträchtigt wird.

Anweisungen

OK-Symbol Schleifenanweisungen sollten in folgenden Fällen Namen haben:

Forever: loop
   ...
end loop Forever;    

OK-Symbol Wenn eine Schleife einen Namen hat, sollte eine enthaltene exit-Anweisung diesen Namen angeben.

OK-Symbol Schleifen, für die am Anfang eine Überprüfung des Beendigungsstatus erforderlich ist, sollten die Form einer "while"-Schleife haben. Schleifen, bei denen der Beendigungsstatus an anderer Stelle überprüft werden muss, sollten die allgemeine Form und eine exit-Anweisung haben.

Zeigefingersymbol Minimieren Sie die Zahl der exit-Anweisungen in einer Schleife.

OK-Symbol Wenden Sie in einer "for"-Schleife, die über einem Array iteriert, das Attribut 'Range für das Array-Objekt und nicht einen expliziten Bereich oder einen anderen Subtyp.

Zeigefingersymbol Von der Schleife unabhängiger Code sollte nicht in der Schleife stehen. Obwohl das "Code Hoisting" (Verschieben von Ausdrücken an den spätestmöglichen Ausführungszeitpunkt) eine allgemeine Art der Compileroptimierung ist, kann es nicht angewendet werden, wenn der invariante Code andere Kompiliereinheiten aufruft.

Beispiel:

World_Search:
while not World.Is_At_End(World_Iterator) loop
    ...
Country_Search:
    while not Nation.Is_At_End(Country_Iterator) loop
            declare
        City_Map: constant City.Map := City.Map_Of
            (The_City => Nation.City_Of(Country_Iterator),
             In_Atlas => World.Country_Of(World_Iterator).Atlas);
begin
        ...    

Der Aufruf von "World.Country_Of" im obigen Code ist von der Schleife unabhängig (d. h., das Land - country - wird in der inneren Schleife nicht geändert). In den meisten Fällen kann/darf der Compiler den Aufruf nicht aus der Schleife an eine andere Stelle verschieben, weil der Aufruf Nebeneffekte haben könnte, die die Programmausführung beeinflussen. Der Code wird daher durch die Schleife jedes Mal unnötigerweise ausgeführt.

Wenn Sie die Schleife wie folgt neu schreiben, ist sie effizienter und leichter zu verstehen und zu warten:

Country_Search:
while not World.Is_At_End(World_Iterator) loop
            declare
        This_Country_Atlas: constant Nation.Atlas 
            := World.Country_Of
                    (World_Iterator).Atlas;
begin
        ...
        City_Search:
        while not Nation.Is_At_End (The_City_Iterator) loop
            declare
                City.Map_Of (
                    The_City => Nation.City_Of
                                        (Country_Iterator),
                    In_Atlas => This_Country_Atlas );
begin
                ...    

OK-Symbol Unterprogramm- und Eingangsaufrufe sollten benannte Zuordnungen verwenden.

Wenn jedoch klar ist, dass der Schwerpunkt der Operation beim ersten (oder einzigen) Parameter liegt (z. B. dem direkten Objekt eines transitiven Verbs), kann der Name für diesen (und nur diesen) Parameter weggelassen werden.

Subscriber.Delete (The_Subscriber => Old_Subscriber);    

Hier ist Subscriber.Delete das transitive Verb und Old_Subscriber das direkte Objekt. Die folgenden Ausdrücke ohne die benannte Zuordnung The_Subscriber => Old_Subscriber sind akzeptabel:

Subscriber.Delete	(Old_Subscriber);
Subscriber.Delete (Old_Subscriber, 
                   Update_Database  => True,
                   Expunge_Name_Set => False);
if Is_Administrator (Old_Subscriber) then ...    

Es gibt auch Fälle, in denen die Bedeutung von Parametern so offensichtlich ist, das benannte Zuordnungen lediglich die Lesbarkeit beeinträchtigen würden. Dies trifft beispielsweise zu, wenn alle Parameter denselben Typ und Modus und keine Standardwerte haben.

if Is_Equal (X, Y) then ...
Swap (U, B);    

OK-Symbol In case-Anweisungen oder in Satztypdefinitionen (für Varianten) sollte kein when others verwendet werden.

Der Verzicht auf when others hilft in der Wartungsphase, denn diese Konstrukte werden ungültig, sobald die diskrete Typdefinition modifiziert wird. Der Programmierer muss sich dann überlegen, wie die Modifizierung gehandhabt werden soll. Das Konstrukt when others wird jedoch toleriert, wenn der Selektor ein großer Integer-Bereich ist.

Zeigefingersymbol Verwenden Sie an Stelle mehrerer "elsif" eine case-Anweisung, wenn die Verzweigungsbedingung ein diskreter Wert ist.

OK-Symbol Unterprogramme sollten nur einen Rückkehrpunkt haben.

Versuchen Sie, Unterprogramme am Ende des Anweisungsabschnitts zu verlassen. Funktionen sollten nicht mehr als eine Rückkehranweisung haben. Frei über den Hauptteil der Funktion verteilte Rückkehranweisungen sind wie goto-Anweisungen und machen den Code schwer lesbar und schwer zu warten.

Prozeduren sollten gar keine Rückkehranweisung haben.

Zeigefingersymbol Mehrere return können nur in sehr kleinen Funktionen toleriert werden, wenn alle return gleichzeitig erkennbar sind und der Code eine sehr regelmäßige Struktur hat.

function Get_Some_Attribute return Some_Type is
begin
    if Some_Condition then
        return This_Value;
else
        return That_Other_Value;
        end if;
end Get_Some_Attribute;    

Handsymbol 'Achtung' Die Anweisung goto kann nur eingeschränkt verwendet werden.

Die Anweisung "goto" ist trotz der Syntax von goto-Kennsätzen und der eingeschränkten Verwendungsbedingungen in Ada nicht so nachteilig wie man meinen könnte. In vielen Fällen ist goto besser lesbar und aussagekräftiger als manche äquivalenten Konstrukte (ein mit einer Ausnahme erstelltes imitiertes goto beispielsweise).

Hinweise zur Codierung

Zeigefingersymbol Wenn Sie Arrays manipulieren, gehen Sie nicht davon aus, dass der Index der Arrays mit 1 beginnt. Verwenden Sie die Attribute 'Last, 'First und 'Range.

Zeigefingersymbol Definieren Sie die gebräuchlichsten eingeschränkten Subtypen Ihres uneingeschränkten Typs (es werden größtenteils Datensätze sein) und verwenden Sie diese Subtypen für Parameter und Rückgabewerte, um die Selbstüberprüfung im Clientcode zu verstärken.

type Style is (Regular, Bold, Italic, Condensed);
type Font (Variety: Style) is ...
subtype Regular_Font is Font (Variety => Regular);
subtype Bold_Font is Font (Variety => Bold);
function Plain_Version (Of_The_Font: Font) return Regular_Font;
procedure Oblique (The_Text   : in out Text;
                   Using_Font : in     Italic_Font);
...    

Kapitel 8

Transparenzprobleme

Überladung und Homographen

Folgende Richtlinien sollten angewendet werden:

OK-Symbol Überladen Sie Unterprogramme.

Stellen Sie jedoch bei Verwendung desselben Bezeichners sicher, dass er tatsächlich dieselbe Art von Operation impliziert.

OK-Symbol Vermeiden Sie ein Verdecken von Homographbezeichnern in verschachtelten Geltungsbereichen.

Dies führt zur Irritation des Lesers und zu potenziellen Risiken bei der Wartung. Achten Sie auch auf das Vorhandensein und den Geltungsbereich von Steuervariablen von "for"-Schleifen.

OK-Symbol Überladen Sie nicht Operationen für Subtypen, sondern nur Operationen für den Typ.

Diese Überladung gilt für den Basistyp und alle seine Subtypen.

Beispiel:

subtype Table_Page is Syst.Natural16 range 0..10;
function "+"(Left, Right: Table_Page) return Table_Page;    

Beim Abgleich von Unterprogrammen sucht der Compiler nach dem Basistyp und nicht nach dem Subtyp eines Parameters. Im obigen Beispiel wird "+" daher tatsächlich für alle Natural16-Werte des aktuellen Pakets und nicht nur für Table_Page neu definiert. Demzufolge würde jeder Ausdruck "Natural16 + Natural16" jetzt einem Aufruf von "+"(Table_Page, Table_Page) zugeordnet werden, der wahrscheinlich das falsche Ergebnis zurückgibt oder eine Ausnahme auslöst.

Kontextklauseln

OK-Symbol Minimieren Sie die Anzahl der von "with"-Klauseln eingeführten Abhängigkeiten.

Wo die Transparenz durch eine "with"-Klausel verbessert werden kann, sollte die Klausel einen möglichst kleinen Codebereich abdecken. Verwenden Sie eine "with"-Klausel nur, wenn es erforderlich ist. Im Idealfall werden Sie sie nur in einem Hauptteil oder auch im Stub eines großen Hauptteils verwenden.

Verwenden Sie für den erneuten Export von Entitäten der tieferen Ebenen Schnittstellenpakete, um den sichtbaren Import einer Vielzahl von Paketen der tieferen Ebenen mit "with" zu vermeiden. Nutzen Sie abgeleitete Typen, Umbenennung, Oberflächenunterprogramme und eventuell vordefinierten Typen (z. B. Strings), wie sie bei Paketen mit Umgebungsbefehlen verwendet werden.

Verwenden Sie weiche (schwache) Einheitenverbindungen (mit formalen generischen Parameter) und keine harten (starken) Verbindungen (mit "with"-Klauseln).

OK-Symbol "Use"-Klauseln sollten nicht verwendet werden.

Wenn Sie die Verwendung von "use"-Klauseln weitestgehend vermeiden, verbessern Sie die Lesbarkeit, sofern diese Regel durch Namenskonventionen unterstützt wird, die vom Kontext und einer geeigneten Umbenennung Gebrauch machen (siehe obigen Abschnitt "Namenskonventionen"). Auf diese Weise können Sie auch einige Überraschungen hinsichtlich der Transparenz vermeiden, insbesondere in der Wartungsphase.

Für ein Paket, das einen Zeichentyp definiert, ist eine "use"-Klausel in jeder Kompiliereinheit erforderlich, die auf der Basis dieses Zeichentyps Zeichenfolgenliterale definieren muss.

package Internationalization is
    type Latin_1_Char is (..., 'A', 'B', 'C', ..., U_Umlaut, ...);
    type Latin_1_String is array (Positive range <>) of 
            Latin_1_Char;
end Internationalization ;
use Internationalization;
Hello : constant Latin_1_String := "Baba"    

Das Fehlen einer "use"-Klausel verhindert die Verwendung von Operatoren in Infix-Form. Diese können in der Clienteinheit umbenannt werden.

function "=" (X, Y : Subscriber.Id) return Boolean 
            renames Subscriber."=";
function "+" (X, Y :Base_Types.Angle) return Base_Types.Angle
            renames Base_Types."+";    

OK-Symbol Da das Fehlen einer "use"-Klausel häufig dazu führt, dass dieselbe Gruppe von Umbenennungen in zahlreiche Clienteinheiten eingeschlossen wird, können alle diese Umbenennungen durch ein im definierenden Paket verschachteltes Paket 'Operations' innerhalb des definierenden Pakets aufgeschlüsselt werden. In der Clienteinheit wird dann eine "use"-Klausel für das Paket 'Operations' empfohlen.

package Pack is
    type Foo is range 1 .. 10;
    type Bar is private;
     ...
    package Operations is
        function "+" (X, Y : Pack.Foo) return Pack.Foo 
                renames Pack."+";
        function "=" (X, Y : Pack.Foo) return Boolean 
                renames Pack."=";
        function "=" (X, Y : Pack.Bar) return Boolean 
                renames Pack."=";
        ...
    end Operations;
private
	...
end Pack;
with Pack;
package body Client is
    use Pack.Operations; -- Macht NUR Operationen direkt sichtbar
    ...
    A, B : Pack.Foo;    -- Präfix Pack noch immer erforderlich
    ...
    A := A + B ;        -- Beachten Sie, dass "+" direkt
                        -- sichtbar ist. 

Das Paket 'Operations' sollte immer diesen Namen haben und immer an das Ende des sichtbaren Abschnitts des definierenden Pakets gestellt werden. Die "use"-Klausel sollte nur verwendet werden, wo es notwendig ist, d. h. im Hauptteil des Clients, sofern in der Spezifikation keine Operation verwendet wird, was oft der Fall ist.

with Defs;
package Client is
    ...
    package Inner is
        use Defs;
        ...
    end Inner;		-- Der Geltungsbereich der USE-Klausel endet hier.
    ...
end Client;                declare
    use Special_Utilities;
begin
    ...
end;                -- Der Geltungsbereich der USE-Klausel endet hier. 

Umbenennungen

Zeigefingersymbol Verwenden Sie Umbenennungsdeklarationen.

In Verbindung mit den Einschränkungen für "use"-Klauseln werden Umbenennungen empfohlen, um die Lesbarkeit des Codes zu verbessern. Wenn mehrfach auf eine Einheit mit einem sehr langen Namen verwiesen wird, kann die Lesbarkeit durch Angabe eines sehr kurzen Namens für diese Einheit verbessert werden.

with Directory_Tools;
with String_Utilities;
with Text_Io;
package Example is
    package Dt renames Directory_Tools;
    package Su renames String_Utilities;
    package Tio renames Text_Io;
    package Dtn renames Directory_Tools.Naming;
    package Dto renames Directory_Tools.Object;
        ...    

OK-Symbol Die Auswahl von Kurznamen sollte innerhalb des gesamten Projekts konsistent sein, um dem Prinzip 'Möglichst wenig Überraschungen' gerecht zu werden. Verwenden Sie daher den Kurznamen direkt im Paket.

package With_A_Very_Long_Name is package Vln renames 
            With_A_Very_Long_Name;
    ...
end
with With_A_Very_Long_Name;
package Example is package Vln renames With_A_Very_Long_Name;
-- Ab hier ist Vln eine Abkürzung. 

Beachten Sie, dass die Umbenennung von Paketen nur den sichtbaren Abschnitt des umbenannten Pakets transparent macht.

OK-Symbol Importierte Paketumbenennungen müssen am Anfang des Deklarationsabschnitts gruppiert und alphabetisch sortiert werden.

Zeigefingersymbol Umbenennungen können lokal überall dort verwendet werden, wo sie die Lesbarkeit verbessern. (Dies hat keine negativen Auswirkungen auf die Laufzeit.) Typen können ohne Einschränkungen als Subtypen umbenennt werden.

Wie bereits im Abschnitt zu Kommentaren angegeben, sind Umbenennungen oft eine elegante und einfach zu verwaltende Möglichkeit, den Code zu dokumentieren, wenn Sie beispielsweise einem komplexen Objekt einen einfachen Namen geben oder die Bedeutung eines Typs lokal präzisieren möchten. Wählen Sie den Geltungsbereich des Umbenennungsbezeichners aus, um Unklarheiten zu vermeiden.

Zeigefingersymbol Durch das Umbenennen von Ausnahmen können Ausnahmen in mehreren Einheiten aufgeschlüsselt werden, z. B. in allen Instanziierungen eines generischen Pakets. In einem Paket, das einen Typ ableitet, sollten Ausnahmen, die potenziell von den abgeleiteten Unterprogrammen ausgelöst werden, zusammen mit dem abgeleiteten Typ erneut exportiert werden, damit die Clients nicht das ursprüngliche Paket mit "with" importieren müssen.

with Inner_Defs; 
package Exporter is ... 
procedure May_Raise_Exception; -- Raises exception Inner_Defs.Bad_Schmoldu when ... ... 
Bad_Schmoldu : exception renames Inner_Defs.Bad_Schmoldu; ...

Zeigefingersymbol Das Umbenennen von Unterprogrammen mit verschiedenen Standardwerten für "in"-Parameter kann eine einfache Codeaufschlüsselung ermöglichen und die Lesbarkeit verbessern.

procedure Alert (Message : String;
                 Beeps   : Natural);
procedure Bip (Message : String := "";
                   Beeps   : Natural := 1)
        renames Alert;
procedure Bip_Bip (Message : String := "";
                   Beeps   : Natural := 2) 
        renames Alert;    procedure Message (Message : String;
                   Beeps   : Natural := 0)
        renames Alert;    procedure Warning (Message : String;
                   Beeps   : Natural := 1)
        renames Alert;    

OK-Symbol Vermeiden Sie, den (alten) Namen der umbenannten Entität im unmittelbaren Geltungsbereich der Umbenennungsdeklaration zu verwenden. Verwenden Sie nur den von der Umbenennungsdeklaration eingeführten Bezeichner oder das Operatorsymbol (neuer Name).

Anmerkung zu Use-Klauseln

Seit vielen Jahren gibt es in der Ada-Community eine Kontroverse über "use"-Klauseln, die streckenweise an einem Glaubenskrieg gleichkommt. Beide Parteien haben verschiedene Argumente ins Feld geführt, die sich oft nicht auf große Projekte übertragen lassen, und Beispiele vorgebracht, die unrealistisch oder kalkuliert unfair sind.

Verfechter der "use"-Klausel stehen auf dem Standpunkt, dass die Klausel die Lesbarkeit verbessert. Sie bringen Beispiele für besonders schwer lesbare, lange und redundante Namen, die bei mehrfacher Verwendung von einer Umbenennung profitieren würden. Sie vertreten außerdem die Meinung, dass ein Ada-Compiler die Überladung auflösen kann. Das stimmt zwar, aber eine Person, die mit einem umfangreichen Ada-Programm beschäftigt ist, kann die Überladung nicht zu zuverlässig und mit Sicherheit nicht so schnell wie ein Compiler auflösen. Weiterhin gehen sie davon aus, dass hoch entwickelte Unterstützungsumgebungen für die Ada-Programmierung wie die Rational-Umgebung die explizit vollständig qualifizierten Namen überflüssig machen. Das ist jedoch nicht wahr. Der Benutzer sollte nicht für jeden Bezeichner, den er nicht sicher kennt, die Taste [Definition] drücken müssen. Der Benutzer sollte nicht raten müssen, sondern sofort sehen können, welche Objekte und Abstraktionen verwendet werden. Die Rosen-Anhänger leugnen die potenziellen Gefahren der "use"-Klausel bei der Programmwartung. Sie unterstellen, dass die Entstehung solcher Risiken auf die Unfähigkeit der Programmierer zurückzuführen ist. Wir denken, dass die Risiken durch vollständig qualifizierte Namen vermieden werden.

Falls Sie der Meinung sind, dass die Methoden, die oben vorgeschlagen wurden, um die Folgen der Einschränkungen für "use"-Klauseln zu entschärfen, einen zu hohen Tippaufwand erfordern, sollten Sie sich die Bemerkung von Norman H. Cohen zu eigen machen: "Die Zeit, die beim Tippen eines Programms eingespart wird, geht mehrfach verloren, wenn das Programm überprüft, mit Debuggern getestet und gewartet wird.".

Und schließlich hat sich gezeigt, dass das Fehlen von "use"-Klauseln bei großen Systemen die verbessern verkürzt, da der Suchaufwand in Symboltabellen reduziert wird.

Lesern, die sich detaillierter über die Kontroverse zur use-Klausel informieren möchten, empfehlen wir folgende Literatur:

D. Bryan, "Dear Ada", Ada Letters 7, 1, Januar/Februar 1987, Seiten 25-28

J. P. Rosen, "In Defense of the Use Clause", Ada Letters 7, 7, November/Dezember 1987, Seiten 77-81

G. O. Mendal, "Three Reasons to Avoid the Use Clause", Ada Letters 8, 1, Januar/Februar 1988, Seiten 52-57

R. Racine, "Why the Use Clause Is Beneficial", Ada Letters 8, 3, Mai/Juni 1988, Seiten 123-127

N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), Seiten 361-362

M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), Seiten 368-370


Kapitel 9

Programmstruktur und Kompilierungsprobleme

Dekomposition von Paketen

Es gibt zwei grundsätzliche Wege, ein großes "logisches" Paket einer ersten Designphase in mehrere kleinere Ada-Bibliothekseinheiten zu zerlegen, die leichter zu verwalten, zu kompilieren, zu warten und zu verstehen sind.

a) Die verschachtelte Dekomposition

Bei diesem Ansatz liegt die Betonung auf der Verwendung von Ada-Untereinheiten und/oder -Unterpaketen. Die wichtigen Unterprogramme, Task-Hauptteile und Hauptteile innerer Pakete werden systematisch voneinander getrennt. Dieser Prozess wird innerhalb dieser Untereinheiten/Unterpakete rekursiv wiederholt.

b) Die unstrukturierte Dekomposition

Das logische Paket wird in ein Netz kleinerer Pakete zerlegt, die durch "with"-Klauseln miteinander verbunden werden. Das ursprüngliche logische Paket ist meistens eine Oberfläche (oder ein Designartefakt, das es es gar nicht mehr gibt).

Jeder der beiden Ansätze hat seine Vor- und Nachteile. Die verschachtelte Dekomposition erfordert weniger Code und führt zu einer einfacheren Benennung (da viele Bezeichner kein Präfix benötigen). In der Rational-Umgebung ist die Struktur im Bibliotheksimage sehr transparent und leichter umzusetzen (Befehle Ada.Make_Separate, Ada.Make_Inline). Bei der unstrukturierten Dekomposition führt oft zu weniger Neukompilierungen und zu einer besseren oder saubereren Struktur (insbesondere an Subsystemgrenzen). Diese Art der Dekomposition fördert außerdem die Wiederverwendung. Sie ist auch leichter mit automatischen Neukompilierungstools und Konfigurationsmanagement zu verwalten. Bei der flachen Struktur besteht jedoch ein größeres Risiko, vom ursprünglichen Design abzuweichen, indem einige der Pakete der tieferen Ebenen, die durch die Dekomposition erstellt wurden, mit "with" importiert werden.

OK-Symbol Die Verschachtelungstiefe sollte bei Unterprogrammen auf drei Ebenen und bei Paketen auf zwei Ebenen beschränkt werden. Verschachteln Sie keine Pakete innerhalb von Unterprogrammen.

package Level_1 is
    package Level_2 is
package body Level_1 is
    procedure Level_2 is
        procedure Level_3 is    

Zeigefingersymbol Verwenden Sie für verschachtelte Einheiten Hauptteil-Stubs ("separate Hauptteile"), wenn eine der folgenden Situationen zutrifft:

Struktur von Deklarationsabschnitten

Paketspezifikation

OK-Symbol Der Deklarationsabschnitt einer Paketspezifikation sollte Deklarationen in dieser Reihenfolge enthalten:

1) Umbenennungsdeklaration für das eigentliche Paket

2) Umbenennungsdeklarationen für importierte Entitäten

3) "Use"-Klauseln

4) benannte Zahlen

5) Deklarationen von Typen und Subtypen

6) Konstanten

7) Deklarationen von Ausnahmen

8) Spezifikationen exportierter Unterprogramme

9) ggf. verschachtelte Pakete

10) privater Abschnitt

Zeigefingersymbol In einem Paket, das mehrere Haupttypen einführt, sind verschiedene Gruppen zusammengehöriger Deklarationen vorteilhafter:

5) Typ- und Subtypdeklarationen für A

6) Konstanten

7) Deklarationen von Ausnahmen

8) Spezifikationen exportierter Unterprogramme für A

5) Typ- und Subtypdeklarationen für B

6) Konstanten

7) Deklarationen von Ausnahmen

8) Spezifikationen exportierter Unterprogramme für B

usw.

Zeigefingersymbol Wenn der Deklarationsabschnitt groß ist (> 100 Zeilen), begrenzen Sie die verschiedenen Teilabschnitte durch Kommentarblöcke.

Pakethauptteil

OK-Symbol Der Deklarationsabschnitt eines Pakethauptteils sollte Deklarationen in dieser Reihenfolge enthalten:

1) Umbenennungsdeklarationen (für importierte Entitäten)

2) "Use"-Klauseln

3) benannte Zahlen

4) Deklarationen von Typen und Subtypen

5) Konstanten

6) Deklarationen von Ausnahmen

7) Spezifikationen lokaler Unterprogramme

8) Hauptteile lokaler Unterprogramme

9) Hauptteile exportierter Unterprogramme

10) ggf. Hauptteile verschachtelter Pakethauptteile

Weitere Konstrukte

OK-Symbol Andere Deklarationsabschnitte, z. B. in Hauptteilen von Unterprogrammen, Task-Hauptteilen und Blockanweisungen, folgen demselben allgemeinen Muster.

Kontextklauseln

OK-Symbol Verwenden Sie pro importierter Bibliothekseinheit eine "with"-Klausel. Sortieren Sie die with-Klauseln in alphabetischer Reihenfolge. Falls in einer Einheit mit "with"-Klausel eine "use"-Klausel zu verwenden ist, sollte sie unmittelbar auf die entsprechende "with"-Klausel folgen. Informationen zum Pragma 'Elaborate' finden Sie im folgenden Abschnitt.

Reihenfolge der Ausarbeitung

OK-Symbol Versuchen Sie nicht, mit der Reihenfolge der Ausarbeitung von Bibliothekseinheiten einen bestimmten Effekt zu erzielen.

Jede Ada-Implementierung ist frei, ihre Strategie für die Berechnung der Ausarbeitungsreihenfolge zu wählen, sofern sie die einfachsten Regeln aus dem Ada-Referenzhandbuch [ISO87] erfüllt. Einige Implementierungen nutzen intelligentere Strategien als andere (z. B. die Ausarbeitung der Hauptteile, sobald dies nach der entsprechenden Spezifikation machbar ist). Es gibt auch Implementierungen, bei denen kein Wert auf eine intelligente Strategie gelegt wird (insbesondere für generische Instanziierungen), was zu sehr schweren Portierbarkeitsproblemen führt.

Für den berüchtigten Fehler "Zugriff vor Ausarbeitung" während der Programmausarbeitung (der normalerweise die Ausnahme Program_Error auslösen sollte) gibt es drei Hauptursachen:

task type T;
type T_Ptr is access T;
SomeT : T_Ptr := new T; -- Zugriff vor der Ausarbeitung 

OK-Symbol Um Probleme beim Portieren von Anwendungen von einem Ada-Compiler auf einen anderen zu vermeiden, sollte der Programmierer den Code neu strukturieren (was nicht immer möglich ist) oder die Ausarbeitungsreihenfolge mit folgender Strategie durch das Pragma 'Elaborate' explizit steuern.

In der Kontextklausel einer Einheit Q sollte ein Pragma 'Elaborate' auf jede Einheit P, die in einer "with"-Klausel erscheint, angewendet werden:

Falls nun P einen Typ T exportiert, so dass die Ausarbeitung von Objekten des Typs T eine Funktion im Paket R aufruft, sollte die Kontextklausel von Q Folgendes enthalten:

with R;
pragma Elaborate (R);    

Dies gilt auch dann, wenn Q keine direkten Verweise auf R enthält!

In der Praxis kann es einfacher (aber nicht immer möglich) sein, die Regel aufzustellen, dass das Paket P Folgendes enthalten sollte:

with R;
pragma Elaborate (R);    

Das Paket Q muss einfach Folgendes enthalten:

with P;
pragma Elaborate (P);    

Hier wird die richtige Ausarbeitungsreihenfolge durch die Transitivität erreicht.


Kapitel 10

Parallelität

Handsymbol 'Achtung' Verwenden Sie Tasks nur eingeschränkt.

Tasks sind ein sehr mächtiges Feature, das jedoch heikel in der Verwendung ist. Eine nicht gerechtfertigte Verwendung von Tasks kann einen großen Zeitaufwand und einen hohen Platzbedarf mit sich bringen. Kleine Änderungen an einem Abschnitt des Systems können die Lebendigkeit einer Gruppe von Tasks komplett gefährden und so zu Blockierungen und/oder gegenseitigen Sperren führen. Es ist schwierig, Programme mit Tasks (mit Debuggern) zu testen. Über die Verwendung von Tasks, ihre Platzierung und Interaktion sollte daher auf Projektebene entschieden werden. Tasks können nicht verdeckt verwendet oder von unerfahrenen Programmierern geschrieben werden. Das Task-Verarbeitungsmodell eines Ada-Programms muss transparent und verständlich sein.

Solange es keine effektive Unterstützung durch parallele Hardware gibt, sollten Tasks nur eingeführt werden, wenn Parallelität tatsächlich erforderlich ist. Dies ist der Fall, wenn zeitabhängige Aktionen ausgedrückt werden (periodische Aktivitäten oder die Einführung von Zeitlimits) oder Aktionen, die von einem externen Ereignis abhängen, z. B. einer Unterbrechung oder dem Eintreffen einer externen Nachricht. Tasks müssen ebenfalls für die Entkopplung anderer Aktivitäten eingeführt werden, z. B. das Puffern, die Warteschlangensteuerung, die Zuteilung und die Synchronisation des Zugriffs auf allgemeine Ressourcen.

OK-Symbol Geben Sie die Größe des Task-Stacks mit einer Klausel 'Storage_Size length an.

Aus denselben Gründen und unter denselben Umständen, die length-Klauseln für Objektgruppen erforderlich machen (siehe obigen Abschnitt "Zugriffstypen"), muss die Größe einer Task immer dann angegeben werden, wenn Speicher eine knappe Ressource ist. Deklarieren Sie zu diesem Zweck immer Tasks eines explizit deklarierten Typs (da die length-Klausel nur auf einen Typ angewendet werden kann). Zur dynamischen Größenanpassung des Stacks können Sie einen Funktionsaufruf verwenden.

Anmerkung: Es kann schwer einzuschätzen sein, wie groß der Stack für die einzelnen Tasks sein muss. Zur Vereinfachung dieser Einschätzung kann das Laufzeitsystem mit einem Hochwassermarkenmechanismus zu instrumentieren.

OK-Symbol Verwenden Sie im Hauptteil einer Task eine Ausnahmebehandlungsroutine, um ein nicht erklärtes Sterben einer Task zu vermeiden oder wenigstens zu protokollieren.

Tasks, die keine Ausnahmen behandeln, sterben in der Regel unbemerkt. Versuchen Sie nach Möglichkeit, die Art des Sterbens, insbesondere Storage_Error, zu dokumentieren. Dies ermöglicht eine Optimierung der Stack-Größe. Beachten Sie, dass dies eine Kapselung der Zuordnung (Basiselement new) in einem Unterprogramm erfordert, das wiederum eine andere Ausnahme als Storage_Error exportiert.

OK-Symbol Erstellen Sie Tasks während der Ausarbeitung des Programms.

Aus denselben Gründen und unter denselben Umständen, die eine Zuordnung von Objektgruppen während der Programmausarbeitung erforderlich machen (siehe obigen Abschnitt "Zugriffstypen"), sollte die gesamte Task-Verarbeitungsstruktur der Anwendung schon sehr früh beim Programmstart erstellt werden. Es ist besser, wenn ein Programm durch Speichermangel gar nicht starten kann, als wenn es ein paar Tage später stirbt.

In den folgenden Regeln wird zwischen Service-Tasks und Anwendungs-Tasks unterschieden. Service-Tasks sind kleine und algorithmisch einfache Tasks, die eine Art "Nahtstelle" zwischen anwendungsbezogenen Tasks bilden. Beispiele für Service-Tasks (oder Zwischen-Tasks) sind Puffer, Transporter, Relais, Agenten, Monitore usw., die in der Regel Synchronisations-, Entkopplungs-, Zwischenspeicher- und Anklopfdienste bereitstellen. Anwendungs-Tasks haben, wie der Name schon sagt, einen direkteren Bezug zu den den Primärfunktionen der Anwendung.

OK-Symbol Vermeiden Sie Hybrid-Tasks. Anwendungs-Tasks sollten reine Aufruf-Tasks sein und Service-Tasks reine aufgerufene Tasks.

Eine reine aufgerufene Tasks enthält nur accept-Anweisungen oder Anweisungen für selektives Warten und keine Eingangsaufrufe.

OK-Symbol Vermeiden Sie Kreisstrukturen im Diagramm der Eingangsaufrufe.

Sie verringern damit die Gefahr gegenseitiger Sperren ganz erheblich. Wenn sich Kreisstrukturen nicht gänzlich vermeiden lassen, sollten Sie sie wenigstens im stationären Zustand des Systems vermeiden. Mit diesen beiden Regeln wird die Struktur auch leichter verständlich.

Handsymbol 'Achtung' Schränken Sie die Verwendung gemeinsamer Variablen ein.

Achten Sie ganz besonders auf verdeckte gemeinsame Variablen, d. h. auf Variablen, die beispielsweise in Pakethauptteilen verborgen sind und auf die Basiselemente zugreifen, die für verschiedene Tasks sichtbar sind. Gemeinsame Variablen können in Extremfällen für die Synchronisation des Zugriffs auf allgemeine Datenstrukturen genutzt werden, wenn die Kosten für Rendezvous zu hoch sind. Überprüfen Sie, ob das Pragma 'Shared' effektiv unterstützt wird.

Handsymbol 'Achtung' Verwenden Sie abort-Anweisungen nur eingeschränkt.

Die Anweisung abort wird allgemein als eines der gefährlichsten und schädlichsten Basiselemente der Sprache angesehen. Wird sie für eine nicht bedingte (und nahezu asynchrone) Beendigung von Tasks verwendet, sind kaum sinnvolle Rückschlüsse auf das Verhalten einer gegebenen Task-Verarbeitungsstruktur möglich. Es gibt jedoch seltene und ganz spezifische Umstände, unter denen eine Anweisung abort erforderlich ist.

Beispiel: Es werden untergeordnete Services bereitgestellt, die keine Zeitlimiteinrichtung haben. Die einzige Möglichkeit, ein Zeitlimit einzuführen, ist die Bereitstellung des Service durch eine Hilfsagenten-Task, das Warten (mit einem Zeitlimit) auf eine Antwort vom Agenten und das anschließende Abtöten des Agenten mit abort, sofern der Service nicht innerhalb der Wartezeit bereitgestellt wurde.

Ein Abbruch (abort) ist tolerierbar, wenn nachgewiesen werden kann, dass nur der Abbrechende und der Abgebrochene betroffen sind (wenn z. B. keine andere Task die abgebrochene Task aufrufen kann).

Handsymbol 'Achtung' Verwenden Sie delay-Anweisungen nur eingeschränkt.

Die beliebige Aussetzung einer Task kann zu schwerwiegenden Zeitplanungsproblemen führen, die nur schwer festzustellen und zu korrigieren sind.

Handsymbol 'Achtung' Verwenden Sie die Attribute 'Count, 'Terminated und 'Callable nur eingeschränkt.

Das Attribut 'Count sollte nur als grobe Angabe verwendet werden. Zeitplanentscheidungen sollten nicht davon abhängig gemacht werden, ob der Wert des Attributs gleich oder ungleich null ist, da die tatsächliche Anzahl wartender Tasks sich zwischen dem Zeitpunkt der Attributauswertung und dem Verwendungszeitpunkt des Wertes ändern kann.

Mit bedingten Eingangsaufrufen (oder dem äquivalenten Konstrukt mit accept) können Sie zuverlässig überprüfen, ob keine wartenden Tasks vorhanden sind.

select
    The_Task.Some_Entry;
else
    -- etwas anderes tun
end select;    

Die obige Angabe ist der folgenden vorzuziehen:

if The_Task.Some_Entry'Count > 0 then
    The_Task.Some_Entry;
else
    -- etwas anderes tun
        end if;

Das Attribut 'Terminated ist nur von Bedeutung, wenn seine Auswertung true ergibt, und das Attribut 'Callable nur, wenn seine Auswertung false ergibt, was ihre Zweckmäßigkeit stark einschränkt. Sie sollten diese Attribute nicht verwenden, um die Tasks beim Systemabschluss zu synchronisieren.

Handsymbol 'Achtung' Verwenden Sie Prioritäten nur eingeschränkt.

Prioritäten haben in Ada begrenzt Einfluss auf die Zeitplanung. Insbesondere wird die Priorität von Tasks, die auf Eingaben warten, bei der Sortierung der Eingabewarteschlangen oder der Auswahl des Eintrags für ein selektives Warten nicht berücksichtigt. Dies kann zu einer Umkehrung der Priorität führen (siehe [GOO88]). Prioritäten werden von der Planungsfunktion nur verwendet, um aus den ausführungsbereiten Tasks die nächste Task auszuwählen. Wegen der Gefahr der Prioritätsumkehrung sollten Sie sich für einen gegenseitigen Ausschluss nicht auf Prioritäten verlassen.

Mit Familien von Einträgen ist es möglich, die Eingabewarteschlange in mehrere untergeordnete Warteschlangen zu unterteilen. Nach einer solchen Unterteilung ist es oft möglich, ein explizites Dringlichkeitskonzept einzuführen.

Falls keine Prioritäten notwendig sind, sollten Sie keiner Task eine Priorität zuordnen.

OK-Symbol Wenn Sie einer Task eine Priorität zugeordnet haben, ordnen Sie allen Tasks der Anwendung eine Priorität zu.

Diese Regel ist notwendig, weil die Priorität von Tasks ohne ein Pragma 'Priority' nicht definiert ist.

OK-Symbol Halten Sie die Anzahl der Prioritätsstufen aus Gründen der Portierbarkeit klein.

Der Bereich des Subtyps System.Priority ist implementierungsdefiniert. Die Erfahrung zeigt, dass der tatsächlich verfügbare Bereich von System zu System stark schwankt. Darüber hinaus ist es geschickt, die Prioritäten zentral zu definieren, ihnen Namen und Definitionen zu geben und nicht in allen Tasks ganzzahlige Literale zu verwenden. Ein solches zentrales Paket System_Priorities verbessert die Portierbarkeit und ermöglicht zusammen mit der vorherigen Regel ein einfaches Auffinden aller Task-Spezifikationen.

Zeigefingersymbol Um Verschiebungen in zyklischen Tasks zu vermeiden, sollten Sie beim Programmieren der Anweisung delay die Verarbeitungszeit, den Systemaufwand und die Bevorrechtigung von Tasks berücksichtigen.

Next_Time := Calendar.Clock;
    loop
    -- Job ausführen
    Next_Time := Next_Time + Period;
    delay Next_Time - Clock;
end loop;    

Beachten Sie, dass Next_Time - Clock negativ sein kann, was darauf hinweist, dass die zyklische Task verspätet ausgeführt wird. Es könnte ein Zyklus verloren gehen.

Zeigefingersymbol Um die Planbarkeit garantieren zu können, sollten Sie zyklischen Tasks Prioritäten nach monotonen Raten zuordnen, d. h., der am häufigsten ausgeführten Task die höchste Priorität. (Diesbezügliche Details finden Sie in der Veröffentlichung [SHA90].)

Zeigefingersymbol Ordnen Sie sehr schnellen Zwischen-Tasks wie Überwachungsprogrammen und Puffern eine höhere Priorität zu.

Stellen Sie dann sicher, dass diese Tasks sich nicht selbst durch Rendezvous mit anderen Tasks blockieren. Dokumentieren Sie diese Priorität im Code, damit sie während der Programmwartung respektiert werden kann.

Zeigefingersymbol Um den Effekt von Abweichungen zu minimieren, sollten Sie Eingabemuster oder Ausgabedaten und nicht den Zeitraum selbst mit einer Zeitmarke versehen.

OK-Symbol Vermeiden Sie aktives Warten (Polling).

Stellen Sie sicher, dass Tasks durch Auswahl- oder Eingangsaufrufe warten oder verzögert werden, anstatt ständig zu überprüfen, ob es etwas zu tun gibt.

Zeigefingersymbol Stellen Sie für jedes Rendezvous sicher, dass mindestens eine Seite wartet und nur eine Seite einen bedingten / zeitlich festgelegten Eingangsaufruf hat oder wartet.

Andernfalls besteht vor allem in Schleifen die Gefahr, dass der Code in eine Konkurrenzsituation gerät, die im Ergebnis starke Ähnlichkeit mit dem aktiven Warten hat. Durch eine schlechte Verwendung von Prioritäten kann diese Situation noch verschlimmert werden.

Zeigefingersymbol Achten Sie beim Kapseln von Tasks darauf, dass einige ihrer spezifischen Merkmale in hohem Maße transparent bleiben.

Falls Eingangsaufrufe in Unterprogrammen verdeckt sind, stellen Sie sicher, dass der Leser der Spezifikation dieser Unterprogramme weiß, dass der Aufruf eines solchen Unterprogramms blockiert werden kann. Geben Sie zusätzlich an, ob die Wartezeit begrenzt ist. Wenn ja, geben Sie eine Schätzung für die Obergrenze. Geben Sie mit einer Namenskonvention die potenzielle Wartezeit an (siehe obigen Abschnitt "Unterprogramme").

Falls die Ausarbeitung eines Pakets, der Aufruf eines Unterprogramms oder die Instanziierung einer generischen Einheit eine Task aktiviert, machen Sie diese Tatsache für den Client transparent.

package Mailbox_Io is
    -- Dieses Paket arbeitet eine interne Kontroll-Task aus,
    -- die den gesamten Zugriff auf die externe Mailbox
    -- synchronisiert.
    procedure Read_Or_Wait
        (Name: Mailbox.Name; Mbox: in out Mailbox.Object);
        --
        -- Blockierung (unbegrenztes Warten) 

OK-Symbol Verlassen Sie sich beim selektiven Warten nicht darauf, dass die Einträge in einer bestimmten Reihenfolge ausgewählt werden.

Eine gewisse Fairness bei der Auswahl der in Warteschlangeneinträgen enthaltenen Tasks können Sie erreichen, indem Sie die Warteschlangen explizit ohne Wartezeit in der gewünschten Reihenfolge überprüfen und dann auf alle Einträge warten. Verwenden Sie nicht 'Count.

OK-Symbol Verlassen Sie sich bei Tasks, die in demselben Deklarationsabschnitt ausgearbeitet werden, nicht auf eine bestimmte Aktivierungsreihenfolge.

Falls eine spezifische Startreihenfolge angestrebt wird, sollten Sie diese über Rendezvous mit speziellen Starteinträgen erreichen.

OK-Symbol Implementieren Sie Tasks so, dass sie normal beendet werden.

Sofern die Natur der Anwendung nicht erfordert, das eine einmal aktivierte Task endlos ausgeführt wird, sollten Tasks enden, wenn sie abgeschlossen sind oder durch eine Beendigungsalternative. Für Tasks, deren Master ein Paket auf Bibliotheksebene ist, ist das nicht immer möglich, da das Ada-Referenzhandbuch nicht angibt, unter welchen Bedingungen solche Tasks enden sollten.

Falls die Master-abhängige Struktur keine saubere Beendigung zulässt, sollten Tasks spezielle Abschlusseinträge enthalten, die beim Systemabschluss aufgerufen werden, und auf diese warten.


Kapitel 11

Fehlerbehandlung und Ausnahmen

Nach der allgemeinen Philosophie werden Ausnahmen nur für Fehler (logische und Programmierfehler, Konfigurationsfehler, beschädigte Daten, Nichtverfügbarkeit von Ressourcen usw.) verwendet. Als allgemeine Regel gilt, dass Systeme unter normalen Bedingungen keine Ausnahmen auslösen sollten, wenn keine Überlastung und kein Hardwarefehler vorliegt.

Zeigefingersymbol Verwenden Sie Ausnahmen zur Behandlung von logischen und Programmierfehlern, Konfigurationsfehlern, beschädigten Daten und der Nichtverfügbarkeit von Ressourcen. Dokumentieren Sie Ausnahmen und den Auslösepunkt so früh wie möglich mit dem entsprechenden Protokollierungsmechanismus.

Zeigefingersymbol Minimieren Sie die Zahl der Ausnahmen, die aus einer gegebenen Abstraktion exportiert werden.

Wenn man es bei großen Systemen auf jeder Ebene mit einer Vielzahl von Ausnahmen zu tun hat, ist der Code schwer zu lesen und zu warten. Der Aufwand für die Ausnahmebehandlung kann größer als der für die normale Verarbeitung sein.

Es gibt mehrere Möglichkeiten, die Anzahl der Ausnahmen zu minimieren:

OK-Symbol Verbreiten Sie keine Ausnahmen, die nicht im Design spezifiziert sind.

OK-Symbol Vermeiden Sie in Ausnahmebehandlungsroutinen eine Alternative when others, bis die abgefangene Ausnahme erneut ausgelöst wird.

Sie können also eine lokale "Bereinigung" durchführen und müssen sich nicht mit Ausnahmen belasten, die auf dieser Ebene nicht behandelt werden können:

exception
    when others => 
        if Io.Is_Open (Local_File) then
            Io.Close (Local_File);
        end if;
        raise;
end;    

Das Ende eines Task-Hauptteils ist eine andere Stelle, an der eine Alternative when others verwendet werden kann.

OK-Symbol Verwenden Sie für häufige, vorhersehbare Ereignisse keine Ausnahmen.

Die Verwendung von Ausnahmen für Bedingungen, die keine Fehler im eigentlichen Sinne sind, bringt einige Beeinträchtigungen mit sich:

Verwenden Sie beispielsweise keine Ausnahme las eine Art Sonderwert, der von einer Funktion zurückgegeben wird (wie Value_Not_Found bei einer Suche). Verwenden Sie eine Prozedur mit einem Parameter "out", führen Sie einen Sonderwert mit der Bedeutung Not_Found ein oder packen Sie den zurückgegebenen Typ in einen Datensatz mit einer Diskriminante Not_Found.

OK-Symbol Verwenden Sie für die Implementierung von Steuerstrukturen keine Ausnahmen.

Dies ist ein Sonderfall der vorherigen Regel. Ausnahmen sollten nicht als eine Art "goto"-Anweisung verwendet werden.

Zeigefingersymbol Wenn Sie vordefinierte Ausnahmen abfangen, stellen Sie die Behandlungsroutine in einen sehr kleinen Rahmen um das Konstrukt, das die Ausnahme auslöst.

Vordefinierte Ausnahmen wie Constraint_Error, Storage_Error etc. können an vielen Stellen vorkommen. Falls eine solche Ausnahme aus einem bestimmten Grund abgefangen werden muss, sollte der Geltungsbereich der Behandlungsroutine so eng wie möglich begrenzt werden.

begin
    Ptr := new Subscriber.Object;
exception
    when Storage_Error => 
        raise Subscriber.Collection_Overflow;
end;    

OK-Symbol Beenden Sie Ausnahmebehandlungsroutinen in Funktionen mit einer Anweisung "return" oder "raise". Andernfalls wird im Aufrufer die Ausnahme Program_Error ausgelöst.

Handsymbol 'Achtung' Überprüfungen sollten Sie nur eingeschränkt unterdrücken.

Durch die modernen Ada-Compiler können die Einsparungen beim Codeumfang und die Durchsatzsteigerungen, die mit dem Unterdrücken von Überprüfungen zu erzielen sind, vernachlässigt werden. Die Unterdrückung von Überprüfungen sollte deshalb auf genau eingegrenzte Codeabschnitte beschränkt bleiben, die sich (durch Messungen) als Leistungsengpässe herausgestellt haben. Wenden Sie eine solche Unterdrückung nie auf ein gesamtes System an.

Ebenso gilt, dass keine gesonderte explizite Bereichs- und Diskriminantenüberprüfung nur für den unwahrscheinlichen Fall durchgeführt werden sollte, dass später beschlossen werden könnte, Überprüfungen generell zu unterdrücken. Vertrauen Sie auf die integrierten Prüffunktionen von Ada.

Zeigefingersymbol Geben Sie Ausnahmen nicht über den Geltungsbereich ihrer Deklaration hinaus weiter.

Der Clientcode kann Ausnahmen dann nicht mehr explizit behandeln, bis auf eine Alternative when others, die unter Umständen nicht spezifisch genug ist.

Aus dieser Regel folgt: Wenn Sie einen Typ durch Ableitung reexportieren, denken Sie daran, die Ausnahmen, die die abgeleiteten Unterprogramme auslösen können, ebenfalls zu reexportieren, z. B. durch Umbenennung. Andernfalls müssen die Clients das ursprüngliche definierende Paket mittels "with" importieren.

Zeigefingersymbol Behandeln Sie Numeric_Error und Constraint_Error immer zusammen.

Das Ada-Board hat festgestellt, dass alle Umstände, unter denen Numeric_Error ausgelöst wird, stattdessen Constraint_Error auslösen sollten.

Zeigefingersymbol Stellen Sie sicher, dass Statuscodes einen geeigneten Wert haben.

Wenn Unterprogramme den Statuscode als einen Parameter "out" zurückgeben, stellen Sie sicher, dass dem Parameter "out" ein Wert zugeordnet ist, indem Sie diese Anweisung zur ersten ausführbaren Anweisung im Hauptteil des Unterprogramms machen. Machen Sie alle Status standardmäßig zu einem Erfolgs- oder Fehlerstatus. Denken Sie an alle Möglichkeiten, das Unterprogramm zu verlassen, einschließlich der Ausnahmebehandlungsroutinen.

Zeigefingersymbol Führen Sie Sicherheitsprüfungen lokal durch und überlassen Sie sie nicht dem Client.

Falls ein Unterprogramm fehlerhafte Ausgaben erzeugen kann, wenn keine korrekte Eingabe erfolgt, installieren Sie im Unterprogramm Code, um kontrolliert ungültige Eingaben erkennen und dokumentieren zu können. Verlassen Sie sich nicht auf einen Kommentar, in dem der Client angewiesen wird, korrekte Werte zu übergeben. Theoretisch ist garantiert, dass der Kommentar früher oder später ignoriert wird, was zu kaum feststellbaren Fehlern führen kann, sofern die ungültigen Parameter nicht gefunden werden.

Weitere Informationen hierzu finden Sie in der Veröffentlichung [KR90b].


Kapitel 12

Low-Level-Programmierung

Dieser Abschnitt beschäftigt sich mit Ada-Features, die an sich nicht portierbar sind. Sie sind in Kapitel 13 des Reference Manual for the Ada Programming Language [ISO87] definiert. Die Compiler-spezifischen Features sind im "Anhang F" der Ada-Compiler-Anbieter beschrieben.

Darstellungsklauseln und -attribute

Zeigefingersymbol Lesen Sie sorgfältig Anhang F des Ada-Referenzhandbuchs. (Führen Sie kleine Tests durch, um sicherzugehen, dass der Inhalt verstanden wurde.)

Handsymbol 'Achtung' Verwenden Sie Darstellungsklauseln nur eingeschränkt.

Darstellungsklauseln werden nicht von allen Implementierungen einheitlich unterstützt. Da ihre Verwendung viele Fallstricke mit sich bringen kann, sollten sie nicht frei in einem System verwendet werden.

Darstellungsklauseln sollten für Folgendes verwendet werden:

Darstellungsklauseln können in folgenden Situationen vermieden werden:

Beispiel:

Dieser Code sollte ersetzt werden:

type Foo is (Bla, Bli, Blu, Blo);
for Foo use (Bla => 1, Bli =>3, Blu => 4, Blo => 5);    

type Foo is (Invalid_0, Bla, Invalid_2, Bli, Blu, Blo);    

OK-Symbol Gruppieren Sie Typen mit Darstellungsklauseln in Paketen und bezeichnen Sie diese klar als Pakete mit implementierungsabhängigem Code.

OK-Symbol Gehen Sie beim Satzaufbau nie von einer bestimmten Reihenfolge aus.

OK-Symbol Geben Sie in einer Satzdarstellungsklausel immer die Position aller Diskriminanten an, bevor Sie Komponenten in den Varianten angeben.

OK-Symbol Vermeiden Sie Ausrichtungsklauseln.

Vertrauen Sie auf den Compiler. Er kennt die Ausrichtungsvorgaben des Ziels. Durch den Programmierer eingeführte Ausrichtungsklauseln können später zu Ausrichtungskonflikten führen.

Zeigefingersymbol Achten Sie besonders darauf, ob in uneingeschränkten zusammengesetzten Typen Compiler-generierte Felder vorhanden sind, z. B. in Datensätzen die relative Adresse dynamischer Felder und in Arrays Dope-Vektoren.

Details hierzu finden Sie in Anhang F zum Compiler. Verlassen Sie sich nicht auf die diesbezüglichen Angaben in Kapitel 13 des Ada-Referenzhandbuchs [ISO87].

Unchecked_Conversion

Handsymbol 'Achtung' Verwenden Sie Unchecked_Conversion nur eingeschränkt.

Die einzelnen Ada-Compiler unterscheiden sich stark hinsichtlich der Unterstützung für Unchecked_Conversion. Das genaue Verhalten von Unchecked_Conversion kann, insbesondere bei Anwendung auf zusammengesetzte Typen und Zugriffstypen, im Einzelfall geringfügig abweichen.

OK-Symbol Stellen Sie in einer Instanziierung von Unchecked_Conversion sicher, dass sowohl die Quellentypen als auch die Zieltypen eingeschränkt sind und dieselbe Größe haben.

Nur so ist eine begrenzte Portierbarkeit möglich. Nur so lassen sich auch Probleme mit Informationen vermeiden, die von der Implementierung hinzugefügt werden (wie Dope-Vektoren). Eine Möglichkeit, eine identische Größe beider Typen sicherzustellen, ist das Einschließen der Typen in einen Satztyp mit einer Satzdarstellungsklausel.

Ein Weg, die Eingeschränktheit des Typs zu gewährleisten, ist eine Instanziierung mit einer Oberflächenfunktion, bei der die Einschränkung im Vorfeld berechnet wird.

Zeigefingersymbol Wenden Sie Unchecked_Conversion nicht auf Zugriffswerte oder Tasks an.

Dies wird nicht von allen Systemen unterstützt (z. B. nicht vom nativen Rational-Compiler). Außerdem sollte Folgendes nicht vorausgesetzt werden:


Kapitel 13

Zusammenfassung

Hier sollten noch einmal die wichtigsten Dinge zusammengefasst werden, auf die Sie achten sollten:

Eingeschränkte Features (Handsymbol 'Achtung'):

Unbedingt zu vermeiden ist Folgendes (Zeigefingersymbol):


Referenzliteratur

Dieses Dokument ist eine direkte Ableitung aus Ada Guidelines: Recommendations for Designer and Programmers, Application Note 15, überarbeitete Auflage 1.1, Rational, Santa Clara, CA, 1990 [KR90a]. Für die Erstellung wurden jedoch zahlreiche weitere Quellen herangezogen.

BAR88 B. Bardin & Ch. Thompson, "Composable Ada Software Components and the Re-export Paradigm", Ada Letters VIII, 1, Januar/Februar 1988, Seiten 58-79

BOO87 E. G. Booch, Software Components with Ada, Benjamin/Cummings (1987)

BOO91 Grady Booch: Object-Oriented Design with Applications, Benjamin-Cummings Pub. Co., Redwood City, California, 1991, Seite 580

BRY87 D. Bryan, "Dear Ada", Ada Letters 7, 1, Januar/Februar 1987, Seiten 25-28

COH86 N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), Seiten 361-362

EHR89 D. H. Ehrenfried, Tips for the Use of the Ada Language, Application Note 1, Rational, Santa Clara, CA, 1987

GAU89 M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), Seiten 368-370

GOO88John B. Goodenough and Lui Sha: "The Priority Ceiling Protocol", Sonderausgabe der Ada Letters vom Herbst 1988, Seiten 20-31

HIR92 M. Hirasuna, "Using Inheritance and Polymorphism with Ada in Government Sponsored Contracts", Ada Letters XII, 2, März/April 1992, Seiten 43-56

ISO87 Reference Manual for the Ada Programming Language, International Standard ISO 8652:1987

KR90a Ph. Kruchten, Ada Guidelines: Recommendations for Designer and Programmers, Application Note 15, überarbeitete Ausgabe 1.1, Rational, Santa Clara, CA, 1990

KR90b Ph. Kruchten, "Error-Handling in Large, Object-Based Ada Systems", Ada Letters Band X, Nr. 7, (Sept. 1990), Seiten 91-103

MCO93 Steve McConnell, Code Complete-A Practical Handbook of Software Construction, Microsoft® Press, Redmond, WA, 1993, Seite 857

MEN88 G. O. Mendal, "Three Reasons to Avoid the Use Clause", Ada Letters 8, 1, Januar/Februar 1988, Seiten 52-57

PER88 E. Perez, "Simulating Inheritance with Ada", Ada letters VIII, 5, September/Oktober 1988, Seiten 37-46

PLO92 E. Ploedereder, "How to program in Ada 9X, Using Ada 83", Ada Letters XII, 6, November 1992, Seiten 50-58

RAC88 R. Racine, "Why the Use Clause Is Beneficial", Ada Letters 8, 3, Mai/Juni 1988, Seiten 123-127

RAD85 T. P. Bowen, G. B. Wigle & J. T. Tsai, Specification of Software Quality Attributes, Boeing Aerospace Company, Rome Air Development Center, Technical Report RADC-TR-85-37 (3 Bände)

ROS87 J. P. Rosen, "In Defense of the Use Clause", Ada Letters 7, 7, November/Dezember 1987, Seiten 77-81

SEI72 E. Seidewitz, "Object-Oriented Programming with Mixins in Ada", Ada Letters XII, 2, März/April 1992, Seiten 57-61

SHA90 Lui Sha and John B. Goodenough, "Real-Time Scheduling Theory and Ada", Computer Band 23, Nr. 4 (April 1990), Seiten 53-62

SPC89 Software Productivity Consortium: Ada Quality and Style-Guidelines for the Professional Programmer, Van Nostrand Reinhold (1989)

TAY92 W. Taylor, Ada 9X Compatibility Guide, Version 0.4, Transition Technology Ltd., Cwmbran, Gwent, Vereinigtes Königreich GB, November 1992

WIC89 B. Wichman, Insecurities in the Ada Programming Language, Report DITC137/89, National Physical Laboratory (Vereinigtes Königreich GB), Januar 1989


Glossar

Die meisten Begriffe, die in diesem Dokument verwendet werden, sind in Anhang D des Reference Manual for the Ada Programming Language, [ISO87], definiert. Definitionen zusätzliche Fachbegriffe finden Sie hier:

ADL: Ada Design Language. Dieser Begriff bezieht sich auf die Verwendung von Ada zum Ausdrücken von Designaspekten. In diesem Kontext wird Ada auch als PDL (Program Design Language) bezeichnet.

Umgebung: Die verwendete Ada-Softwareentwicklungsumgebung.

Bibliotheksschalter: In der Rational-Umgebung eine Kompilierungsoption, die für eine ganze Programmbibliothek gilt.

Modellwelt: In der Rational-Umgebung eine spezielle Bibliothek für die Erfassung einheitlicher Einstellungen von Bibliotheksschaltern für ein ganzes Projekt.

Veränderlich: Eigenschaft eines Datensatzes, dessen Diskriminanten Standardwerte haben. Einem Objekt eines veränderlichen Typs können alle Werte des Typs zugeordnet werden, auch Werte, die zu einer Veränderung der Diskriminanten und damit der Struktur des Objekts führen.

Oberfläche: ein Unterprogramm, dessen Hauptteil nur als Relais fungiert. Im Idealfall enthält es nur eine Anweisung, die ein anderes Unterprogramm aufrufen, das eine identische Gruppe von Parametern hat oder Parameter, die aus und in die Parameter des Oberflächenprogramms konvertierbar sind.

PDL: Program Design Language.