Τι σημαίνει όταν ο κώδικας είναι «εύλογος»;

Έχετε ακούσει πιθανώς την έκφραση «εύκολο να το σκεφτείτε» αρκετές φορές για να κάνετε τα αυτιά σας να αιμορραγούν.

Την πρώτη φορά που άκουσα αυτήν την έκφραση, δεν είχα ιδέα τι εννοούσε το άτομο.

Σημαίνει λειτουργίες που είναι εύκολα κατανοητές;

Σημαίνει λειτουργίες που λειτουργούν σωστά;

Σημαίνει συναρτήσεις που είναι εύκολο να αναλυθούν;

Μετά από λίγο, είχα ακούσει «εύκολο να το σκεφτώ» σε τόσα πολλά πλαίσια που σκέφτηκα ότι ήταν απλώς ένα άλλο ημι-χωρίς νόημα προγραμματιστή.

… Αλλά είναι πραγματικά χωρίς νόημα;

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

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

Κατανόηση της συμπεριφοράς του προγράμματος

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

Για παράδειγμα, πάρτε το παρακάτω κομμάτι κώδικα. Αυτό θα πολλαπλασιάσει μια σειρά αριθμών με 3.

Πώς μπορούμε να ελέγξουμε ότι λειτουργεί όπως προορίζεται; Ένας λογικός τρόπος είναι να περάσετε μια δέσμη συστοιχιών ως είσοδος και να διασφαλίσετε ότι επιστρέφει πάντα τον πίνακα με κάθε στοιχείο πολλαπλασιασμένο επί 3.

Φαίνεται καλό μέχρι τώρα. Έχουμε δοκιμάσει ότι η λειτουργία κάνει ό, τι θέλουμε να κάνει.

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

Είναι αυτό που θέλαμε; Τι γίνεται αν χρειαζόμαστε αναφορές τόσο στον αρχικό πίνακα όσο και στον πίνακα που προκύπτει; Πολύ κακό, υποθέτω.

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

Ωχ. Φαίνεται ότι όταν περάσαμε τον πίνακα [1, 2, 3] στη συνάρτηση την πρώτη φορά, επέστρεψε [3, 6, 9] , αλλά αργότερα επέστρεψε [49, 98, 147] . Αυτά είναι πολύ διαφορετικά αποτελέσματα.

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

Έικ. Δεν φαίνεται πλέον τόσο υπέροχο. Ας σκάψουμε λίγο πιο βαθιά.

Μέχρι στιγμής, έχουμε δοκιμάσει τέλειες εισόδους πίνακα. Τώρα, τι γίνεται αν το κάνουμε αυτό:

Τι στον κοσμο?!?

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

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

Τώρα, ας δούμε μια ελαφρώς διαφορετική λειτουργία multiplyByThree :

Όπως ακριβώς παραπάνω, μπορούμε να δοκιμάσουμε την ορθότητα.

Φαίνεται καλό μέχρι τώρα.

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

Οχι. Η αρχική συστοιχία είναι ανέπαφη!

Επιστρέφει την ίδια έξοδο για μια δεδομένη είσοδο;

Ναι! Δεδομένου ότι η μεταβλητή πολλαπλασιαστή βρίσκεται τώρα εντός του πεδίου της συνάρτησης, ακόμη και αν δηλώσουμε μια διπλή πολλαπλασιαστή μεταβλητή στο καθολικό πεδίο, δεν θα επηρεάσει το αποτέλεσμα.

Επιστρέφει το ίδιο πράγμα εάν περάσουμε πολλά διαφορετικά επιχειρήματα;

Ναι! Τώρα η λειτουργία συμπεριφέρεται πιο προβλέψιμα - είτε επιστρέφει ένα σφάλμα είτε έναν νέο πίνακα που προκύπτει.

Σε αυτό το σημείο, πόσο σίγουροι είμαστε ότι αυτή η λειτουργία κάνει ακριβώς αυτό που θέλουμε να κάνει; Καλύψαμε όλες τις άκρες; Ας δοκιμάσουμε μερικά ακόμη:

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

Ας προσπαθήσουμε να το διορθώσουμε προσθέτοντας έναν άλλο έλεγχο στον έλεγχο βρόχου για μη έγκυρα στοιχεία πίνακα:

Με αυτήν τη νέα λειτουργία, γιατί να μην δοκιμάσετε ξανά αυτές τις δύο θήκες:

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

Τέλος, ένας ορισμός

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

  1. Δεν έχει ανεπιθύμητες παρενέργειες
  2. Δεν βασίζεται ή επηρεάζει την εξωτερική κατάσταση
  3. Λαμβάνοντας υπόψη το ίδιο επιχείρημα, θα επιστρέφει πάντα την ίδια αντίστοιχη έξοδο (επίσης γνωστή ως "διαφάνεια αναφοράς").

Τρόποι που μπορούμε να εγγυηθούμε αυτές τις ιδιότητες

Υπάρχουν πολλοί διαφορετικοί τρόποι με τους οποίους μπορούμε να εγγυηθούμε ότι ο κώδικας μας είναι εύλογος. Ας ρίξουμε μια ματιά σε μερικά:

Δοκιμές μονάδας

Πρώτον, μπορούμε να γράψουμε μονάδες δοκιμών για να απομονώσουμε κομμάτια κώδικα και να επαληθεύσουμε ότι λειτουργούν όπως προορίζεται:

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

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

Τύποι

Εκτός από τις δοκιμές, ενδέχεται επίσης να χρησιμοποιήσουμε τύπους για να διευκολύνουμε τη λογική σχετικά με τον κώδικα. Για παράδειγμα, εάν χρησιμοποιούσαμε ένα στατικό τύπο ελεγκτή για JavaScript όπως το Flow, θα μπορούσαμε να διασφαλίσουμε ότι ο πίνακας εισαγωγής είναι πάντα ένας πίνακας αριθμών:

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

Αμετάβλητο

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

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

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

Ως μια διασκεδαστική εξερεύνηση, θα χρησιμοποιήσω το Elm, μια τυποποιημένη λειτουργική γλώσσα προγραμματισμού, για να δείξω πώς το αμετάβλητο μας βοηθά:

Αυτό το μικρό απόσπασμα κάνει το ίδιο πράγμα με το JavaScript πολλαπλασιάζεται με τη λειτουργία ByThree από πριν, εκτός από το ότι είναι τώρα στο Elm. Δεδομένου ότι το Elm είναι μια πληκτρολογημένη γλώσσα, θα δείτε στη γραμμή 6 ότι ορίζουμε τους τύπους εισόδου και εξόδου για τη συνάρτηση multiplyByThree καθώς και οι δύο είναι μια λίστα αριθμών. Η ίδια η λειτουργία χρησιμοποιεί τη βασική λειτουργία χάρτη για τη δημιουργία του πίνακα που προκύπτει.

Τώρα που ορίσαμε τη λειτουργία μας στο Elm, ας κάνουμε τον τελευταίο γύρο των ίδιων δοκιμών που κάναμε για την προηγούμενη λειτουργία multiplyByThree :

Όπως μπορείτε να δείτε, το αποτέλεσμα είναι αυτό που περιμέναμε και το originalArray δεν έχει μεταβληθεί.

Τώρα, ας ρίξουμε το Elm για ένα τέχνασμα και δοκιμάστε να μεταλλάξετε τον πολλαπλασιαστή:

Αχα! Η Elm σας εμποδίζει να το κάνετε αυτό. Ρίχνει ένα πολύ φιλικό λάθος.

Τι γίνεται αν επρόκειτο να περάσουμε μια συμβολοσειρά ως όρισμα, αντί για έναν πίνακα αριθμών;

Φαίνεται ότι η Elm το έπιασε επίσης. Επειδή δηλώσαμε το όρισμα ως Λίστα αριθμών, δεν μπορούμε να περάσουμε τίποτα παρά μια Λίστα αριθμών ακόμα κι αν προσπαθήσαμε!

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

Τώρα είναι η σειρά σας να σκεφτείτε τον κωδικό σας

Σας προκαλώ να αφιερώσετε μια στιγμή την επόμενη φορά που θα ακούσετε κάποιον να λέει, "Το XYZ το καθιστά εύκολο να σκεφτείτε τον κώδικα" ή "Το ABC κάνει είναι δύσκολο να σκεφτείτε τον κώδικα". Αντικαταστήστε αυτό το φανταχτερό λέξη-κλειδί με τις ιδιότητες που αναφέρονται παραπάνω και προσπαθήστε να καταλάβετε τι σημαίνει αυτό το άτομο. Ποιες ιδιότητες έχει το κομμάτι κώδικα που το καθιστά εύκολο να σκεφτεί κανείς;

Προσωπικά, κάνοντας αυτή την άσκηση με βοήθησε να σκεφτώ κριτικά τον κώδικα και, με τη σειρά του, με ώθησε να σκεφτώ πώς να γράψω προγράμματα που είναι πιο εύκολο να το σκεφτώ. Ελπίζω να κάνει το ίδιο και για εσάς!

Θα ήθελα πολύ να ακούσω τις σκέψεις σας για άλλες ιδιότητες που μπορεί να είχα χάσει που νομίζετε ότι είναι σημαντικές. Αφήστε τα σχόλιά σας στα σχόλια!