Πώς να μην σκοτώσετε ένα έργο με κακές πρακτικές κωδικοποίησης;
Bartosz Slysz
Software Engineer
Πολλοί προγραμματιστές που ξεκινούν την καριέρα τους θεωρούν ότι το θέμα της ονομασίας μεταβλητών, συναρτήσεων, αρχείων και άλλων στοιχείων δεν είναι πολύ σημαντικό. Ως αποτέλεσμα, η λογική του σχεδιασμού τους είναι συχνά σωστή - οι αλγόριθμοι εκτελούνται γρήγορα και παράγουν το επιθυμητό αποτέλεσμα, ενώ μπορεί να είναι μόλις και μετά βίας αναγνώσιμοι. Σε αυτό το άρθρο, θα προσπαθήσω εν συντομία να περιγράψω από τι πρέπει να καθοδηγούμαστε κατά την ονοματοδοσία διαφόρων στοιχείων κώδικα και πώς να μην περάσουμε από το ένα άκρο στο άλλο.
Γιατί η παραμέληση του σταδίου της ονοματοδοσίας θα παρατείνει (σε ορισμένες περιπτώσεις - σε τεράστιο βαθμό) την ανάπτυξη του έργου σας;
Ας υποθέσουμε ότι εσείς και ο ομάδα καταλαμβάνουν το κωδικός από άλλους προγραμματιστές. Το έργο you inherit αναπτύχθηκε χωρίς καμία αγάπη - δούλευε μια χαρά, αλλά κάθε στοιχείο του θα μπορούσε να είχε γραφτεί με πολύ καλύτερο τρόπο.
Όταν πρόκειται για την αρχιτεκτονική, στην περίπτωση της κληρονομικότητας κώδικα, σχεδόν πάντα προκαλεί μίσος και θυμό από τους προγραμματιστές που την απέκτησαν. Μερικές φορές αυτό οφείλεται στη χρήση τεχνολογιών που πεθαίνουν (ή έχουν εκλείψει), μερικές φορές στον λανθασμένο τρόπο σκέψης για την εφαρμογή στην αρχή της ανάπτυξης και μερικές φορές απλώς στην έλλειψη γνώσεων του υπεύθυνου προγραμματιστή.
Σε κάθε περίπτωση, καθώς περνάει ο χρόνος του έργου, είναι δυνατόν να φτάσουμε σε ένα σημείο όπου οι προγραμματιστές είναι έξαλλοι με τις αρχιτεκτονικές και τις τεχνολογίες. Άλλωστε, κάθε εφαρμογή χρειάζεται μετά από κάποιο χρονικό διάστημα την επανεγγραφή κάποιων τμημάτων ή απλώς αλλαγές σε συγκεκριμένα τμήματα - είναι φυσικό. Αλλά το πρόβλημα που θα κάνει τα μαλλιά των προγραμματιστών να γκριζάρουν είναι η δυσκολία στην ανάγνωση και κατανόηση του κώδικα που κληρονόμησαν.
Ειδικά σε ακραίες περιπτώσεις, όταν οι μεταβλητές ονομάζονται με απλά, ανούσια γράμματα και οι συναρτήσεις αποτελούν ένα ξαφνικό κύμα δημιουργικότητας, που δεν συνάδει με κανέναν τρόπο με την υπόλοιπη εφαρμογή, οι προγραμματιστές σας μπορεί να τρελαθούν. Σε μια τέτοια περίπτωση, οποιαδήποτε ανάλυση κώδικα που θα μπορούσε να εκτελεστεί γρήγορα και αποτελεσματικά με σωστή ονομασία απαιτεί πρόσθετη ανάλυση των αλγορίθμων που είναι υπεύθυνοι για την παραγωγή του αποτελέσματος της συνάρτησης, για παράδειγμα. Και μια τέτοια ανάλυση, αν και δυσδιάκριτη - σπαταλά τεράστιο χρόνο.
Η υλοποίηση νέων λειτουργιών κατά μήκος διαφόρων τμημάτων της εφαρμογής σημαίνει ότι περνάτε τον εφιάλτη της ανάλυσής της, μετά από κάποιο χρονικό διάστημα πρέπει να επιστρέψετε στον κώδικα και να τον αναλύσετε ξανά, επειδή οι προθέσεις του δεν είναι σαφείς και ο προηγούμενος χρόνος που δαπανήθηκε για να κατανοήσετε τη λειτουργία του ήταν χαμένος, επειδή δεν θυμάστε πλέον ποιος ήταν ο σκοπός του.
Και έτσι, μας παρασύρει ένας ανεμοστρόβιλος αταξίας που κυριαρχεί στην εφαρμογή και καταναλώνει σιγά-σιγά κάθε συμμετέχοντα στην ανάπτυξή της. Οι προγραμματιστές μισούν το έργο, οι διαχειριστές του έργου μισούν να εξηγούν γιατί ο χρόνος ανάπτυξής του αρχίζει να αυξάνεται συνεχώς, και ο πελάτης χάνει την εμπιστοσύνη του και θυμώνει επειδή τίποτα δεν πάει σύμφωνα με το σχέδιο.
Πώς να το αποφύγετε;
Ας το παραδεχτούμε - ορισμένα πράγματα δεν μπορούν να παραλειφθούν. Αν έχουμε επιλέξει ορισμένες τεχνολογίες στην αρχή του έργου, πρέπει να γνωρίζουμε ότι με τον καιρό είτε θα σταματήσουν να υποστηρίζονται είτε όλο και λιγότεροι προγραμματιστές θα γνωρίζουν άπταιστα τεχνολογίες μερικών ετών που σιγά σιγά παρωθούνται. Ορισμένες βιβλιοθήκες στις ενημερώσεις τους απαιτούν περισσότερο ή λιγότερο εμπεριστατωμένες αλλαγές στον κώδικα, οι οποίες συχνά συνεπάγονται μια δίνη εξαρτήσεων στην οποία μπορεί να κολλήσετε ακόμη περισσότερο.
Από την άλλη πλευρά, δεν πρόκειται για ένα τόσο μαύρο σενάριο- φυσικά, οι τεχνολογίες γίνονται παλαιότερες, αλλά ο παράγοντας που σίγουρα επιβραδύνει το χρόνο ανάπτυξης των έργων που τις περιλαμβάνουν είναι σε μεγάλο βαθμό ο άσχημος κώδικας. Και φυσικά, πρέπει να αναφέρουμε εδώ το βιβλίο του Robert C. Martin - πρόκειται για μια βίβλο για προγραμματιστές, όπου ο συγγραφέας παρουσιάζει πολλές καλές πρακτικές και αρχές που πρέπει να ακολουθούνται για τη δημιουργία κώδικα που επιδιώκει την τελειότητα.
Το βασικό πράγμα κατά την ονομασία των μεταβλητών είναι να αποδίδεται με σαφήνεια και απλότητα η πρόθεσή τους. Ακούγεται αρκετά απλό, αλλά μερικές φορές παραμελείται ή αγνοείται από πολλούς ανθρώπους. Ένα καλό όνομα θα προσδιορίζει τι ακριβώς υποτίθεται ότι αποθηκεύει η μεταβλητή ή τι υποτίθεται ότι κάνει η συνάρτηση - δεν μπορεί να ονομάζεται υπερβολικά γενικά, αλλά από την άλλη πλευρά, δεν μπορεί να γίνει μια μακρόσυρτη βραδυφλεγής που και μόνο η ανάγνωσή της προκαλεί αρκετή πρόκληση για τον εγκέφαλο. Μετά από κάποιο χρονικό διάστημα με κώδικα καλής ποιότητας, βιώνουμε το φαινόμενο της εμβύθισης, όπου είμαστε σε θέση να οργανώσουμε υποσυνείδητα την ονομασία και τη διαβίβαση δεδομένων στη συνάρτηση με τέτοιο τρόπο ώστε το όλο πράγμα να μην αφήνει ψευδαισθήσεις ως προς το ποια πρόθεση το οδηγεί και ποιο είναι το αναμενόμενο αποτέλεσμα της κλήσης του.
Ένα άλλο πράγμα που μπορεί να βρεθεί στο JavaScript, μεταξύ άλλων, είναι μια προσπάθεια υπερ-βελτιστοποίησης του κώδικα, η οποία σε πολλές περιπτώσεις τον καθιστά δυσανάγνωστο. Είναι φυσιολογικό ότι ορισμένοι αλγόριθμοι απαιτούν ιδιαίτερη προσοχή, η οποία συχνά αντανακλά το γεγονός ότι η πρόθεση του κώδικα μπορεί να είναι λίγο πιο περίπλοκη. Παρ' όλα αυτά, οι περιπτώσεις στις οποίες χρειαζόμαστε υπερβολική βελτιστοποίηση είναι εξαιρετικά σπάνιες, ή τουλάχιστον αυτές στις οποίες ο κώδικάς μας είναι βρώμικος. Είναι σημαντικό να θυμόμαστε ότι πολλές βελτιστοποιήσεις που σχετίζονται με τη γλώσσα λαμβάνουν χώρα σε ελαφρώς χαμηλότερο επίπεδο αφαίρεσης- για παράδειγμα, η μηχανή V8 μπορεί, με αρκετές επαναλήψεις, να επιταχύνει σημαντικά τους βρόχους. Αυτό που πρέπει να τονιστεί είναι το γεγονός ότι ζούμε στον 21ο αιώνα και δεν γράφουμε προγράμματα για την αποστολή Apollo 13. Έχουμε πολύ περισσότερα περιθώρια ελιγμών στο θέμα των πόρων - είναι εκεί για να χρησιμοποιηθούν (κατά προτίμηση με λογικό τρόπο :>).
Μερικές φορές το σπάσιμο του κώδικα σε μέρη δίνει πραγματικά πολλά. Όταν οι λειτουργίες σχηματίζουν μια αλυσίδα της οποίας ο σκοπός είναι η εκτέλεση ενεργειών που είναι υπεύθυνες για μια συγκεκριμένη τροποποίηση των δεδομένων - είναι εύκολο να χαθούμε. Επομένως, με έναν απλό τρόπο, αντί να τα κάνετε όλα σε μια αλυσίδα, μπορείτε να αναλύσετε τα επιμέρους τμήματα του κώδικα που είναι υπεύθυνα για ένα συγκεκριμένο πράγμα σε επιμέρους στοιχεία. Αυτό όχι μόνο θα καταστήσει σαφή την πρόθεση των επιμέρους λειτουργιών, αλλά θα σας επιτρέψει επίσης να δοκιμάσετε τμήματα κώδικα που είναι υπεύθυνα για ένα μόνο πράγμα και μπορούν εύκολα να επαναχρησιμοποιηθούν.
Μερικά πρακτικά παραδείγματα
Νομίζω ότι η πιο ακριβής αναπαράσταση ορισμένων από τις παραπάνω δηλώσεις θα είναι να δείξουμε πώς λειτουργούν στην πράξη - σε αυτή την παράγραφο, θα προσπαθήσω να περιγράψω ορισμένες κακές πρακτικές κώδικα που μπορούν λίγο πολύ να μετατραπούν σε καλές. Θα επισημάνω τι διαταράσσει την αναγνωσιμότητα του κώδικα σε ορισμένες στιγμές και πώς να το αποτρέψετε.
Η πληγή των μεταβλητών με ένα γράμμα
Μια τρομερή πρακτική που δυστυχώς είναι αρκετά διαδεδομένη, ακόμη και στα πανεπιστήμια, είναι η ονομασία των μεταβλητών με ένα μόνο γράμμα. Είναι δύσκολο να μη συμφωνήσουμε, ότι μερικές φορές είναι μια αρκετά βολική λύση - αποφεύγουμε την περιττή σκέψη για το πώς να καθορίσουμε τον σκοπό μιας μεταβλητής και αντί να χρησιμοποιούμε πολλούς ή περισσότερους χαρακτήρες για να την ονομάσουμε, χρησιμοποιούμε μόνο ένα γράμμα - π.χ. i, j, k.
Παραδόξως, ορισμένοι ορισμοί αυτών των μεταβλητών είναι προικισμένοι με ένα πολύ μεγαλύτερο σχόλιο, το οποίο καθορίζει τι είχε κατά νου ο συγγραφέας.
Ένα καλό παράδειγμα εδώ θα ήταν η αναπαράσταση της επανάληψης πάνω σε έναν δισδιάστατο πίνακα που περιέχει τις αντίστοιχες τιμές στο σημείο τομής της στήλης και της γραμμής.
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
// pretty bad
for (let i = 0; i < array[i]; i++) {
for (let j = 0; j < array[i][j]; j++) {
// εδώ είναι το περιεχόμενο, αλλά κάθε φορά που χρησιμοποιούνται τα i και j πρέπει να πάω πίσω και να αναλύσω για τι χρησιμοποιούνται
}
}
// εξακολουθεί να είναι κακό αλλά αστείο
let i; // row
let j; // στήλη
for (i = 0; i < array[i]; i++) {
for (j = 0; j < array[i][j]; j++) {
// εδώ είναι το περιεχόμενο, αλλά κάθε φορά που χρησιμοποιούνται τα i και j πρέπει να πάω πίσω και να ελέγξω τα σχόλια για τι χρησιμοποιούνται
}
}
// πολύ καλύτερα
const rowCount = array.length,
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = array[rowIndex],
const columnCount = row.length,
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
const column = row[columnIndex],
// έχει κανείς αμφιβολίες για το τι είναι τι;
}
}
Ύπουλη υπερ-βελτιστοποίηση
Μια ωραία μέρα, έπεσα πάνω σε έναν εξαιρετικά εξελιγμένο κώδικα γραμμένο από έναν μηχανικός λογισμικού. Αυτός ο μηχανικός είχε καταλάβει ότι η αποστολή δικαιωμάτων χρήστη ως συμβολοσειρές που προσδιορίζουν συγκεκριμένες ενέργειες θα μπορούσε να βελτιστοποιηθεί σε μεγάλο βαθμό χρησιμοποιώντας μερικά τεχνάσματα σε επίπεδο bit.
Πιθανώς μια τέτοια λύση θα ήταν εντάξει αν ο στόχος ήταν ο Commodore 64, αλλά ο σκοπός αυτού του κώδικα ήταν μια απλή διαδικτυακή εφαρμογή, γραμμένη σε JS. Ήρθε η ώρα να ξεπεράσουμε αυτή την ιδιορρυθμία: Ας υποθέσουμε ότι ένας χρήστης έχει μόνο τέσσερις επιλογές σε ολόκληρο το σύστημα για την τροποποίηση περιεχομένου: δημιουργία, ανάγνωση, ενημέρωση, διαγραφή. Είναι αρκετά φυσικό να στέλνουμε αυτά τα δικαιώματα είτε σε μορφή JSON ως κλειδιά ενός αντικειμένου με καταστάσεις είτε σε έναν πίνακα.
Ωστόσο, ο έξυπνος μηχανικός μας παρατήρησε ότι ο αριθμός τέσσερα είναι μια μαγική τιμή στη δυαδική παρουσίαση και το υπολόγισε ως εξής:
Ολόκληρος ο πίνακας των δυνατοτήτων έχει 16 γραμμές, έχω παραθέσει μόνο 4 για να σας δώσω την ιδέα της δημιουργίας αυτών των δικαιωμάτων. Η ανάγνωση των δικαιωμάτων έχει ως εξής:
Αυτό που βλέπετε παραπάνω δεν είναι Κώδικας WebAssembly. Δεν θέλω να παρεξηγηθώ εδώ - τέτοιες βελτιστοποιήσεις είναι κάτι φυσιολογικό για συστήματα όπου ορισμένα πράγματα πρέπει να καταλαμβάνουν πολύ λίγο χρόνο ή μνήμη (ή και τα δύο). Οι εφαρμογές ιστού, από την άλλη πλευρά, σίγουρα δεν είναι ένα μέρος όπου τέτοιες υπερ-βελτιστοποιήσεις έχουν απόλυτο νόημα. Δεν θέλω να γενικεύσω, αλλά στη δουλειά των προγραμματιστών front-end πιο σύνθετες λειτουργίες που φτάνουν στο επίπεδο της αφαίρεσης bit σπάνια εκτελούνται.
Απλώς δεν είναι ευανάγνωστος και ένας προγραμματιστής που μπορεί να κάνει ανάλυση ενός τέτοιου κώδικα σίγουρα θα αναρωτηθεί ποια αόρατα πλεονεκτήματα έχει αυτή η λύση και τι μπορεί να καταστραφεί όταν η ομάδα ανάπτυξης θέλει να το ξαναγράψει σε μια πιο λογική λύση.
Επιπλέον - υποψιάζομαι ότι η αποστολή των δικαιωμάτων ως ένα συνηθισμένο αντικείμενο θα επέτρεπε σε έναν προγραμματιστή να διαβάσει την πρόθεση σε 1-2 δευτερόλεπτα, ενώ η ανάλυση όλου αυτού του πράγματος από την αρχή θα πάρει τουλάχιστον μερικά λεπτά. Θα υπάρχουν αρκετοί προγραμματιστές στο έργο, ο καθένας από αυτούς θα πρέπει να συναντήσει αυτό το κομμάτι κώδικα - θα πρέπει να το αναλύσουν αρκετές φορές, επειδή μετά από κάποιο χρονικό διάστημα θα ξεχάσουν τι μαγικό συμβαίνει εκεί. Αξίζει τον κόπο να σώσετε αυτά τα λίγα bytes; Κατά τη γνώμη μου, όχι.
Διαίρει και βασίλευε
Ανάπτυξη ιστοσελίδων αυξάνεται με ταχείς ρυθμούς και δεν υπάρχουν ενδείξεις ότι κάτι θα αλλάξει σύντομα από αυτή την άποψη. Πρέπει να παραδεχτούμε ότι η ευθύνη των front-end προγραμματιστών αυξήθηκε πρόσφατα σημαντικά - ανέλαβαν το μέρος της λογικής που είναι υπεύθυνο για την παρουσίαση των δεδομένων στη διεπαφή χρήστη.
Μερικές φορές η λογική αυτή είναι απλή και τα αντικείμενα που παρέχονται από το API έχουν απλή και ευανάγνωστη δομή. Μερικές φορές, ωστόσο, απαιτούν διαφορετικούς τύπους χαρτογράφησης, ταξινόμησης και άλλες λειτουργίες για την προσαρμογή τους σε διαφορετικές θέσεις στη σελίδα. Και αυτό είναι το σημείο όπου μπορούμε εύκολα να πέσουμε στο βάλτο.
Πολλές φορές, έχω πιάσει τον εαυτό μου να κάνει τα δεδομένα στις λειτουργίες που εκτελούσα σχεδόν αδιάβαστα. Παρά τη σωστή χρήση των μεθόδων συστοιχιών και τη σωστή ονομασία μεταβλητών, οι αλυσίδες των πράξεων σε ορισμένα σημεία σχεδόν έχαναν το πλαίσιο αυτού που ήθελα να πετύχω. Επίσης, ορισμένες από αυτές τις λειτουργίες έπρεπε μερικές φορές να χρησιμοποιηθούν αλλού, και μερικές φορές ήταν παγκόσμιες ή αρκετά περίπλοκες ώστε να απαιτούν τη συγγραφή δοκιμών.
Ξέρω, ξέρω - αυτό δεν είναι κάποιο ασήμαντο κομμάτι κώδικα που απεικονίζει εύκολα αυτό που θέλω να μεταφέρω. Και γνωρίζω επίσης ότι η υπολογιστική πολυπλοκότητα των δύο παραδειγμάτων είναι ελαφρώς διαφορετική, ενώ στο 99% των περιπτώσεων δεν χρειάζεται να ανησυχούμε γι' αυτό. Η διαφορά μεταξύ των αλγορίθμων είναι απλή, καθώς και οι δύο προετοιμάζουν έναν χάρτη τοποθεσιών και ιδιοκτητών συσκευών.
Ο πρώτος προετοιμάζει αυτόν τον χάρτη δύο φορές, ενώ ο δεύτερος τον προετοιμάζει μόνο μία φορά. Και το πιο απλό παράδειγμα που μας δείχνει ότι ο δεύτερος αλγόριθμος είναι πιο φορητός έγκειται στο γεγονός ότι πρέπει να αλλάξουμε τη λογική δημιουργίας αυτού του χάρτη για τον πρώτο και π.χ. να κάνουμε τον αποκλεισμό ορισμένων τοποθεσιών ή άλλα περίεργα πράγματα που ονομάζονται επιχειρησιακή λογική. Στην περίπτωση του δεύτερου αλγορίθμου, τροποποιούμε μόνο τον τρόπο λήψης του χάρτη, ενώ όλες οι υπόλοιπες τροποποιήσεις δεδομένων που συμβαίνουν στις επόμενες γραμμές παραμένουν αμετάβλητες. Στην περίπτωση του πρώτου αλγορίθμου, πρέπει να τροποποιήσουμε κάθε προσπάθεια προετοιμασίας του χάρτη.
Και αυτό είναι μόνο ένα παράδειγμα - στην πράξη, υπάρχουν πολλές τέτοιες περιπτώσεις όπου χρειάζεται να μετασχηματίσουμε ή να αναδιαμορφώσουμε ένα συγκεκριμένο μοντέλο δεδομένων σε ολόκληρη την εφαρμογή.
Ο καλύτερος τρόπος για να αποφύγουμε να συμβαδίζουμε με τις διάφορες επιχειρησιακές αλλαγές είναι να προετοιμάσουμε σφαιρικά εργαλεία που μας επιτρέπουν να εξάγουμε τις πληροφορίες που μας ενδιαφέρουν με έναν αρκετά γενικό τρόπο. Ακόμη και με το κόστος αυτών των 2-3 χιλιοστών του δευτερολέπτου που μπορεί να χάσουμε με το κόστος της αποβελτιστοποίησης.
Περίληψη
Το να είσαι προγραμματιστής είναι ένα επάγγελμα όπως όλα τα άλλα - κάθε μέρα μαθαίνουμε νέα πράγματα, κάνοντας συχνά πολλά λάθη. Το πιο σημαντικό είναι να μαθαίνουμε από αυτά τα λάθη, να γινόμαστε καλύτεροι στο επάγγελμά μας και να μην επαναλαμβάνουμε αυτά τα λάθη στο μέλλον. Δεν μπορείτε να πιστεύετε στο μύθο ότι η δουλειά που κάνουμε θα είναι πάντα άψογη. Μπορείτε, ωστόσο, βασιζόμενοι στις εμπειρίες των άλλων, να μειώσετε ανάλογα τα λάθη.
Ελπίζω ότι η ανάγνωση αυτού του άρθρου θα σας βοηθήσει να αποφύγετε τουλάχιστον μερικά από τα κακές πρακτικές κωδικοποίησης που έχω βιώσει στη δουλειά μου. Σε περίπτωση οποιωνδήποτε ερωτήσεων σχετικά με τις βέλτιστες πρακτικές κώδικα, μπορείτε να απευθυνθείτε Πλήρωμα The Codest για να συμβουλευτείτε τις αμφιβολίες σας.