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

Ορθογονικότητα Log4j για παράδειγμα

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

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

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

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

Οι διαστάσεις του Log4j

Η καταγραφή είναι απλώς μια πιο φανταστική έκδοση του System.out.println () statement, και το Log4j είναι ένα πακέτο βοηθητικών προγραμμάτων που αφαιρεί τους μηχανισμούς της καταγραφής στην πλατφόρμα Java. Μεταξύ άλλων, οι λειτουργίες Log4j επιτρέπουν στους προγραμματιστές να κάνουν τα εξής:

  • Συνδεθείτε σε διαφορετικά appenders (όχι μόνο στην κονσόλα, αλλά και σε αρχεία, τοποθεσίες δικτύου, σχεσιακές βάσεις δεδομένων, βοηθητικά προγράμματα καταγραφής λειτουργικού συστήματος και άλλα)
  • Συνδεθείτε σε διάφορα επίπεδα (όπως ERROR, WARN, INFO και DEBUG)
  • Ελέγξτε κεντρικά πόσες πληροφορίες καταγράφονται σε ένα δεδομένο επίπεδο καταγραφής
  • Χρησιμοποιήστε διαφορετικές διατάξεις για να ορίσετε πώς αποδίδεται ένα συμβάν καταγραφής σε μια συμβολοσειρά

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

Log4j στο JavaWorld

Πάρτε ένα επισκόπηση του Log4j και μάθετε πώς να γράφετε το δικό σας προσαρμοσμένα προσαρτήματα Log4j. Θέλετε περισσότερα μαθήματα Java; Να πάρει το Ενημερωτικό δελτίο Enterprise Java παραδόθηκε στα εισερχόμενά σας.

Λαμβάνοντας υπόψη τους τύπους Log4j ως πτυχές

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

  • Προσάρτημα: Πού πρέπει να αποστέλλονται τα δεδομένα συμβάντων καταγραφής για προβολή ή αποθήκευση;
  • Σχέδιο: Πώς πρέπει να παρουσιάζεται ένα συμβάν καταγραφής;
  • Επίπεδο: Ποια συμβάντα καταγραφής πρέπει να υποβληθούν σε επεξεργασία;

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

Η καταχώριση 1 είναι ένα τυπικό απόσπασμα κώδικα που εφαρμόζει το Log4j:

Λίστα 1. Ένα παράδειγμα υλοποίησης Log4j

// καταγραφή εγκατάστασης! Logger logger = Logger.getLogger ("Foo"); Appender appender = νέο ConsoleAppender (); Διάταξη διάταξης = νέο org.apache.log4j.TTCCLayout () appender.setLayout (διάταξη); logger.addAppender (appender); logger.setLevel (Level.INFO); // ξεκινήστε την καταγραφή! logger.warn ("Γεια σας Κόσμος");

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

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

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

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

Σχεδιασμός και κωδικοποίηση ορθογωνικότητας

Εάν αναρωτιέστε τώρα πώς να σχεδιάσετε και να κωδικοποιήσετε την ορθογωνικότητα στα προγράμματά σας, τότε βρίσκεστε στο σωστό μέρος. Η βασική ιδέα είναι η χρήση αφαίρεση. Κάθε διάσταση ενός ορθογώνιου συστήματος αφορά μια συγκεκριμένη πτυχή του προγράμματος. Μια τέτοια διάσταση συνήθως αντιπροσωπεύεται από ένα τύπος (κλάση, διεπαφή ή απαρίθμηση). Η πιο κοινή λύση είναι να χρησιμοποιήσετε ένα αφηρημένος τύπος (διεπαφή ή αφηρημένη κλάση). Κάθε ένας από αυτούς τους τύπους αντιπροσωπεύει μια διάσταση, ενώ η παρουσία τύπου αντιπροσωπεύει τα σημεία εντός της δεδομένης διάστασης. Επειδή οι αφηρημένοι τύποι δεν μπορούν να δημιουργηθούν άμεσα, απαιτούνται επίσης συγκεκριμένες κατηγορίες.

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

Σχήμα 3. Μέσα στη διάσταση επιπέδου

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

Για παράδειγμα, στο Log4j ο αφηρημένος τύπος Σχέδιο ορίζει τη μέθοδο αγνοεί Throwable (). Αυτή η μέθοδος επιστρέφει ένα boolean που δείχνει εάν η διάταξη μπορεί να αποδώσει ίχνη στοίβας εξαίρεσης ή όχι. Όταν ένας υποψήφιος χρησιμοποιεί μια διάταξη, θα ήταν εντάξει να γράψετε κώδικα υπό όρους αγνοεί Throwable (). Για παράδειγμα, ένας προσαρτητής αρχείων θα μπορούσε να εκτυπώσει ίχνη στοίβας εξαίρεσης System.err όταν χρησιμοποιείτε διάταξη που δεν μπορούσε να χειριστεί εξαιρέσεις.

Με παρόμοιο τρόπο, α Σχέδιο εφαρμογή θα μπορούσε να αναφέρεται σε ένα συγκεκριμένο Επίπεδο κατά την απόδοση συμβάντων καταγραφής. Για παράδειγμα, εάν το επίπεδο καταγραφής ήταν Επίπεδο. ΣΦΑΛΜΑ, μια εφαρμογή διάταξης που βασίζεται σε HTML θα μπορούσε να τυλίξει το μήνυμα καταγραφής σε ετικέτες που το καθιστούν με κόκκινο χρώμα. Και πάλι, το θέμα είναι αυτό Επίπεδο. ΣΦΑΛΜΑ ορίζεται από Επίπεδο, ο τύπος που αντιπροσωπεύει την ιδιότητα.

Θα πρέπει, ωστόσο, να αποφεύγετε αναφορές σε συγκεκριμένες τάξεις υλοποίησης για άλλες διαστάσεις. Εάν ένας υποψήφιος χρησιμοποιεί διάταξη, δεν χρειάζεται να γνωρίζετε τι είδους της διάταξης είναι. Το σχήμα 4 απεικονίζει καλές και κακές αναφορές.

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

Παραβίαση της ορθογωνικότητας

Συνολικά, το Log4j είναι ένα καλό παράδειγμα χρήσης ορθογωνικότητας. Ωστόσο, ορισμένος κώδικας στο Log4j παραβιάζει αυτήν την αρχή.

Το Log4j περιέχει ένα appender που ονομάζεται JDBCAppender, που χρησιμοποιείται για τη σύνδεση σε μια σχεσιακή βάση δεδομένων. Λαμβάνοντας υπόψη την επεκτασιμότητα και τη δημοτικότητα της σχεσιακής βάσης δεδομένων, και το γεγονός ότι αυτό καθιστά τα συμβάντα καταγραφής εύκολα αναζητήσιμα (με ερωτήματα SQL), JDBCAppender είναι μια σημαντική περίπτωση χρήσης.

JDBCAppender προορίζεται για την αντιμετώπιση του προβλήματος της σύνδεσης σε μια σχεσιακή βάση δεδομένων μετατρέποντας τα συμβάντα καταγραφής σε SQL ΕΙΣΑΓΕΤΕ δηλώσεις. Επιλύει αυτό το πρόβλημα χρησιμοποιώντας ένα Σχέδιο διάταξης.

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

Λίστα 2. PatternLayout

Σχέδιο συμβολοσειράς = "% p [@% d {dd MMM yyyy HH: mm: ss} in% t]% m% n"; Διάταξη διάταξης = νέο org.apache.log4j.PatternLayout (μοτίβο); appender.setLayout (διάταξη);

JDBCAppender χρησιμοποιεί ένα Σχέδιο διάταξης με ένα μοτίβο που ορίζει το SQL ΕΙΣΑΓΕΤΕ δήλωση. Συγκεκριμένα, μπορεί να χρησιμοποιηθεί ο ακόλουθος κώδικας για τη ρύθμιση της δήλωσης SQL που χρησιμοποιείται:

Λίστα 3. Δήλωση εισαγωγής SQL

public void setSql (String s) {sqlStatement = s; if (getLayout () == null) {this.setLayout (νέα μοτίβα) } αλλιώς {((PatternLayout) getLayout ()). setConversionPattern (s); }}

Ενσωματωμένος σε αυτόν τον κώδικα είναι η σιωπηρή υπόθεση ότι η διάταξη, εάν οριστεί πριν από τη χρήση του setLayout (Διάταξη) μέθοδος που ορίζεται στο Προσάρτημα, στην πραγματικότητα είναι ένα παράδειγμα του Σχέδιο διάταξης. Όσον αφορά την ορθογονικότητα, αυτό σημαίνει ότι ξαφνικά πολλά σημεία στον τρισδιάστατο κύβο που χρησιμοποιούν JDBCAppender με διατάξεις εκτός από Σχέδιο διάταξης δεν αντιπροσωπεύουν πλέον έγκυρες διαμορφώσεις συστήματος! Δηλαδή, οποιεσδήποτε προσπάθειες ορισμού της συμβολοσειράς SQL με διαφορετική διάταξη θα οδηγούσαν σε εξαίρεση χρόνου εκτέλεσης (class cast).

Εικόνα 5. JDBCAppender που παραβιάζει την ορθογονικότητα

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

Λίστα 4. Παράκαμψη του setLayout ()

public void setLayout (Διάταξη διάταξης) {if (layout instanceOf PatternLayout) {super.setLayout (διάταξη); } αλλιώς {ρίξτε νέο IllegalArgumentException ("Η διάταξη δεν είναι έγκυρη"); }}

Δυστυχώς, αυτή η προσέγγιση έχει επίσης προβλήματα. Η μέθοδος στην καταχώριση 4 ρίχνει μια εξαίρεση χρόνου εκτέλεσης και οι εφαρμογές που καλούν αυτήν τη μέθοδο ενδέχεται να μην είναι έτοιμες να την πιάσουν. Με άλλα λόγια, το setLayout (διάταξη διάταξης) Η μέθοδος δεν μπορεί να εγγυηθεί ότι δεν θα υπάρξει εξαίρεση χρόνου εκτέλεσης. Αποδυναμώνει επομένως τις εγγυήσεις (μετα-προϋποθέσεις) που παρέχονται με τη μέθοδο που παρακάμπτει. Αν το κοιτάξουμε με όρους προϋποθέσεων, setLayout απαιτεί η διάταξη να είναι μια παρουσία του Σχέδιο διάταξης, και ως εκ τούτου ισχυρότερη προϋποθέσεις από τη μέθοδο που αντικαθιστά. Σε κάθε περίπτωση, παραβιάσαμε μια βασική αντικειμενοστραφή αρχή σχεδιασμού, η οποία είναι η αρχή υποκατάστασης Liskov που χρησιμοποιείται για την προστασία της κληρονομιάς.

Λύσεις

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

Μια πιθανή λύση θα ήταν να χρησιμοποιήσετε μια πιο εξελιγμένη δομή δεδομένων ως τύπο επιστροφής για τη μορφή. Ωστόσο, αυτό συνεπάγεται επιπλέον επιβάρυνση σε καταστάσεις όπου ίσως θέλετε να δημιουργήσετε μια συμβολοσειρά. Θα πρέπει να δημιουργηθούν επιπλέον ενδιάμεσα αντικείμενα και στη συνέχεια να συλλέγονται σκουπίδια, υπονομεύοντας την απόδοση του πλαισίου καταγραφής. Η χρήση ενός πιο εξελιγμένου τύπου επιστροφής θα έκανε το Log4j πιο δύσκολο να κατανοηθεί. Η απλότητα είναι ένας πολύ επιθυμητός σχεδιαστικός στόχος.

Μια άλλη πιθανή λύση θα ήταν να χρησιμοποιήσετε την "αφαίρεση με στρώσεις" χρησιμοποιώντας δύο αφηρημένους τύπους, Προσάρτημα και Προσαρμόσιμο Appender που εκτείνεται Προσάρτημα. Μόνο Προσαρμόσιμο Appender τότε θα καθορίσει τη μέθοδο setLayout (διάταξη διάταξης). JDBCAppender θα εφαρμόσει μόνο Προσάρτημα, ενώ άλλες εφαρμογές εφαρμογής όπως Console Appender θα εφαρμόσει Προσαρμόσιμο Appender. Το μειονέκτημα αυτής της προσέγγισης είναι η αυξημένη πολυπλοκότητα (π.χ. τρόπος επεξεργασίας αρχείων διαμόρφωσης Log4j) και το γεγονός ότι οι προγραμματιστές πρέπει να λάβουν μια τεκμηριωμένη απόφαση σχετικά με το επίπεδο αφαίρεσης που θα χρησιμοποιούν νωρίς.

Συμπερασματικά

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

$config[zx-auto] not found$config[zx-overlay] not found