“Δουλεύει τοπικά” (αγγλ. “Works on my machine”) — η κλασική φράση προγραμματιστή που δεν μπορεί να αναπαραγάγει ένα bug στο τοπικό του περιβάλλον, αν και το bug εμφανίζεται σταθερά σε άλλα μέλη της ομάδας ή στο production. Η κατάσταση προκύπτει από διαφορές στη διαμόρφωση, στις εκδόσεις εξαρτήσεων, στο λειτουργικό σύστημα ή στα δεδομένα μεταξύ του υπολογιστή του προγραμματιστή και του περιβάλλοντος όπου το bug αναπαράγεται. Σύμφωνα με το Stack Overflow Survey 2023, το 58% των προγραμματιστών λένε αυτή τη φράση τουλάχιστον μία φορά το μήνα, και το 31% — εβδομαδιαία. Αναλύουμε γιατί ο κώδικας δεν λειτουργεί παντού το ίδιο και πώς να τυποποιήσετε το περιβάλλον.
Βασικά σημεία
“Δουλεύει τοπικά” — η φράση που λέει ένας προγραμματιστής όταν ένας συνάδελφος ή δοκιμαστής αναφέρει ένα bug, αλλά στον υπολογιστή του προγραμματιστή το bug δεν αναπαράγεται. Εξωτερικά μοιάζει με άρνηση του προβλήματος, αλλά τεχνικά η κατάσταση είναι πραγματική: ο κώδικας μπορεί πράγματι να λειτουργεί σε ένα περιβάλλον και να αποτυγχάνει σε ένα άλλο. Μία διαφορά ενός bit στη διαμόρφωση — και η συμπεριφορά της εφαρμογής αλλάζει δραστικά.
Η φράση έγινε meme στην κοινότητα πληροφορικής, επειδή είναι ταυτόχρονα αληθινή και άχρηστη. Από την πλευρά του προγραμματιστή — ο κώδικας πράγματι λειτουργεί στον υπολογιστή του. Από την πλευρά της ομάδας — το πρόβλημα υπάρχει και πρέπει να λυθεί, όχι να δικαιολογηθεί. Το χιούμορ της κατάστασης είναι ότι ο προγραμματιστής λέει την αλήθεια, αλλά αυτή η αλήθεια δεν βοηθά στη διόρθωση του bug. Το meme είναι τόσο δημοφιλές που του αφιερώνονται χιλιάδες αναρτήσεις στο Reddit, XKCD και συνέδρια DevOps.
Από την πλευρά των διαδικασιών, η φράση “δουλεύει τοπικά” — δείκτης προβλημάτων με την αναπαραγωγιμότητα του περιβάλλοντος. Αν δύο προγραμματιστές δεν μπορούν να πάρουν το ίδιο αποτέλεσμα με τον ίδιο κώδικα — σημαίνει ότι η διαδικασία ρύθμισης του περιβάλλοντος δεν είναι τυποποιημένη. Η πρακτική DevOps υποστηρίζει: το περιβάλλον πρέπει να είναι αναπαραγώγιμο με μία εντολή από το αποθετήριο χωρίς χειροκίνητες ενέργειες.
Το τοπικό περιβάλλον του προγραμματιστή σχεδόν πάντα διαφέρει από το production. Ο προγραμματιστής χρησιμοποιεί macOS ή Windows, ενώ ο διακομιστής λειτουργεί σε Linux. Διαφορετικά λειτουργικά συστήματα έχουν διαφορετικά συστήματα αρχείων, κωδικοποιήσεις, χρονισμούς νημάτων και κλήσεις συστήματος. Ακόμα κι αν και τα δύο περιβάλλοντα είναι Linux — η έκδοση πυρήνα, glibc, OpenSSL μπορεί να διαφέρουν.
Η δεύτερη αιτία — το σύνολο εγκατεστημένου λογισμικού. Στον υπολογιστή του προγραμματιστή μπορεί να είναι εγκατεστημένη η καθολική έκδοση Node.js 20, ενώ στη διαμόρφωση CI/CD αναφέρεται η έκδοση 18. Ή ο προγραμματιστής χρησιμοποιεί PostgreSQL 16 τοπικά, ενώ στο production — PostgreSQL 14. Οι διαφορές σε δευτερεύουσες εκδόσεις συχνά δεν είναι αισθητές, αλλά οι μεγάλες ενημερώσεις μπορεί να αλλάξουν τη συμπεριφορά των SQL ερωτημάτων. Σύμφωνα με την npm Inc., το 67% των bugs που σχετίζονται με εξαρτήσεις προκαλούνται από διαφορά σε patch-εκδόσεις.
Η τρίτη αιτία — συνθήκες δικτύου. Στον τοπικό υπολογιστή δεν υπάρχουν καθυστερήσεις, όρια εύρους ζώνης και προβλήματα DNS. Στο production, οποιοδήποτε αίτημα σε εξωτερικό API μπορεί να διαρκέσει 500 ms αντί για 5 ms. Timeouts, retry-λογική, race conditions — όλα αυτά τα προβλήματα εμφανίζονται μόνο υπό πραγματική φόρτιση και σε πραγματικές συνθήκες δικτύου. Η εξομοίωση δικτύου μέσω εργαλείων όπως το Toxiproxy βοηθά στον εντοπισμό τέτοιων προβλημάτων πριν από το deployment.
Η πρώτη αιτία — έλλειψη δεδομένων. Ο προγραμματιστής εργάζεται με δοκιμαστικά fixtures, ενώ στο production υπάρχουν εκατομμύρια εγγραφές με απροσδόκητες τιμές. NULL σε ένα πεδίο που ο προγραμματιστής θεωρούσε υποχρεωτικό, Unicode χαρακτήρας σε ένα όνομα, πολύ μεγάλη συμβολοσειρά — όλα αυτά μπορεί να προκαλούν bugs που δεν αναπαράγονται στην τοπική ΒΔ με συνθετικά δεδομένα.
Η δεύτερη αιτία — διαφορετικές σημαίες μεταγλώττισης και build. Η έκδοση Release/Distribution μπορεί να διαφέρει από την Debug. Οι βελτιστοποιήσεις μεταγλωττιστή, η αφαίρεση debug-καταγραφών, το inlining συναρτήσεων — όλα αυτά μπορεί να κρύβουν ή, αντίθετα, να εμφανίζουν bugs. Τυπικό παράδειγμα: στο debug build λειτουργεί ένα assert που αποτυγχάνει στο release build λόγω διαφορετικής σειράς αρχικοποίησης μεταβλητών.
Η τρίτη αιτία — τοπική προσωρινή μνήμη και προσωρινά αρχεία. Ο προγραμματιστής μπορεί να μην παρατηρήσει ένα bug επειδή στον browser είναι αποθηκευμένα παλιά scripts, στο Redis υπάρχουν παρωχημένα δεδομένα, και στο σύστημα αρχείων υπάρχουν προσωρινά αρχεία από προηγούμενες εκτελέσεις. Μια καθαρή εκτέλεση (incognito λειτουργία, εκκαθάριση cache, fresh install) συχνά αναπαράγει το bug που δεν εμφανιζόταν “από μόνο του”.
Η τέταρτη αιτία — συγκρούσεις καθολικών και τοπικών εξαρτήσεων. Εργαλεία όπως Ruby gems, Python pip, Node.js npm μπορεί να έχουν καθολικά εγκατεστημένα πακέτα που “βοηθούν” τον κώδικα να λειτουργεί τοπικά, αλλά απουσιάζουν στο production. Η χρήση εικονικών περιβαλλόντων (virtualenv, venv, nvm) απομονώνει το έργο από καθολικές εγκαταστάσεις και καθιστά το περιβάλλον επαναλήψιμο.
Η φράση “δουλεύει τοπικά” καταστρέφει την εμπιστοσύνη στην ομάδα. Αν ένας προγραμματιστής δεν μπορεί τακτικά να αναπαραγάγει bugs, οι συνάδελφοι αρχίζουν να αμφιβάλλουν για την ικανότητα ή την προσοχή του στη δοκιμή. Με την πάροδο του χρόνου, αυτό οδηγεί σε μικροδιαχείριση: κάθε αλλαγή απαιτεί έλεγχο από δεύτερο προγραμματιστή, γεγονός που επιβραδύνει την ανάπτυξη. Σύμφωνα με το Google Project Aristotle, η ψυχολογική ασφάλεια στην ομάδα επηρεάζει άμεσα την παραγωγικότητα, και οι συνεχείς διαφωνίες για το περιβάλλον είναι ένας από τους παράγοντες μείωσής της.
Το δεύτερο πρόβλημα — επιβράδυνση του code review. Αν ένας προγραμματιστής δεν μπορεί να αναπαραγάγει ένα bug τοπικά, μπορεί να απορρίψει το pull request του συναδέλφου με τα λόγια “σε μένα δουλεύει — σημαίνει ότι το πρόβλημα είναι σε σένα”. Αυτό προκαλεί συγκρούσεις και καθυστερεί την παράδοση λειτουργιών. Η τυποποίηση του περιβάλλοντος αφαιρεί αυτή τη σύγκρουση: αν και οι δύο προγραμματιστές εργάζονται στο ίδιο Docker container, το ερώτημα “σε ποιον δουλεύει” χάνει το νόημά του.
Το τρίτο πρόβλημα — απώλεια bugs στο tracker. Bugs που “δεν αναπαράγονται στον προγραμματιστή” συχνά κλείνονται με την ένδειξη “δεν αναπαράγεται” (Cannot Reproduce). Σε ένα μήνα το bug εμφανίζεται στο production και η διόρθωσή του κοστίζει 10 φορές περισσότερο. Κανόνας: αν ένα bug αναπαράγεται έστω και σε ένα άτομο — υπάρχει ανεξάρτητα από το αν δουλεύει στον προγραμματιστή ή όχι.
Ο πρώτος και πιο αποτελεσματικός τρόπος — Docker. Ολόκληρο το έργο πρέπει να εκκινείται μέσω docker-compose up χωρίς πρόσθετες ενέργειες. Βάση δεδομένων, cache, ουρά μηνυμάτων, web server — όλα ανεβαίνουν σε containers. Ο προγραμματιστής εγκαθιστά μόνο Docker και Git. Τα υπόλοιπα — μέσα στα containers. Αυτό εγγυάται ότι όλα τα μέλη της ομάδας έχουν το ίδιο περιβάλλον ανεξάρτητα από το ΛΣ.
Ο δεύτερος τρόπος — διαχειριστές εκδόσεων. Αν το Docker είναι αδύνατο (περιορισμοί αδειοδότησης, legacy υποδομή), χρησιμοποιήστε nvm (Node.js), rbenv (Ruby), pyenv (Python), sdkman (Java). Οι διαχειριστές εκδόσεων επιτρέπουν την εναλλαγή εκδόσεων γλωσσών και εργαλείων εντός του έργου. Τα αρχεία .nvmrc, .ruby-version, .python-version πρέπει να βρίσκονται στο αποθετήριο και να ελέγχονται από το CI/CD.
Ο τρίτος τρόπος — Vagrant για εικονικές μηχανές. Το Vagrant ανεβάζει μια εικονική μηχανή με καθορισμένο ΛΣ και διαμόρφωση πάνω από VirtualBox ή VMware. Μέσα στην VM εγκαθίστανται όλες οι εξαρτήσεις μέσω provisioning scripts (shell, Ansible, Puppet). Το Vagrant είναι βαρύτερο από το Docker, αλλά παρέχει πλήρη απομόνωση σε επίπεδο ΛΣ — χρήσιμο για έργα που εξαρτώνται από συγκεκριμένη έκδοση πυρήνα Linux.
Ο τέταρτος — Makefile και bootstrap scripts. Ακόμα και ένα απλό Makefile με στόχους install, test, build, clean μπορεί να τυποποιήσει καθημερινές ενέργειες. Η εντολή make install πρέπει να εγκαθιστά όλες τις εξαρτήσεις, να ρυθμίζει τη ΒΔ και να δημιουργεί δοκιμαστικά δεδομένα. Ένα ενιαίο σημείο εισόδου για όλους τους προγραμματιστές αποκλείει χειροκίνητα λάθη κατά τη ρύθμιση του περιβάλλοντος.
Το κύριο εργαλείο — lock-αρχεία εξαρτήσεων. package-lock.json (npm), yarn.lock (Yarn), Podfile.lock (CocoaPods), pubspec.lock (Flutter) καθορίζουν τις ακριβείς εκδόσεις κάθε πακέτου. Χωρίς lock-αρχείο, δύο προγραμματιστές που εγκαθιστούν εξαρτήσεις σε διαφορετικές χρονικές στιγμές μπορεί να λάβουν διαφορετικές δευτερεύουσες εκδόσεις. Το lock-αρχείο πρέπει να βρίσκεται στο αποθετήριο και να μην επεξεργάζεται χειροκίνητα.
Το δεύτερο εργαλείο — .env.example στο αποθετήριο. Ένα αρχείο-πρότυπο μεταβλητών περιβάλλοντος με σχόλια. Ο προγραμματιστής το αντιγράφει σε .env και συμπληρώνει τις δικές του τιμές. Το CI/CD pipeline ελέγχει ότι όλες οι υποχρεωτικές μεταβλητές έχουν οριστεί. Σύμφωνα με το GitLab 2023, οι ομάδες που χρησιμοποιούν .env.example μειώνουν τον αριθμό περιστατικών που σχετίζονται με μεταβλητές περιβάλλοντος κατά 40%.
Το τρίτο εργαλείο — pre-commit hooks. Αυτόματος έλεγχος που εκτελείται πριν από κάθε commit: linter, formatter, έλεγχος τύπων, δοκιμές. Αν τα hooks είναι ρυθμισμένα ίδια για όλους τους προγραμματιστές, τότε στο production δεν θα φτάσουν σφάλματα μορφοποίησης ή τύπων που “στον τοπικό υπολογιστή πέρασαν”. Husky για JavaScript και pre-commit για Python — δημοφιλείς λύσεις.
Το τέταρτο — CI/CD pipeline που εκτελεί δοκιμές σε καθαρό περιβάλλον. Αν οι δοκιμές περνούν στο CI αλλά όχι τοπικά — το πρόβλημα είναι στη ρύθμιση του τοπικού περιβάλλοντος. Αν οι δοκιμές δεν περνούν στο CI — το pull request δεν γίνεται merge. Αυτός ο αυστηρός κανόνας αποκλείει την είσοδο bugs που “δουλεύουν τοπικά” στο βασικό branch.
Συχνές ερωτήσεις
Είναι αμυντική αντίδραση: ο προγραμματιστής ξοδεύει πολύ χρόνο στην αποσφαλμάτωση και το να ακούσει ότι ο κώδικας δεν λειτουργεί είναι ψυχολογικά επώδυνο. Η φράση δίνει χρόνο να “αλλάξει” και να αρχίσει την αναζήτηση της αιτίας χωρίς αίσθημα ενοχής.
Ζητήστε να αναπαραγάγετε το bug σε καθαρό περιβάλλον (clean install, incognito λειτουργία). Αν δεν αναπαράγεται — συγκρίνετε τις εκδόσεις εξαρτήσεων και τις μεταβλητές περιβάλλοντος. Αν δεν βοηθά — σηκώστε Docker περιβάλλον ταυτόσημο με το production.
Το Docker παρέχει ένα απομονωμένο container με σταθερή διαμόρφωση που λειτουργεί το ίδιο σε οποιοδήποτε ΛΣ. Όλοι οι προγραμματιστές χρησιμοποιούν το ίδιο Dockerfile, επομένως το περιβάλλον είναι ταυτόσημο. Αν το bug δεν αναπαράγεται στο container — σημαίνει ότι το πρόβλημα είναι πράγματι στον κώδικα, όχι στο σύστημα.
Το lock-αρχείο καθορίζει ακριβή hashes και εκδόσεις όλων των μεταβατικών εξαρτήσεων. Ακόμα κι αν στο μητρώο πακέτων κυκλοφορήσει μια νέα έκδοση εξάρτησης, η εγκατάσταση μέσω lock-αρχείου εγγυάται ότι κάθε προγραμματιστής θα λάβει το ίδιο σύνολο πακέτων με τους υπόλοιπους.
Το Vagrant με VirtualBox δικαιολογείται αν το έργο εξαρτάται από συγκεκριμένες μονάδες πυρήνα ΛΣ ή απαιτεί πλήρη απομόνωση σε επίπεδο πυρήνα. Για το 90% των έργων, το Docker είναι ελαφρύτερο, ταχύτερο και πιο βολικό. Η επιλογή εξαρτάται από το πόσο βαθιά αλληλεπιδρά το έργο με το ΛΣ.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης