Μια γρήγορη εισαγωγή στο Refactoring για αρχάριους
Marta Swiatkowska
Junior Software Engineer
Ίσως γράφω για κάτι προφανές για πολλούς, αλλά ίσως όχι για όλους. Το Refactoring είναι, νομίζω, ένα περίπλοκο θέμα επειδή περιλαμβάνει την αλλαγή του κώδικα χωρίς να επηρεάζει τη λειτουργία του.
Ως εκ τούτου, είναι ακατανόητο για ορισμένους ότι αναδόμηση είναι στην πραγματικότητα ένας τομέας του προγραμματισμού και αποτελεί επίσης ένα πολύ σημαντικό μέρος της εργασίας του προγραμματιστή. Ο κώδικας εξελίσσεται διαρκώς, θα τροποποιείται εφόσον υπάρχει η δυνατότητα προσθήκης νέων λειτουργιών. Ωστόσο, μπορεί να πάρει μια μορφή που δεν επιτρέπει πλέον την αποτελεσματική προσθήκη νέων λειτουργικοτήτων και θα ήταν ευκολότερο να ξαναγραφτεί ολόκληρο το πρόγραμμα.
Τι είναι το refactoring;
Συνήθως, η απάντηση που ακούτε είναι ότι αλλάζει η δομή του κώδικα εφαρμόζοντας μια σειρά μετασχηματισμών αναδόμησης χωρίς να επηρεάζεται η παρατηρήσιμη συμπεριφορά του κώδικα. Αυτό είναι αλήθεια. Πρόσφατα, συνάντησα επίσης έναν ορισμό από τον Martin Fowler στο βιβλίο του "Βελτίωση του σχεδιασμού του υφιστάμενου κώδικα" όπου περιγράφει αναδόμηση ως "μεγάλες αλλαγές με μικρά βήματα". Περιγράφει αναδόμηση ως αλλαγή κώδικα που δεν επηρεάζει τη λειτουργία του, αλλά τονίζει ότι πρέπει να γίνει με μικρά βήματα.
Το βιβλίο υποστηρίζει επίσης ότι αναδόμηση δεν επηρεάζει τη λειτουργία του κώδικα και επισημαίνει ότι δεν έχει καμία επίδραση στην επιτυχία των δοκιμών σε καμία περίπτωση. Περιγράφει βήμα προς βήμα τον τρόπο ασφαλούς εκτέλεσης αναδόμηση. Μου άρεσε το βιβλίο του επειδή περιγράφει απλά κόλπα που μπορούν να χρησιμοποιηθούν στην καθημερινή εργασία.
Γιατί χρειαζόμαστε αναδιαμόρφωση;
Τις περισσότερες φορές, μπορεί να το χρειαστείτε όταν θέλετε να εισαγάγετε μια νέα λειτουργικότητα και ο κώδικας στην τρέχουσα έκδοσή του δεν το επιτρέπει ή θα ήταν πιο δύσκολο χωρίς αλλαγές στον κώδικα. Επίσης, είναι χρήσιμο σε περιπτώσεις που η προσθήκη περισσότερων λειτουργιών είναι ασύμφορη από άποψη χρόνου, δηλαδή θα ήταν ταχύτερο να ξαναγράψετε τον κώδικα από την αρχή. Νομίζω ότι μερικές φορές ξεχνιέται ότι αναδόμηση μπορεί να κάνει τον κώδικα καθαρότερο και πιο ευανάγνωστο. Ο Martin γράφει στο βιβλίο του πώς εκτελεί αναδιαμόρφωση όταν αισθάνεται δυσάρεστες οσμές στον κώδικα και, όπως το θέτει, "αφήνει πάντα χώρο για το καλύτερο". Και εδώ με εξέπληξε βλέποντας την αναδιαμόρφωση ως στοιχείο της καθημερινής εργασίας στον κώδικα. Για μένα, οι κώδικες είναι συχνά ακατανόητοι, η ανάγνωσή τους είναι μια μικρή εμπειρία, καθώς ο κώδικας είναι συχνά μη διαισθητικός.
Το χαρακτηριστικό γνώρισμα ενός καλά σχεδιασμένου προγράμματος είναι η αρθρωτότητα, χάρη στο οποίο αρκεί να γνωρίζετε μόνο ένα μικρό μέρος του κώδικα για να εισαγάγετε τις περισσότερες τροποποιήσεις. Η αρθρωτότητα διευκολύνει επίσης τους νέους ανθρώπους να εισέλθουν και να αρχίσουν να εργάζονται πιο αποτελεσματικά. Για να επιτευχθεί αυτή η αρθρωτότητα, τα συναφή στοιχεία του προγράμματος πρέπει να ομαδοποιούνται μεταξύ τους, με τις συνδέσεις να είναι κατανοητές και να εντοπίζονται εύκολα. Δεν υπάρχει ένας και μοναδικός κανόνας για το πώς μπορεί να γίνει αυτό. Καθώς γνωρίζετε και κατανοείτε όλο και καλύτερα τον τρόπο με τον οποίο υποτίθεται ότι λειτουργεί ο κώδικας, μπορείτε να ομαδοποιείτε τα στοιχεία, αλλά μερικές φορές πρέπει επίσης να δοκιμάζετε και να ελέγχετε.
Ένας από τους κανόνες της αναδόμησης στο YAGNI, είναι ακρωνύμιο για το "You Aren't Gonna't Need It" και προέρχεται από το Προγραμματισμός eXtreme (XP) χρησιμοποιείται κυρίως σε Ευέλικτηανάπτυξη λογισμικού ομάδες. Για να μην πολυλογώ, YAGNI λέει ότι πρέπει να γίνονται μόνο σύγχρονα πράγματα. Αυτό ουσιαστικά σημαίνει ότι ακόμη και αν κάτι μπορεί να χρειαστεί στο μέλλον, δεν πρέπει να γίνει τώρα. Αλλά δεν μπορούμε επίσης να συντρίψουμε περαιτέρω επεκτάσεις και εδώ είναι που η αρθρωτότητα γίνεται σημαντική.
Όταν μιλάμε για αναδόμηση, πρέπει να αναφερθεί ένα από τα βασικότερα στοιχεία, δηλαδή οι δοκιμές. Στο αναδόμηση, πρέπει να γνωρίζουμε ότι ο κώδικας εξακολουθεί να λειτουργεί, διότι αναδόμηση δεν αλλάζει τον τρόπο λειτουργίας του, αλλά τη δομή του, οπότε όλες οι δοκιμές πρέπει να περάσουν. Είναι καλύτερο να εκτελούμε δοκιμές για το τμήμα του κώδικα στο οποίο εργαζόμαστε μετά από κάθε μικρό μετασχηματισμό. Μας δίνει μια επιβεβαίωση ότι όλα λειτουργούν όπως πρέπει και συντομεύει τον χρόνο της όλης διαδικασίας. Αυτό είναι που αναφέρει ο Martin στο βιβλίο του - εκτελέστε δοκιμές όσο το δυνατόν συχνότερα, ώστε να μην κάνουμε ένα βήμα πίσω και να μην χάνουμε χρόνο ψάχνοντας για έναν μετασχηματισμό που έσπασε κάτι.
Αναδιαμόρφωση κώδικα χωρίς δοκιμή είναι ένας πόνος και υπάρχει μεγάλη πιθανότητα να πάει κάτι στραβά. Αν είναι δυνατόν, θα ήταν καλύτερο να προσθέσετε τουλάχιστον κάποιες βασικές δοκιμές που θα μας δώσουν μια μικρή διαβεβαίωση ότι ο κώδικας λειτουργεί.
Οι μετασχηματισμοί που αναφέρονται παρακάτω είναι μόνο παραδείγματα, αλλά είναι πραγματικά χρήσιμοι στον καθημερινό προγραμματισμό:
Εξαγωγή συναρτήσεων και μεταβλητών - αν η συνάρτηση είναι πολύ μεγάλη, ελέγξτε αν υπάρχουν δευτερεύουσες συναρτήσεις που θα μπορούσαν να εξαχθούν. Το ίδιο ισχύει και για μεγάλες γραμμές. Αυτοί οι μετασχηματισμοί μπορούν να βοηθήσουν στην εύρεση επαναλήψεων στον κώδικα. Χάρη στις μικρές συναρτήσεις, ο κώδικας γίνεται πιο σαφής και κατανοητός,
Μετονομασία συναρτήσεων και μεταβλητών - η χρήση της σωστής σύμβασης ονοματοδοσίας είναι απαραίτητη για τον καλό προγραμματισμό. Τα ονόματα των μεταβλητών, όταν είναι σωστά επιλεγμένα, μπορούν να πουν πολλά για τον κώδικα,
Ομαδοποίηση των συναρτήσεων σε μια κλάση - αυτή η αλλαγή είναι χρήσιμη όταν δύο κλάσεις εκτελούν παρόμοιες λειτουργίες, καθώς μπορεί να μειώσει το μήκος της κλάσης,
Παράκαμψη της ένθετης δήλωσης - εάν η συνθήκη ελέγχεται για μια ειδική περίπτωση, εκδίδετε μια δήλωση επιστροφής όταν η συνθήκη εμφανίζεται. Αυτοί οι τύποι δοκιμών αναφέρονται συχνά ως ρήτρα φύλαξης. Η αντικατάσταση μιας ένθετης δήλωσης υπό συνθήκη με μια δήλωση εξόδου αλλάζει την έμφαση στον κώδικα. Η κατασκευή if-else αποδίδει ίση βαρύτητα και στις δύο παραλλαγές. Για το άτομο που διαβάζει τον κώδικα, είναι ένα μήνυμα ότι κάθε μία από αυτές είναι εξίσου πιθανή και σημαντική,
Παρουσιάζοντας μια ειδική περίπτωση - αν χρησιμοποιείτε κάποιες συνθήκες στον κώδικά σας πολλές φορές, ίσως αξίζει να δημιουργήσετε μια ξεχωριστή δομή γι' αυτές. Ως αποτέλεσμα, οι περισσότεροι έλεγχοι ειδικών περιπτώσεων μπορούν να αντικατασταθούν με απλές κλήσεις συναρτήσεων. Συχνά η κοινή τιμή που απαιτεί ειδική επεξεργασία είναι το null. Ως εκ τούτου, αυτό το μοτίβο ονομάζεται συχνά αντικείμενο μηδέν. Ωστόσο, αυτή η προσέγγιση μπορεί να χρησιμοποιηθεί σε οποιαδήποτε ειδική περίπτωση,
Αντικατάσταση του πολυμορφισμού εντολών υπό όρους.
Παράδειγμα
Αυτό είναι ένα άρθρο σχετικά με αναδόμηση και χρειάζεται ένα παράδειγμα. Θέλω να δείξω ένα απλό δείγμα αναδιαμόρφωσης παρακάτω με τη χρήση του Παράκαμψη της ένθετης δήλωσης και Αντικατάσταση του πολυμορφισμού εντολών υπό όρους. Ας υποθέσουμε ότι έχουμε μια συνάρτηση προγράμματος που επιστρέφει ένα hash με πληροφορίες για το πώς να ποτίζουμε τα φυτά στην πραγματική ζωή. Τέτοιες πληροφορίες θα ήταν πιθανώς στο μοντέλο, αλλά για το συγκεκριμένο παράδειγμα, τις έχουμε στη συνάρτηση.
def watering_info(plant)
result = {}
if plant.is_a? Suculent || plant.is_a? Cactus
result = { water_amount: ", how_to: "From the bottom", watering_duration: "2 weeks" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = { water_amount: "Big amount", how_to: "As you prefer", watering_duration: "5 days" }
elsif plant.is_a? Peperomia
result = { water_amount: "Dicent amount",
how_to: "Δεν τους αρέσει το νερό στα φύλλα",
watering_duration: "1 week" }
else
result = { water_amount: "Dicent amount",
how_to: "Όπως προτιμάτε",
watering_duration: "1 εβδομάδα"
}
end
επιστροφή αποτελέσματος
end
Η ιδέα είναι να αλλάξετε αν επιστρέψετε:
if plant.isa? Suculent || plant.isa? Cactus
result = { wateramount: ", howto: "From the bottom",
Προς
return { water_amount: ", how_to: "From the bottom",watering_duration: "2 weeks" } if plant.is_a? Suculent || plant.is_a? Cactus
Και ούτω καθεξής με τα πάντα, μέχρι να καταλήξουμε σε μια συνάρτηση που μοιάζει με αυτή:
def watering_info(plant)
return result = { wateramount: ", howto: "From the bottom", wateringduration: "2 weeks" } if plant.isa? Suculent || plant.is_a? Cactus
return result = { wateramount: "Big amount", howto: "As you prefer", wateringduration: "5 days" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent amount",
howto: "Δεν τους αρέσει το νερό στα φύλλα",
wateringduration: "1 week" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent amount",
how_to: "Όπως προτιμάτε",
watering_duration: "1 εβδομάδα"
}
end
Στο τέλος, είχαμε ήδη ένα αποτέλεσμα επιστροφής. Και μια καλή συνήθεια είναι να το κάνετε αυτό βήμα προς βήμα και να δοκιμάζετε κάθε αλλαγή. Θα μπορούσατε να αντικαταστήσετε αυτό το μπλοκ if με μια περίπτωση switch και θα φαινόταν αμέσως καλύτερο και δεν θα χρειαζόταν να ελέγχετε όλα τα if κάθε φορά. Θα ήταν κάπως έτσι:
def watering_info(plant)
swich plant.class.to_string
case Suculent, Cactus
{ wateramount: ", howto: "From the bottom", watering_duration: "2 weeks" }
περίπτωση Alocasia, Maranta
{ wateramount: "Big amount", howto: "Όπως προτιμάτε", watering_duration: "5 days" }
case Peperomia
{ water_amount: "Dicent amount",
how_to: "Δεν τους αρέσει το νερό στα φύλλα",
watering_duration: "1 εβδομάδα" }
else
{ water_amount: "Dicent amount",
how_to: "Όπως προτιμάτε",
watering_duration: "1 εβδομάδα" }
end
end
Και στη συνέχεια μπορείτε να εφαρμόσετε το Αντικατάσταση του πολυμορφισμού εντολών υπό όρους. Αυτό γίνεται για να δημιουργήσετε μια κλάση με μια συνάρτηση που επιστρέφει τη σωστή τιμή και τα αλλάζει στις σωστές θέσεις τους.
κλάση Ζουμερό
...
def watering_info()
return { wateramount: ", howto: "From the bottom", watering_duration: "2 weeks" }
end
end
κλάση Κάκτος
...
def watering_info()
return { wateramount: ", howto: "From the bottom", watering_duration: "2 weeks" }
end
end
κλάση Alocasia
...
def watering_info
return { wateramount: "Big amount", howto: "Όπως προτιμάτε", watering_duration: "5 days" }
end
end
κλάση Maranta
...
def watering_info
return { wateramount: "Big amount", howto: "Όπως προτιμάτε", watering_duration: "5 days" }
end
end
κλάση Peperomia
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Δεν τους αρέσει το νερό στα φύλλα",
watering_duration: "1 εβδομάδα" }
end
end
κλάση Plant
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Όπως προτιμάτε",
watering_duration: "1 εβδομάδα" }
end
end
Και στην κύρια συνάρτηση watering_infofunction, ο κώδικας θα μοιάζει ως εξής:
def watering_info(plant)
plant.map(&:watering_info)
end
Φυσικά, αυτή η λειτουργία μπορεί να αφαιρεθεί και να αντικατασταθεί με το περιεχόμενό της. Με αυτό το παράδειγμα, ήθελα να παρουσιάσω τη γενική μοτίβο της αναδόμησης.
Περίληψη
Αναδιαμόρφωση είναι ένα μεγάλο θέμα. Ελπίζω αυτό το άρθρο να αποτέλεσε κίνητρο για να διαβάσετε περισσότερα. Αυτά τα δεξιότητες αναδόμησης να σας βοηθήσει να εντοπίσετε τα σφάλματά σας και να βελτιώσετε το εργαστήριο καθαρού κώδικα. Συνιστώ να διαβάσετε το βιβλίο του Martin (Improving the Design of Existing Code), το οποίο είναι ένα αρκετά βασικό και χρήσιμο σύνολο κανόνων του αναδόμηση. Ο συγγραφέας παρουσιάζει βήμα προς βήμα διάφορους μετασχηματισμούς με πλήρη εξήγηση και κίνητρα και συμβουλές για το πώς να αποφύγετε τα λάθη σε αναδόμηση. Λόγω της ευελιξίας του, είναι ένα ευχάριστο βιβλίο για frontend και προγραμματιστές backend.