Με το πλήθος των δωρεάν πηγών, βιβλίων, διαδικτυακών μαθημάτων προγραμματισμού που είναι διαθέσιμα αυτή τη στιγμή, ο καθένας μπορεί να μάθει να προγραμματίζει. Ωστόσο, εξακολουθεί να υπάρχει ένα ποιοτικό χάσμα μεταξύ της κωδικοποίησης και της μηχανικής λογισμικού. Πρέπει να υπάρχει;
Έγραψα το πρώτο μου "Hello world" πριν από είκοσι χρόνια - αυτή είναι η απάντηση που δίνω αν κάποιος με ρωτήσει πόσο καιρό είμαι προγραμματιστής. Τα τελευταία δέκα χρόνια, απολαμβάνω μια καριέρα που με κάνει να αγγίζω κωδικός σχεδόν κάθε μέρα - αυτή είναι η απάντηση που δίνω αν με ρωτήσουν πόσο καιρό είμαι επαγγελματίας κωδικοποιητής.
Πόσο καιρό είμαι μηχανικός λογισμικού? Θα έλεγα περίπου πέντε χρόνια. Περίμενε, αυτοί οι αριθμοί δεν ταιριάζουν! Τι άλλαξε λοιπόν; Ποιον θα μπορούσα να θεωρήσω μηχανικό λογισμικού και ποιον "απλώς" προγραμματιστή;
Ο ορισμός του μηχανικού λογισμικού
Η κωδικοποίηση είναι σχετικά εύκολη. Δεν είναι πια όλα μνημονικά assembly σε γελοία περιορισμένα συστήματα. Και αν χρησιμοποιείτε κάτι τόσο εκφραστικό και ισχυρό όσο η Ruby, είναι ακόμα πιο εύκολο.
Απλά παίρνετε ένα εισιτήριο, βρίσκετε το σημείο που πρέπει να εισάγετε τον κώδικά σας, υπολογίζετε τη λογική που πρέπει να τοποθετήσετε εκεί και μπουμ - έτοιμο. Αν είστε λίγο πιο προχωρημένοι, βεβαιώνεστε ότι ο κώδικάς σας είναι όμορφος. Χωρίζεται λογικά σε μεθόδους. Έχει αξιοπρεπείς προδιαγραφές που δεν ελέγχουν μόνο το ευτυχές μονοπάτι. Αυτό κάνει ένας καλός προγραμματιστής.
Ένας μηχανικός λογισμικού δεν σκέφτεται πλέον σε μεθόδους και κλάσεις, τουλάχιστον όχι κατά κύριο λόγο. Κατά την εμπειρία μου, ένας μηχανικός λογισμικού σκέφτεται σε ροές. Βλέπει πρώτα και κύρια το βροντερό, ορμητικό ποτάμι δεδομένων και αλληλεπίδρασης που διατρέχει το σύστημα. Σκέφτονται τι πρέπει να κάνουν για να εκτρέψουν ή να αλλάξουν αυτή τη ροή. Ο όμορφος κώδικας, οι λογικές μέθοδοι και οι σπουδαίες προδιαγραφές έρχονται σχεδόν εκ των υστέρων.
Είναι χελώνες σε όλη τη διαδρομή προς τα κάτω
Οι άνθρωποι γενικά σκέφτονται με έναν συγκεκριμένο τρόπο για τις περισσότερες αλληλεπιδράσεις με την πραγματικότητα. Ελλείψει καλύτερου όρου, ας το ονομάσουμε "από πάνω προς τα κάτω" προοπτική. Αν αυτό που δουλεύει ο εγκέφαλός μου είναι να φτιάξω ένα φλιτζάνι τσάι, θα υπολογίσει πρώτα τα γενικά βήματα: πάω στην κουζίνα, βάζω τον βραστήρα, ετοιμάζω το φλιτζάνι, ρίχνω νερό, επιστρέφω στο γραφείο.
Δεν θα βρει πρώτα ποιο φλιτζάνι να χρησιμοποιήσει πρώτα, καθώς στέκομαι αφηρημένος στο γραφείο μου- αυτή η αφηρημάδα θα έρθει αργότερα, καθώς στέκομαι μπροστά στο ντουλάπι. Δεν θα σκεφτεί ότι μπορεί να μας έχει τελειώσει το τσάι (ή, τουλάχιστον, να μας έχει τελειώσει το καλή πράγματα). Είναι ευρύ, αντιδραστικό και επιρρεπές σε σφάλματα. Συνολικά - πολύ ανθρώπινο στη φύση.
Καθώς ο μηχανικός λογισμικού εξετάζει αλλαγές στην κάπως παράξενη ροή δεδομένων, θα το κάνει φυσικά με παρόμοιο τρόπο. Ας εξετάσουμε αυτό το παράδειγμα ιστορίας χρήστη:
Ένας πελάτης παραγγέλνει ένα widget. Κατά την τιμολόγηση της παραγγελίας, πρέπει να ληφθούν υπόψη τα εξής:
- Βασική τιμή widget στην τοποθεσία του χρήστη
- Σχήμα widget (τροποποιητής τιμής)
- Εάν πρόκειται για παραγγελία βιασύνης (τροποποιητής τιμής)
- Εάν η παράδοση της παραγγελίας πραγματοποιείται σε αργία στην περιοχή του χρήστη (τροποποιητής τιμής)
Όλα αυτά μπορεί να φαίνονται επιτηδευμένα (και προφανώς είναι), αλλά δεν απέχουν πολύ από κάποιες πραγματικές ιστορίες χρηστών που είχα την ευχαρίστηση να συντρίψω πρόσφατα.
Τώρα, ας δούμε τη διαδικασία σκέψης που μπορεί να χρησιμοποιήσει ένας μηχανικός λογισμικού για να αντιμετωπίσει αυτό το πρόβλημα:
"Λοιπόν, πρέπει να πάρουμε τον χρήστη και την παραγγελία του. Στη συνέχεια, αρχίζουμε να υπολογίζουμε το συνολικό ποσό. Θα ξεκινήσουμε από το μηδέν. Στη συνέχεια, θα εφαρμόσουμε τον τροποποιητή σχήματος του widget. Στη συνέχεια, τη χρέωση βιασύνης. Μετά βλέπουμε αν είναι αργία, και μπουμ, τελειώσαμε πριν το μεσημεριανό!"
Αχ, η βιασύνη που μπορεί να προκαλέσει μια απλή ιστορία χρήστη. Αλλά ο μηχανικός λογισμικού είναι μόνο άνθρωπος, όχι μια τέλεια πολυνηματική μηχανή, και η παραπάνω συνταγή είναι μια γενική προσέγγιση. Ο μηχανικός συνεχίζει να σκέφτεται βαθύτερα τότε:
"Ο τροποποιητής σχήματος του widget είναι... Ω, αυτό εξαρτάται πάρα πολύ από το widget, έτσι δεν είναι. Και μπορεί να είναι διαφορετικά ανά locale, ακόμα και αν όχι τώρα, τότε στο μέλλον," νομίζουν, που κάηκαν προηγουμένως από τις μεταβαλλόμενες επιχειρηματικές απαιτήσεις, "και η χρέωση βιασύνης μπορεί επίσης να είναι. Και οι αργίες είναι σούπερ τοπικά συγκεκριμένες, επίσης, και οι ζώνες ώρας θα εμπλέκονται! Είχα ένα άρθρο εδώ σχετικά με την αντιμετώπιση των χρόνων σε διαφορετικές ζώνες ώρας στο Rails εδώ... ωωω, αναρωτιέμαι αν η ώρα της παραγγελίας αποθηκεύεται με τη ζώνη στη βάση δεδομένων! Καλύτερα να ελέγξω το σχήμα".
Εντάξει, μηχανικός λογισμικού. Σταμάτα. Υποτίθεται ότι φτιάχνεις ένα φλιτζάνι τσάι, αλλά είσαι αφηρημένος μπροστά από το ντουλάπι και σκέφτεσαι αν το ανθισμένο φλιτζάνι μπορεί να εφαρμοστεί στο πρόβλημα του τσαγιού σου.
Παρασκευάζοντας το τέλειο φλιτζάνι widget
Αλλά αυτό μπορεί εύκολα να συμβεί όταν προσπαθείς να κάνεις κάτι τόσο αφύσικο για τον ανθρώπινο εγκέφαλο όσο το να σκέφτεσαι σε διάφορα βάθη λεπτομέρειας. ταυτόχρονα.
Μετά από μια σύντομη περιήγηση στο ευρύχωρο οπλοστάσιο των συνδέσμων τους σχετικά με το χειρισμό της ζώνης ώρας, ο μηχανικός μας συγκεντρώνεται και αρχίζει να το αναλύει σε πραγματικό κώδικα. Αν δοκιμάσουν την αφελή προσέγγιση, θα μπορούσε να μοιάζει κάπως έτσι:
def calculate_price(user, order)
order.price = 0
order.price = WidgetPrices.find_by(widget_type: order.widget.type).price
order.price = WidgetShapes.find_by(widget_shape: order.widget.shape).modifier
...
end
Και συνέχιζαν και συνέχιζαν, με αυτόν τον απολαυστικό διαδικαστικό τρόπο, μόνο και μόνο για να σταματήσουν κατά πολύ στην πρώτη αναθεώρηση κώδικα. Γιατί αν το σκεφτείτε, είναι απολύτως φυσιολογικό να σκέφτεστε με αυτόν τον τρόπο: πρώτα οι γενικές γραμμές και πολύ αργότερα οι λεπτομέρειες. Στην αρχή δεν νομίζατε καν ότι είχατε βγει από το καλό τσάι, έτσι δεν είναι;
Ο μηχανικός μας, ωστόσο, είναι καλά εκπαιδευμένος και δεν του είναι άγνωστο το Service Object, οπότε να τι αρχίζει να συμβαίνει αντί γι' αυτό:
class BaseOrderService
def self.call(user, order)
new(user, order).call
end
def initialize(user, order)
@user = χρήστης
@order = order
end
def call
puts "[WARN] Εφαρμογή μη προεπιλεγμένης κλήσης για #{self.class.name}!"
user, order
end
end
class WidgetPriceService < BaseOrderService; end
class ShapePriceModifier < BaseOrderService; end
class RushPriceModifier < BaseOrderService; end
class HolidayDeliveryPriceModifier < BaseOrderService; end
class OrderPriceCalculator < BaseOrderService
def call
user, order = WidgetPriceService.call(user, order)
user, order = ShapePriceModifier.call(user, order)
user, order = RushPriceModifier.call(user, order)
user, order = HolidayDeliveryPriceModifier.call(user, order)
user, order
end
end
```
Ωραία! Τώρα μπορούμε να εφαρμόσουμε ένα καλό TDD, να γράψουμε μια περίπτωση δοκιμής γι' αυτό και να συμπληρώσουμε τις κλάσεις μέχρι να μπουν όλα τα κομμάτια στη θέση τους. Και θα είναι και όμορφο, επίσης.
Καθώς και εντελώς αδύνατο να το αιτιολογήσει κανείς.
Εχθρός είναι το κράτος
Βέβαια, όλα αυτά είναι καλά διαχωρισμένα αντικείμενα με ενιαίες αρμοδιότητες. Αλλά εδώ είναι το πρόβλημα: εξακολουθούν να είναι αντικείμενα. Το μοτίβο των αντικειμένων υπηρεσιών με το "προσποιούμαστε βίαια ότι αυτό το αντικείμενο είναι μια συνάρτηση" είναι στην πραγματικότητα ένα δεκανίκι. Δεν υπάρχει τίποτα που να εμποδίζει οποιονδήποτε να καλεί HolidayDeliveryPriceModifier.new(user, order).something_else_entirely
. Τίποτα δεν εμποδίζει τους ανθρώπους να προσθέσουν εσωτερική κατάσταση σε αυτά τα αντικείμενα.
Για να μην αναφέρουμε χρήστης
και παραγγελία
είναι επίσης αντικείμενα, και το να τα πειράξεις είναι τόσο εύκολο όσο το να κάνεις ένα γρήγορο order.save
κάπου σε αυτά τα κατά τα άλλα "καθαρά" λειτουργικά αντικείμενα, αλλάζοντας την υποκείμενη πηγή της αλήθειας, ή αλλιώς μια βάση δεδομένων, κατάσταση. Σε αυτό το επινοημένο παράδειγμα δεν είναι κάτι σημαντικό, αλλά σίγουρα μπορεί να σας γυρίσει μπούμερανγκ αν αυτό το σύστημα αυξηθεί σε πολυπλοκότητα και επεκταθεί σε πρόσθετα, συχνά ασύγχρονα, μέρη.
Ο μηχανικός είχε τη σωστή ιδέα. Και χρησιμοποίησε έναν πολύ φυσικό τρόπο έκφρασης αυτής της ιδέας. Αλλά το να γνωρίζει πώς να εκφράσει αυτή την ιδέα - με έναν όμορφο και εύκολο στη λογική τρόπο - σχεδόν εμποδίστηκε από το υποκείμενο παράδειγμα OOP. Και αν κάποιος που δεν έχει κάνει ακόμα το άλμα στην έκφραση των σκέψεών του ως εκτροπές της ροής δεδομένων προσπαθήσει να αλλάξει λιγότερο επιδέξια τον υποκείμενο κώδικα, θα συμβούν άσχημα πράγματα.
Να γίνεις λειτουργικά καθαρός
Μακάρι να υπήρχε ένα παράδειγμα όπου η έκφραση των ιδεών σας με όρους ροής δεδομένων να ήταν όχι μόνο εύκολη, αλλά και αναγκαία. Αν η συλλογιστική μπορούσε να γίνει απλή, χωρίς δυνατότητα εισαγωγής ανεπιθύμητων παρενεργειών. Αν τα δεδομένα μπορούσαν να είναι αμετάβλητα, όπως ακριβώς το ανθισμένο φλιτζάνι στο οποίο φτιάχνετε το τσάι σας.
Ναι, αστειεύομαι φυσικά. Αυτό το παράδειγμα υπάρχει και ονομάζεται λειτουργικός προγραμματισμός.
Ας δούμε πώς θα μπορούσε να φαίνεται το παραπάνω παράδειγμα σε ένα προσωπικό αγαπημένο μας πρόγραμμα, την Elixir.
defmodule WidgetPrices do
def priceorder([user, order]) do
[user, order]
|> widgetprice
|> shapepricemodifier
|> rushpricemodifier
|> holidaypricemodifier
end
defp widgetprice([user, order]) do
%{widget: widget} = order
price = WidgetRepo.getbase_price(widget)
[user, %{order | price: price }]
end
defp shapepricemodifier([user, order]) do
%{widget: widget, price: currentprice} = order
modifier = WidgetRepo.getshapeprice(widget)
[user, %{order | price: currentprice * modifier} ]
end
defp rushpricemodifier([user, order]) do
%{rush: rush, price: currentprice} = order
if rush do
[user, %{order | price: currentprice * 1.75} ]
else
[user, %{order | price: current_price} ]
end
end
defp holidaypricemodifier([user, order]) do
%{ημερομηνία: ημερομηνία, τιμή: τρέχουσα τιμή} = παραγγελία
modifier = HolidayRepo.getholidaymodifier(user, date)
[user, %{order | price: currentprice * modifier}]
end
end
```
Θα μπορούσατε να παρατηρήσετε ότι πρόκειται για ένα πλήρως επεξεργασμένο παράδειγμα του πώς μπορεί στην πραγματικότητα να επιτευχθεί η ιστορία του χρήστη. Αυτό οφείλεται στο γεγονός ότι είναι λιγότερο βαρύγδουπο από ό,τι θα ήταν στη Ruby. Χρησιμοποιούμε μερικά βασικά χαρακτηριστικά που είναι μοναδικά στην Elixir (αλλά γενικά διαθέσιμα σε λειτουργικές γλώσσες):
Καθαρές λειτουργίες. Στην πραγματικότητα δεν αλλάζουμε το εισερχόμενο παραγγελία
καθόλου, απλά δημιουργούμε νέα αντίγραφα - νέες επαναλήψεις της αρχικής κατάστασης. Ούτε μεταπηδάμε στο πλάι για να αλλάξουμε κάτι. Και ακόμα και αν το θέλαμε, παραγγελία
είναι απλά ένας "χαζός" χάρτης, δεν μπορούμε να καλέσουμε το order.save
σε οποιοδήποτε σημείο εδώ, επειδή απλά δεν ξέρει τι είναι αυτό.
Αντιστοίχιση προτύπων. Μάλλον παρόμοια με την αποδόμηση του ES6, αυτό μας επιτρέπει να τραβήξουμε τιμή
και widget
από την παραγγελία και να την μεταβιβάσουμε, αντί να εξαναγκάζουμε τους φίλους μας WidgetRepo
και HolidayRepo
να ξέρετε πώς να αντιμετωπίσετε μια πλήρη παραγγελία
.
Χειριστής σωλήνων. Βλέπεις στο price_order
, μας επιτρέπει να περνάμε δεδομένα μέσα από συναρτήσεις σε ένα είδος "pipeline" - μια έννοια άμεσα γνωστή σε όποιον έχει εκτελέσει ποτέ ps aux | grep postgres
για να ελέγξω αν το καταραμένο πράγμα λειτουργούσε ακόμα.
Έτσι σκέφτεστε
Οι παρενέργειες δεν αποτελούν πραγματικά βασικό μέρος της διαδικασίας σκέψης μας. Αφού ρίξετε νερό στο φλιτζάνι σας, δεν ανησυχείτε γενικά ότι ένα λάθος στον βραστήρα μπορεί να προκαλέσει υπερθέρμανση και έκρηξη - τουλάχιστον όχι τόσο ώστε να ψάξετε τα εσωτερικά του για να ελέγξετε αν κάποιος δεν άφησε κατά λάθος explode_after_pouring
γύρισε ψηλά.
Ο δρόμος από τον προγραμματιστή στον μηχανικό λογισμικού - που ξεπερνάει την ανησυχία για αντικείμενα και καταστάσεις και καταλήγει στην ανησυχία για ροές δεδομένων - μπορεί σε ορισμένες περιπτώσεις να διαρκέσει χρόνια. Σίγουρα το έκανε για τον εαυτό μου που μεγάλωσε με OOP. Με τις λειτουργικές γλώσσες, αρχίζετε να σκέφτεστε για ροές δεδομένων. την πρώτη σας νύχτα.
Έχουμε κάνει μηχανική λογισμικού περίπλοκο για εμάς και για κάθε νεοεισερχόμενο στον τομέα. Ο προγραμματισμός δεν χρειάζεται να είναι δύσκολος και εγκεφαλικός. Μπορεί να είναι εύκολος και φυσικός.
Ας μην το κάνουμε περίπλοκο και ας λειτουργήσουμε ήδη. Γιατί έτσι σκεφτόμαστε.
Διαβάστε επίσης: