Προγραμματισμός

iContract: Σχεδιασμός με σύμβαση στην Java

Δεν θα ήταν ωραίο αν όλα τα μαθήματα Java που χρησιμοποιείτε, συμπεριλαμβανομένων των δικών σας, ανταποκρίνονταν στις υποσχέσεις τους; Στην πραγματικότητα, δεν θα ήταν ωραίο εάν ήξερες πραγματικά τι υπόσχεται μια συγκεκριμένη τάξη; Εάν συμφωνείτε, διαβάστε το - Design by Contract και το iContract έρχονται στη διάσωση.

Σημείωση: Μπορείτε να κατεβάσετε την πηγή κώδικα για τα παραδείγματα σε αυτό το άρθρο από τους πόρους.

Σχεδιασμός με σύμβαση

Η τεχνική ανάπτυξης λογισμικού Design by Contract (DBC) εξασφαλίζει λογισμικό υψηλής ποιότητας, διασφαλίζοντας ότι κάθε στοιχείο ενός συστήματος ανταποκρίνεται στις προσδοκίες του. Ως προγραμματιστής που χρησιμοποιεί DBC, καθορίζετε ένα στοιχείο συμβάσεις ως μέρος της διεπαφής του στοιχείου. Η σύμβαση καθορίζει τι περιμένει αυτό το στοιχείο από τους πελάτες και τι μπορούν να περιμένουν οι πελάτες από αυτό.

Ο Bertrand Meyer ανέπτυξε το DBC ως μέρος της γλώσσας προγραμματισμού του Άιφελ. Ανεξάρτητα από την προέλευσή του, το DBC είναι μια πολύτιμη τεχνική σχεδιασμού για όλες τις γλώσσες προγραμματισμού, συμπεριλαμβανομένης της Java.

Κεντρικό σημείο στο DBC είναι η έννοια του ισχυρισμός - μια έκφραση Boolean σχετικά με την κατάσταση ενός συστήματος λογισμικού. Κατά το χρόνο εκτέλεσης αξιολογούμε τους ισχυρισμούς σε συγκεκριμένα σημεία ελέγχου κατά την εκτέλεση του συστήματος. Σε ένα έγκυρο σύστημα λογισμικού, όλοι οι ισχυρισμοί αξιολογούνται ως αληθινοί. Με άλλα λόγια, εάν κάποιος ισχυρισμός αξιολογηθεί ως ψευδής, θεωρούμε το σύστημα λογισμικού άκυρο ή κατεστραμμένο.

Η κεντρική έννοια του DBC σχετίζεται κάπως με το #διεκδικώ μακροεντολή σε γλώσσα προγραμματισμού C και C ++. Ωστόσο, το DBC διατυπώνει ισχυρισμούς σε επίπεδα zillion.

Στο DBC, εντοπίζουμε τρία διαφορετικά είδη εκφράσεων:

  • Προϋποθέσεις
  • Μετα προϋποθέσεις
  • Αμετάβλητα

Ας εξετάσουμε το καθένα με περισσότερες λεπτομέρειες.

Προϋποθέσεις

Οι προϋποθέσεις καθορίζουν συνθήκες που πρέπει να ισχύουν για να εκτελεστεί μια μέθοδος. Ως εκ τούτου, αξιολογούνται λίγο πριν εκτελεστεί μια μέθοδος. Οι προϋποθέσεις περιλαμβάνουν την κατάσταση του συστήματος και τα ορίσματα μεταφέρονται στη μέθοδο.

Οι προϋποθέσεις καθορίζουν τις υποχρεώσεις που πρέπει να πληροί ένας πελάτης ενός στοιχείου λογισμικού προτού μπορεί να επικαλεστεί μια συγκεκριμένη μέθοδο του στοιχείου. Εάν αποτύχει μια προϋπόθεση, ένα σφάλμα βρίσκεται στον πελάτη ενός στοιχείου λογισμικού.

Μετα προϋποθέσεις

Αντίθετα, οι μετα-προϋποθέσεις καθορίζουν συνθήκες που πρέπει να ισχύουν μετά την ολοκλήρωση μιας μεθόδου. Κατά συνέπεια, οι μετα-προϋποθέσεις εκτελούνται μετά την ολοκλήρωση μιας μεθόδου. Οι μετα-προϋποθέσεις περιλαμβάνουν την παλιά κατάσταση συστήματος, τη νέα κατάσταση συστήματος, τα ορίσματα μεθόδου και την τιμή επιστροφής της μεθόδου.

Οι μετα-προϋποθέσεις καθορίζουν τις εγγυήσεις που παρέχει ένα στοιχείο λογισμικού στους πελάτες του. Εάν παραβιαστεί μια μετα-κατάσταση, το στοιχείο λογισμικού έχει ένα σφάλμα.

Αμετάβλητα

Ένας αναλλοίωτος καθορίζει μια συνθήκη που πρέπει να διατηρείται οποτεδήποτε ένας πελάτης θα μπορούσε να επικαλεστεί τη μέθοδο ενός αντικειμένου. Οι αναλλοίωτες ορίζονται ως μέρος ενός ορισμού τάξης. Στην πράξη, οι αναλλοίωτες αξιολογούνται ανά πάσα στιγμή πριν και μετά από μια μέθοδο που εκτελείται σε οποιαδήποτε κατηγορία τάξης. Μια παραβίαση ενός αμετάβλητου μπορεί να υποδηλώνει ένα σφάλμα είτε στον πελάτη είτε στο στοιχείο λογισμικού.

Ισχυρισμοί, κληρονομιά και διεπαφές

Όλες οι δηλώσεις που καθορίζονται για μια τάξη και οι μέθοδοι της ισχύουν και για όλες τις υποκατηγορίες. Μπορείτε επίσης να καθορίσετε ισχυρισμούς για διασυνδέσεις. Ως εκ τούτου, όλοι οι ισχυρισμοί μιας διεπαφής πρέπει να ισχύουν για όλες τις κλάσεις που εφαρμόζουν τη διεπαφή.

iContract - DBC με Java

Μέχρι στιγμής, έχουμε μιλήσει για το DBC γενικά. Πιθανότατα να έχετε κάποια ιδέα τώρα για αυτό που μιλάω, αλλά αν είστε νέοι στο DBC, τα πράγματα μπορεί να είναι ακόμα λίγο ομιχλώδη.

Σε αυτήν την ενότητα, τα πράγματα θα γίνουν πιο συγκεκριμένα. Το iContract, που αναπτύχθηκε από τον Reto Kamer, προσθέτει κατασκευές στην Java που σας επιτρέπουν να καθορίσετε τους ισχυρισμούς DBC για τους οποίους συζητήσαμε νωρίτερα.

Βασικά στοιχεία iContract

Το iContract είναι ένας προεπεξεργαστής για Java. Για να το χρησιμοποιήσετε, επεξεργάζεστε πρώτα τον κώδικα Java με το iContract, δημιουργώντας ένα σύνολο διακοσμημένων αρχείων Java. Στη συνέχεια, μεταγλωττίζετε τον διακοσμημένο κώδικα Java ως συνήθως με τον μεταγλωττιστή Java.

Όλες οι οδηγίες iContract στον κώδικα Java βρίσκονται σε σχόλια κλάσης και μεθόδου, όπως και οι οδηγίες Javadoc. Με αυτόν τον τρόπο, το iContract διασφαλίζει πλήρη συμβατότητα προς τα πίσω με τον υπάρχοντα κώδικα Java και μπορείτε πάντα να μεταγλωττίσετε απευθείας τον κώδικα Java χωρίς τους ισχυρισμούς του iContract.

Σε έναν τυπικό κύκλο ζωής του προγράμματος, θα μετακινήσετε το σύστημά σας από ένα περιβάλλον ανάπτυξης σε ένα περιβάλλον δοκιμής και μετά σε ένα περιβάλλον παραγωγής. Στο περιβάλλον ανάπτυξης, θα δημιουργήσετε τον κώδικά σας με ισχυρισμούς iContract και θα τον εκτελέσετε. Με αυτόν τον τρόπο μπορείτε να εντοπίσετε πρόσφατα σφάλματα που έχουν εισαχθεί. Στο περιβάλλον δοκιμής μπορεί να θέλετε να διατηρήσετε το μεγαλύτερο μέρος των ισχυρισμών ενεργοποιημένο, αλλά θα πρέπει να τα πάρετε εκτός κλάσεων κρίσιμης απόδοσης. Μερικές φορές έχει νόημα να διατηρήσετε κάποιους ισχυρισμούς ενεργοποιημένους σε ένα περιβάλλον παραγωγής, αλλά μόνο σε τάξεις που σίγουρα δεν είναι καθόλου κρίσιμες για την απόδοση του συστήματός σας. Το iContract σάς επιτρέπει να επιλέξετε ρητά τις τάξεις που θέλετε να οργανώσετε με ισχυρισμούς.

Προϋποθέσεις

Στο iContract, τοποθετείτε προϋποθέσεις σε μια κεφαλίδα μεθόδου χρησιμοποιώντας το @προ διευθυντικός. Ακολουθεί ένα παράδειγμα:

/ ** * @pre f> = 0,0 * / δημόσιο float sqrt (float f) {...} 

Το παράδειγμα προϋπόθεσης διασφαλίζει ότι το επιχείρημα φά της λειτουργίας τετραγωνικά () είναι μεγαλύτερο ή ίσο με το μηδέν. Οι πελάτες που χρησιμοποιούν αυτήν τη μέθοδο είναι υπεύθυνοι για τη συμμόρφωση με αυτήν την προϋπόθεση. Εάν δεν το κάνουν, εμείς ως εκτελεστές του τετραγωνικά () απλά δεν ευθύνονται για τις συνέπειες.

Η έκφραση μετά το @προ είναι μια έκφραση Java Boolean.

Μετα προϋποθέσεις

Οι προϋποθέσεις προστίθενται επίσης στο σχόλιο κεφαλίδας της μεθόδου στην οποία ανήκουν. Στο iContract, το @Θέση Η οδηγία ορίζει τους όρους:

/ ** * @pre f> = 0,0 * @ post Math.abs ((επιστροφή * επιστροφή) - στ) <0,001 * / δημόσιο float sqrt (float f) {...} 

Στο παράδειγμά μας, έχουμε προσθέσει μια προϋπόθεση που διασφαλίζει ότι το τετραγωνικά () Η μέθοδος υπολογίζει την τετραγωνική ρίζα του φά εντός συγκεκριμένου περιθωρίου σφάλματος (+/- 0,001).

Το iContract εισάγει κάποιους συγκεκριμένους συμβολισμούς για μετα-προϋποθέσεις. Πρωτα απο ολα, ΕΠΙΣΤΡΟΦΗ σημαίνει την τιμή επιστροφής της μεθόδου. Κατά το χρόνο εκτέλεσης, αυτό θα αντικατασταθεί από την τιμή επιστροφής της μεθόδου.

Στις μετα-προϋποθέσεις, υπάρχει συχνά ανάγκη να γίνει διάκριση μεταξύ της αξίας ενός επιχειρήματος πριν εκτέλεση της μεθόδου και μετά, υποστηρίζεται στο iContract με το @προ χειριστής. Εάν προσθέσετε @προ σε μια έκφραση σε μια μετα-κατάσταση, θα αξιολογηθεί με βάση την κατάσταση του συστήματος προτού εκτελεστεί η μέθοδος:

/ ** * Προσθήκη στοιχείου σε μια συλλογή. * * @post c.size () = [email protected] () + 1 * @post c.contains (o) * / public void append (Συλλογή c, Object o) {...} 

Στον παραπάνω κώδικα, η πρώτη μετα-κατάσταση ορίζει ότι το μέγεθος της συλλογής πρέπει να αυξηθεί κατά 1 όταν προσθέτουμε ένα στοιχείο. Η έκφραση c @ προ αναφέρεται στη συλλογή ντο πριν από την εκτέλεση του προσαρτώ μέθοδος.

Αμετάβλητα

Με το iContract, μπορείτε να καθορίσετε αναλλοίωτα στο σχόλιο κεφαλίδας ενός ορισμού κλάσης:

/ ** * Ένα PositiveInteger είναι ένας ακέραιος που είναι εγγυημένος ότι είναι θετικός. * * @inv intValue ()> 0 * / η κλάση PositiveInteger επεκτείνει τον ακέραιο {...} 

Σε αυτό το παράδειγμα, το αμετάβλητο εγγυάται ότι το Θετικός ακέραιοςΗ τιμή είναι πάντα μεγαλύτερη ή ίση με το μηδέν. Αυτός ο ισχυρισμός ελέγχεται πριν και μετά την εκτέλεση οποιασδήποτε μεθόδου αυτής της κλάσης.

Γλώσσα περιορισμού αντικειμένου (OCL)

Παρόλο που οι εκφράσεις επιβεβαίωσης στο iContract είναι έγκυρες εκφράσεις Java, μοντελοποιούνται μετά από ένα υποσύνολο της γλώσσας αντικειμένων περιορισμών (OCL). Το OCL είναι ένα από τα πρότυπα που διατηρούνται και συντονίζονται από την ομάδα διαχείρισης αντικειμένων ή το OMG. (Το OMG φροντίζει την CORBA και τα σχετικά πράγματα, σε περίπτωση που χάσετε τη σύνδεση.) Το OCL είχε ως στόχο να καθορίσει περιορισμούς στα εργαλεία μοντελοποίησης αντικειμένων που υποστηρίζουν την Unified Modeling Language (UML), ένα άλλο πρότυπο που προστατεύεται από την OMG.

Επειδή η γλώσσα εκφράσεων iContract διαμορφώνεται σύμφωνα με το OCL, παρέχει ορισμένους προχωρημένους λογικούς τελεστές πέρα ​​από τους λογικούς τελεστές της Java.

Ποσοτικά: εμπρός και υπάρχει

Υποστήριξη iContract για όλα και υπάρχει ποσοτικοποιητές. ο για όλα Το quantifier καθορίζει ότι μια συνθήκη πρέπει να ισχύει για κάθε στοιχείο μιας συλλογής:

/ * * @invariant forall IEm Employee e in getEm Employees () | * getRooms (). περιέχει (e.getOffice ()) * / 

Το παραπάνω αναλλοίωτο καθορίζει ότι κάθε υπάλληλος που επιστρέφεται από getEm Employees () έχει ένα γραφείο στη συλλογή δωματίων που επέστρεψε από getRooms (). Εκτός από το για όλα λέξη-κλειδί, η σύνταξη είναι ίδια με εκείνη ενός υπάρχει έκφραση.

Εδώ είναι ένα παράδειγμα χρήσης υπάρχει:

/ ** * @post υπάρχει IRoom r στο getRooms () | r.isΔιαθέσιμο () * / 

Αυτή η μετα-κατάσταση ορίζει ότι μετά την εκτέλεση της σχετικής μεθόδου, η συλλογή επέστρεψε από getRooms () θα περιέχει τουλάχιστον ένα διαθέσιμο δωμάτιο. ο υπάρχει προχωρά τον τύπο Java του στοιχείου συλλογής - Αίθουσα στο παράδειγμα. ρ είναι μια μεταβλητή που αναφέρεται σε οποιοδήποτε στοιχείο της συλλογής. ο σε λέξη-κλειδί ακολουθείται από μια έκφραση που επιστρέφει μια συλλογή (Απαρίθμηση, Πίνακας, ή Συλλογή). Αυτή η έκφραση ακολουθείται από μια κάθετη γραμμή, ακολουθούμενη από μια συνθήκη που περιλαμβάνει τη μεταβλητή στοιχείου, ρ στο παράδειγμα. Χρησιμοποιήστε το υπάρχει ποσοτικοποιητής όταν μια συνθήκη πρέπει να ισχύει για τουλάχιστον ένα στοιχείο σε μια συλλογή.

Και τα δυο για όλα και υπάρχει μπορεί να εφαρμοστεί σε διαφορετικά είδη συλλογών Java. Υποστηρίζουν Απαρίθμησημικρό, Πίνακαςs, και Συλλογήμικρό.

Επιπτώσεις: υπονοεί

Το iContract παρέχει το υποδηλώνει τελεστής να καθορίσει τους περιορισμούς της φόρμας, "Αν το A κρατάει, τότε το B πρέπει επίσης να κρατήσει." Λέμε, "A υπονοεί Β." Παράδειγμα:

/ ** * @invariant getRooms (). isEmpty () σημαίνει getEm Employees (). isEmpty () // χωρίς δωμάτια, χωρίς υπαλλήλους * / 

Αυτό αναλλοίωτο εκφράζει ότι όταν το getRooms () η συλλογή είναι κενή, η getEm Employees () Η συλλογή πρέπει επίσης να είναι κενή. Σημειώστε ότι δεν καθορίζει ότι πότε getEm Employees () είναι άδειο, getRooms () πρέπει επίσης να είναι άδειο.

Μπορείτε επίσης να συνδυάσετε τους λογικούς τελεστές που μόλις εισήχθησαν για να σχηματίσουν σύνθετους ισχυρισμούς. Παράδειγμα:

/ ** * @invariant forall IEm Employee e1 στο getEm Employees () | * forall IEm Employee e2 στο getEm Employees () | * (e1! = e2) σημαίνει e1.getOffice ()! = e2.getOffice () // ένα ενιαίο γραφείο ανά υπάλληλο * / 

Περιορισμοί, κληρονομιά και διεπαφές

Το iContract διαδίδει περιορισμούς στις σχέσεις κληρονομιάς και εφαρμογής διεπαφών μεταξύ τάξεων και διεπαφών.

Ας υποθέσουμε ότι η τάξη σι επεκτείνει την τάξη ΕΝΑ. Τάξη ΕΝΑ ορίζει ένα σύνολο αναλλοίωτων, προϋποθέσεων και μετα-προϋποθέσεων. Σε αυτήν την περίπτωση, οι αναλλοίωτες και οι προϋποθέσεις της τάξης ΕΝΑ ισχύουν για την τάξη σι επίσης, και μεθόδους στην τάξη σι πρέπει να ικανοποιεί τις ίδιες προϋποθέσεις με αυτήν την τάξη ΕΝΑ ικανοποιεί Μπορείτε να προσθέσετε πιο περιοριστικούς ισχυρισμούς στην τάξη σι.

Ο προαναφερθείς μηχανισμός λειτουργεί επίσης για διασυνδέσεις και εφαρμογές. Υποθέτω ΕΝΑ και σι είναι διεπαφές και τάξη ντο εφαρμόζει και τα δύο. Σε αυτή την περίπτωση, ντο υπόκειται σε αμετάβλητες, προϋποθέσεις και μετα-προϋποθέσεις και των δύο διεπαφών, ΕΝΑ και σι, καθώς και αυτά που ορίζονται απευθείας στην τάξη ντο.

Προσοχή στις παρενέργειες!

Το iContract θα βελτιώσει την ποιότητα του λογισμικού σας επιτρέποντάς σας να εντοπίζετε πολλά πιθανά σφάλματα από νωρίς. Αλλά μπορείτε επίσης να τραβήξετε τον εαυτό σας στο πόδι (δηλαδή να εισαγάγετε νέα σφάλματα) χρησιμοποιώντας το iContract. Αυτό μπορεί να συμβεί όταν επικαλέστε λειτουργίες στους ισχυρισμούς του iContract που προκαλούν παρενέργειες που αλλάζουν την κατάσταση του συστήματός σας. Αυτό οδηγεί σε απρόβλεπτη συμπεριφορά, επειδή το σύστημα θα συμπεριφέρεται διαφορετικά όταν συντάξετε τον κώδικά σας χωρίς όργανα iContract.

Το παράδειγμα στοίβας

Ας ρίξουμε μια ματιά σε ένα πλήρες παράδειγμα. Έχω ορίσει το Σωρός διεπαφή, η οποία καθορίζει τις οικείες λειτουργίες της αγαπημένης μου δομής δεδομένων:

/ ** * @inv! isEmpty () σημαίνει κορυφή ()! = null // δεν επιτρέπονται μηδενικά αντικείμενα * / δημόσια διεπαφή Stack {/ ** * @pre o! = null * @post! isEmpty () * @post κορυφή () == o * / άκυρη ώθηση (αντικείμενο o); / ** * @pre! isEmpty () * @post @return == top () @ pre * / Αντικείμενο pop (); / ** * @pre! isEmpty () * / Αντικείμενο πάνω (); boolean isEmpty (); } 

Παρέχουμε μια απλή εφαρμογή της διεπαφής:

εισαγωγή java.util. *; / ** * @inv isEmpty () υποδηλώνει element.size () == 0 * / public class StackImpl υλοποιεί Stack {private final LinkedList στοιχεία = new LinkedList (); δημόσια άκυρη ώθηση (αντικείμενο o) {element.add (o); } δημοσίευση αντικειμένου () {final Object popped = top (); element.removeLast (); εμφανίστηκε η επιστροφή. } δημόσιο Αντικείμενο κορυφή () {return element.getLast (); } δημόσιο boolean isEmpty () {return element.size () == 0; }} 

Όπως μπορείτε να δείτε, το Σωρός Η εφαρμογή δεν περιέχει ισχυρισμούς iContract. Αντίθετα, όλοι οι ισχυρισμοί γίνονται στη διεπαφή, που σημαίνει ότι το συστατικό συμβόλαιο του Stack ορίζεται στο περιβάλλον εργασίας στο σύνολό του. Απλώς κοιτάζοντας το Σωρός διεπαφή και οι ισχυρισμοί της, το ΣωρόςΗ συμπεριφορά είναι πλήρως καθορισμένη.

Τώρα προσθέτουμε ένα μικρό πρόγραμμα δοκιμών για να δούμε το iContract σε δράση:

δημόσια τάξη StackTest {public static void main (String [] args) {final Stack s = new StackImpl (); s.push ("ένα"); s.pop (); s.push ("δύο"); s.push ("τρία"); s.pop (); s.pop (); s.pop (); // προκαλεί την αποτυχία ενός ισχυρισμού}} 

Στη συνέχεια, εκτελούμε το iContract για να δημιουργήσουμε το παράδειγμα στοίβας:

java -cp% CLASSPATH%; src; _contract_db; instr com.reliablesystems.iContract.Tool -Z -a -v -minv, pre, post> -b "javac -classpath% CLASSPATH%; src" -c "javac -classpath % CLASSPATH%; instr "> -n" javac -classpath% CLASSPATH%; _ contract_db; instr "-oinstr / @ p / @ f. @ E -k_contract_db / @ p src / *. Java 

Η παραπάνω δήλωση απαιτεί λίγη εξήγηση.