Γενικό Λύκειο > Θεωρία

Κατηγορίες λαθών

(1/6) > >>

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

Θα ξεχωρίσω πρώτα τις κατηγορίες λαθών με βάση τα παρακάτω εργαλεία:

- Οι lexers κάνουν λεκτικό έλεγχο. Ανοικτά αλφαριθμητικά, άκυρα σύμβολα κλπ ανιχνεύονται εδώ. Για απλούστευση, στη ΓΛΩΣΣΑ αυτά τα έχουμε ενώσει με τα συντακτικά λάθη.
- Οι parsers κάνουν συντακτικό έλεγχο. "ΓΙΑ ι ΕΠΑΝΑΛΑΒΕ" ανιχνεύεται εδώ.
- Οι linters κάνουν στατική ανάλυση του κώδικα, με σκοπό να ανιχνεύσουν λογικά λάθη πριν ακόμα εκτελεστεί το πρόγραμμα. ΑΝ λοιπόν θεωρήσουμε ότι η ΓΛΩΣΣΑ περιλαμβάνει linter, η διαίρεση με το μηδέν, η προσπέλαση στοιχείων εκτός πίνακα κλπ θα μπορούσαν να ανιχνευτούν και πριν την εκτέλεση, ενώ κανονικά ανιχνεύονται κατά την εκτέλεση.
- Οι βιβλιοθήκες των γλωσσών προγραμματισμού και των λειτουργικών συστημάτων (runtime systems) εγείρουν τα λάθη χρόνου εκτέλεσης, και εμφανίζουν είτε warnings είτε προκαλούν αντικανονικό τερματισμό του προγράμματος, πριν το φυσιολογικό ΤΕΛΟΣ_ΠΡΟΓΡΑΜΜΑΤΟΣ.

Πάμε τώρα στους compilers / interpreters. Αυτοί στην "απλή" τους έκδοση περιλαμβάνουν μόνο lexers / parsers / runtime-systems.
Άρα αν ζητήσουμε η ΓΛΩΣΣΑ να υλοποιείται με "απλό" Διερμηνευτή, τότε τα περισσότερα "ασαφή σημεία" που συζητάμε, όπως η διαίρεση με το μηδέν ή η ΓΡΑΨΕ εντός συναρτήσεων, ΔΕΝ μπορούν να ανιχνευτούν πριν την εκτέλεση.
Όμως, όλοι οι μοντέρνοι compilers / interpreters είναι πολύ προχωρημένοι και περιλαμβάνουν και linters ή/και code optimizers. Σε αυτήν την περίπτωση, πολλά λάθη θα μπορούσαν να ανιχνευτούν και πριν την εκτέλεση. Όμως ΔΕΝ θα έπρεπε να τα αποκαλούμε "συντακτικά λάθη" για να μην μπλεχτούμε, αλλά να κάνουμε μια νέα κατηγορία λαθών γι' αυτά, "compiler warnings" ή "lintian λάθη" κλπ.

Για παράδειγμα, ο GNU compiler gcc περιλαμβάνει τις παρακάτω ενδιαφέρουσες επιλογές:

* -fsyntax-only: μόνο απλός συντακτικός έλεγχος, καθόλου linting
* -Wfatal-errors: τα warnings γίνονται λάθη και δεν προχωράνε σε εκτέλεση
* -Wunused-variable: μεταβλητή δηλώθηκε αλλά δεν χρησιμοποιήθηκε
* -Wuninitialized: μεταβλητή χρησιμοποιήθηκε χωρίς να αρχικοποιηθεί
* -Warray-bounds: προσπέλαση στοιχείου πίνακα εκτός ορίων
* -Wdiv-by-zero: προειδοποίηση για στατική διαίρεση με το μηδένΈτσι, υπάρχει η δυνατότητα το 1/0 να μην το καταλάβει καθόλου, ή να το καταλάβει και να θεωρηθεί fatal warning και να μην προχωρήσει σε εκτέλεση. ΔΕΝ υπάρχει όμως η δυνατότητα να καταλάβει το ΔΙΑΒΑΣΕ α, ΓΡΑΨΕ 1/α, και ο χρήστης να δώσει α=0, αυτό θα είναι πάντα σφάλμα χρόνου εκτέλεσης. Υπάρχει και η δυνατότητα μια μεταβλητή που δεν χρησιμοποιήθηκε να θεωρηθεί σφάλμα και να μην γίνεται εκτέλεση πριν διορθωθεί.

Προτείνω να αναφέρουμε ως συντακτικά λάθη, και να εξετάζουμε ως συντακτικά λάθη, μόνο αυτά που αντιστοιχούν στο "-fsyntax-only". Δηλαδή κανείς μας να μην αναφέρει το 1/0 ως συντακτικό λάθος.
Περαιτέρω, αν ζητήσουμε από το Διερμηνευτή να κάνει έλεγχο τύπου lint, και να ανιχνεύσει τέτοια λάθη πριν την εκτέλεση, ΠΑΛΙ να μην τα αναφέρουμε ως συντακτικά.
Για παράδειγμα, ο Διερμηνευτής κάνει κάποιους ελέγχους lint, π.χ. έχει επιλογή για το αν επιτρέπεται ή όχι η ΓΡΑΨΕ μέσα σε συναρτήσεις. Αυτό λοιπόν ανιχνεύεται πριν την εκτέλεση, άρα κάποιος θα μπορούσε να το πει "συντακτικό λάθος". Δεν παραβιάζεται όμως κάποιος συντακτικός κανόνας, αυτό το λάθος ανιχνεύεται κατά το linting / static code analysis, που είναι διαφορετικό πράγμα. Δεν έχει νόημα ούτε εμείς ούτε οι μαθητές να μπερδευτούμε με αυτό. Βλέποντας όλη τη σύγχιση που έχει δημιουργηθεί, ίσως θα ήταν καλύτερα να μην είχα βάλει καθόλου linting στο Διερμηνευτή.

Ένα ακόμα σημείο αντιπαραθέσεων είναι τα λάθη χρόνου εκτέλεσης, που είναι ταυτόσημα με τα "λάθη που προκαλούν αντικανονικό τερματισμό", αλλά δεν αναλύονται και πολύ καλά. Λάθη χρόνου εκτέλεσης λέμε τα warnings ή errors που εμφανίζει το runtime system. Η διαίρεση με το μηδέν θα προκαλέσει CPU interrupt το οποίο θα το χειριστεί η libc κλπ. Και μπορούν είτε να εμφανίσουν ένα warning "διαίρεση με το μηδέν, οκ, συνεχίζω", ή να σταματήσουν με "αντικανονικό τερματισμό" την εκτέλεση, είτε ακόμα να πάνε στο "onerror ... goto" που λέει το βιβλίο για την Basic, όπου γίνεται ο "χειρισμός του λάθους χρόνου εκτέλεσης", και έτσι αποφεύγεται ο αντικανονικός τερματισμός.
Εδώ για απλούστευση θα πρότεινα όλα τα λάθη χρόνου εκτέλεσης να θεωρήσουμε ότι προκαλούν τερματισμό και είναι εντελώς ισοδύναμα με τα "λάθη που προκαλούν αντικανονικό τερματισμό".
Προσοχή, ένα άπειρο loop δεν προκαλεί κανένα λάθος χρόνου εκτέλεσης, ούτε η CPU ούτε η libc κλπ δεν καταλαβαίνουν ότι κάτι πάει στραβά. Ο Διερμηνευτής εδώ πάλι πάει να κάνει τον έξυπνο και λέει "έχουν γίνει 100.000 επαναλήψεις, μήπως υπάρχει άπειρο loop;" αλλά δεν μπορούμε αυτό να το εντάξουμε στα λάθη εκτέλεσης.

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

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

Τι λάθος είναι το 1/0;
Κανονικά χρόνου εκτέλεσης, αλλά μπορεί να βρεθεί και με static code analysis (lint). Δεν μπορεί όμως να θεωρηθεί συντακτικό.

Υπάρχουν λογικά λάθη που να μην προκαλούν λάθη χρόνου εκτέλεσης;
Ναι, για παράδειγμα ένα άπειρο loop.

Υπάρχουν λάθη χρόνου εκτέλεσης που να μην είναι λογικά;
Ναι, για παράδειγμα ΓΡΑΨΕ "Hello world" να βγάζει "σφάλμα, δεν βρέθηκε οθόνη (stdout)". Η επίλυσή του δεν αφορά τον κώδικά μας άρα δεν είναι λογικό λάθος (μας).

Τι λάθος είναι η ΓΡΑΨΕ εντός συναρτήσεων;
Με βάση τις γενικότερες επιστημονικές μας γνώσεις, θα θέλαμε να μην είναι λάθος. Με βάση το βιβλίο, είναι απλά κακή πρακτική. Με βάση την οδηγία του Υπουργείου, είναι λάθος χωρίς να αναφέρεται ο χρόνος που θα ανιχνευτεί. Με βάση το Διερμηνευτή, ΑΝ η σχετική επιλογή είναι ενεργοποιημένη (που είναι by default), είναι λάθος τύπου lint/static code analysis. Δεν υπάρχει κάποια ένδειξη πουθενά ώστε να θεωρηθεί συντακτικό ή χρόνου εκτέλεσης. Άρα στα πλαίσια της ΑΕΠΠ αυτή η ερώτηση είναι βαθύ γκρίζο.

ApoAntonis:

--- Παράθεση από: alkisg στις 28 Μαρ 2020, 10:04:54 πμ ---Υπάρχουν λογικά λάθη που να μην προκαλούν λάθη χρόνου εκτέλεσης;
Ναι, για παράδειγμα ένα άπειρο loop.
--- Τέλος παράθεσης ---

Το έχω ξαναγράψει, ελπίζω να μην γίνομαι κουραστικός, τα infinite loops
κινούνται στο όριο της ύλης.


--- Κώδικας: ---i <- 0
x <- 1
ΟΣΟ ( x > 0) ΕΠΑΝΑΛΑΒΕ
    i <- i + 1
    x <- x/2
ΤΕΛΟΣ_ΕΠΑΝΑΛΗΨΗΣ
--- Τέλος κώδικα ---

ενώ αν ακολουθεί η εντολή

--- Κώδικας: ---tmp <- 1/x

--- Τέλος κώδικα ---

το λάθος είναι αχαρακτήριστο ... με αυτά που γνωρίζουμε.

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

alkisg:

--- Παράθεση από: ApoAntonis στις 29 Μαρ 2020, 11:35:07 πμ ---Το έχω ξαναγράψει, ελπίζω να μην γίνομαι κουραστικός, τα infinite loops κινούνται στο όριο της ύλης.

--- Τέλος παράθεσης ---

Για να μην μπλέξουμε τις συζητήσεις, εγώ έλεγα για infinite loops (ΟΣΟ ΑΛΗΘΗΣ ΕΠΑΝΑΛΑΒΕ).
Το παράδειγμά σου κυρίως είναι για την ακρίβεια των πραγματικών αριθμών. Το αν θα κάνει άπειρη επανάληψη ή όχι είναι η παρενέργεια, όχι ο πυρήνας του συγκεκριμένου παραδείγματος.

Αν υποθέσουμε ότι η ΓΛΩΣΣΑ έχει άπειρη ακρίβεια στους πραγματικούς, τότε έχουμε κάνει λάθος υπόθεση, αφού κανένας ψηφιακός υπολογιστής δεν μπορεί να χωρέσει ούτε μία "άπειρη" μεταβλητή. Το σωστό είναι να υποθέσουμε ότι η ΓΛΩΣΣΑ "έχει όσο μεγάλη ακρίβεια χρειαζόμαστε για την επίλυση του προβλήματος", αλλά αυτό δεν στέκει στο παράδειγμα που δίνεις, αφού προσπαθείς να βρεις τα όρια της ακρίβειας. Δηλαδή η κατάλληλη απάντηση είναι ότι το παράδειγμα είναι λάθος.

Παρ' όλα αυτά προχωράμε παρακάτω και δεχόμαστε ότι έχουμε "μια κάποια ακρίβεια", και πάμε να τρέξουμε το παράδειγμά σου. Εκεί λοιπόν θα γίνει "underflow" στην τελική x <- x/2, το οποίο το runtime-system μπορεί να το πιάσει, και επομένως θα έχουμε λάθος χρόνου εκτέλεσης.
Αν δεν πιάσει το "underflow" στο x/2 και το κάνει σιωπηλά 0, τότε θα έχουμε διαίρεση με το 0 στο tmp <- 1/x. Και πάλι δηλαδή λάθος χρόνου εκτέλεσης, αλλά φυσικά όχι επειδή έχει άπειρες επαναλήψεις, αλλά επειδή έφτασε τα όρια ακρίβειας των πραγματικών αριθμών.


--- Παράθεση από: ApoAntonis στις 29 Μαρ 2020, 11:35:07 πμ ---Ακόμα μια απορία. Η δήλωση ακέραιας μεταβλητής ως πραγματική, συνιστά λάθος;

--- Τέλος παράθεσης ---

Εάν εκεί που χρησιμοποιούμε τη μεταβλητή, μπαίνει και ακέραια και πραγματική (π.χ. σε ΔΙΑΒΑΣΕ, στο εσωτερικό της Τ_Ρ, ...), τότε δεν είναι λάθος.
Αν μπαίνει μόνο ακέραια (π.χ. ως τελεστέο των mod/div, ως δείκτη πίνακα), τότε είναι συντακτικό λάθος.

bugman:
Τα λάθη τύπου lint θα μπορούσαν να λέγονταν ΑΝΑΛΥΤΙΚΑ ΛΑΘΗ. Οπότε αν υπάρχει αναλυση λαθών τότε υπάρχουν τα ανάλογα λάθη πριν την εκτέλεση, αλλιώς θα είναι επί της εκτέλεσης.

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

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

Πχ. Έχουμε ένα πρόγραμμα που τρέχει σωστά εδώ και καιρό και έστω ότι σε αυτό ανοίγουμε ένα αρχείο και καταχωρούμε νούμερα. Κάποια στιγμή το μέσο εγγραφής έχει γεμίσει. Δεν το ξέρουμε και τρέχουμε το πρόγραμμα. Πάει λοιπόν το πρόγραμμα να φτιάξει το αρχείο και η λειτουργία του συστήματος λέει ότι δεν μπορεί. Στο πρόγραμμα μας όμως δεν είχε συμπεριληφθεί αυτή η εξαίρεση οπότε συνεχίζει στην επόμενη εντολή να στείλει στο αρχείο τα στοιχεία. Εδώ το λειτουργικό στέλνει πάλι εξαίρεση ότι δεν κάνει τη δουλειά να γράψει τους αριθμούς. Το ερώτημα εδώ είναι. Θα πρέπει με κάποιο τρόπο να μάθουμε αν όντως γράφτηκαν τα στοιχεία; Ναι θα έπρεπε αν επεξεργαζόμασταν τις εξαιρέσεις. Δείτε ότι δεν τις λέω λάθη, γιατί το πρόγραμμα δεν έχει λάθος, και έχει πολλές φορές τρέξει σωστά. Λοιπόν κάπου το σύστημα πρέπει να σταματά το πρόγραμμα, ώστε να καταλάβουμε ότι υπάρχει πρόβλημα και δεν το έχουμε υπό έλεγχο. Έτσι όταν το λειτουργικό βγάζει μια εξαίρεση περιμένει να πάρει το οκ από το πρόγραμμα ότι ικανοποιήθηκε διαφορετικά τερματίζει το πρόγραμμα. Έτσι στο παράδειγμα με την αδυναμία δημιουργίας αρχείου και ταυτόχρονα την μη διεκπεραίωση της εξαίρεσης , χωρίς να στείλει η εφαρμογή μας το καθάρισμα του σφάλματος, έχουμε τερματισμό αντικανονικό.

George:

--- Παράθεση από: alkisg στις 29 Μαρ 2020, 12:04:56 μμ ---
Παρ' όλα αυτά προχωράμε παρακάτω και δεχόμαστε ότι έχουμε "μια κάποια ακρίβεια", και πάμε να τρέξουμε το παράδειγμά σου. Εκεί λοιπόν θα γίνει "underflow" στην τελική x <- x/2, το οποίο το runtime-system μπορεί να το πιάσει, και επομένως θα έχουμε λάθος χρόνου εκτέλεσης.


--- Τέλος παράθεσης ---

Δηλαδή αυτό είναι λάθος χρόνου εκτέλεσης

    i <- 0
   x <- 1
   ΟΣΟ ( x > 0) ΕΠΑΝΑΛΑΒΕ
       i <- i + 1
       x <- x/2
   ΤΕΛΟΣ_ΕΠΑΝΑΛΗΨΗΣ

Ενώ αυτό είναι λογικό λάθος;
   i <- 0
   x <- 1
  ΟΣΟ ( x > 0) ΕΠΑΝΑΛΑΒΕ
       i <- x + 2
      Γράψε i
   ΤΕΛΟΣ_ΕΠΑΝΑΛΗΨΗΣ




Πλοήγηση

[0] Λίστα μηνυμάτων

[#] Επόμενη σελίδα

Μετάβαση στην πλήρη έκδοση