Η μαγεία στον προγραμματισμό — δεν είναι μεταφορά, αλλά ακριβής όρος που υποδηλώνει τιμές (αριθμούς, συμβολοσειρές, σημαίες) των οποίων η σημασία δεν είναι προφανής από τα συμφραζόμενα και απαιτεί εξωτερική γνώση για να γίνει κατανοητή. Η πιο διαδεδομένη μορφή μαγείας — magic numbers: αριθμητικές σταθερές γραμμένες απευθείας στον κώδικα χωρίς εξήγηση γιατί επιλέχθηκε αυτή η συγκεκριμένη τιμή. Σύμφωνα με την έρευνα SonarSource Code Quality Report (2025), περίπου 8 τοις εκατό όλων των προειδοποιήσεων στατικών αναλυτών σχετίζονται με ανεξήγητα literals. Μαγικές τιμές κάνουν τον κώδικα εύθραυστο: η αλλαγή απαιτεί αναζήτηση όλων των εμφανίσεων και ο νέος προγραμματιστής δεν καταλαβαίνει αν μπορεί να αγγίξει τον αριθμό ή αν είναι κρίσιμος για τη λειτουργία του συστήματος.
Κύρια
Μαγεία (magic) — είναι κάθε τιμή στον πηγαίο κώδικα της οποίας το νόημα δεν είναι προφανές χωρίς πρόσθετες γνώσεις για τον τομέα. Ο όρος έχει καθιερωθεί στην κοινότητα: αν ένας προγραμματιστής κοιτάει έναν αριθμό και δεν καταλαβαίνει από πού προήλθε — αυτό είναι μαγεία.
Η μαγεία έχει διάφορους τύπους: αριθμητική (magic numbers), συμβολοσειράς (magic strings), λογική (magic flags) και διαμόρφωσης (hardcoded παράμετροι που θα έπρεπε να βρίσκονται στις ρυθμίσεις). Και οι τέσσερις τύποι ενώνονται από ένα πρόβλημα: όταν αλλάζει η απαίτηση, ο προγραμματιστής πρέπει να βρει όλα τα σημεία όπου χρησιμοποιείται η τιμή και να τα αντικαταστήσει χειροκίνητα. Η παράλειψη έστω και μίας εμφάνισης οδηγεί σε σφάλμα.
Σύμφωνα με την αναφορά JetBrains Code Quality Survey (2025), το 73 τοις εκατό των προγραμματιστών θεωρούν τα magic numbers δείκτη χαμηλής ποιότητας κώδικα, ενώ το 41 τοις εκατό παραδέχεται ότι τα αφήνουν περιστασιακά και οι ίδιοι. Ο κύριος λόγος — βιασύνη: “Θα βάλω τη σταθερά αργότερα” — αλλά το αργότερα δεν έρχεται ποτέ, και μετά από ένα μήνα ο αριθμός 0.85 παραμένει χωρίς εξήγηση στο σώμα της μεθόδου.
Βασικός κανόνας: κάθε literal τιμή, εκτός από 0, 1, true, false και κενή συμβολοσειρά, πρέπει να εξάγεται σε μια ονομασμένη σταθερά. Εξαιρέσεις: αύξηση μετρητή (i + 1), μαθηματικά μηδενικά (έλεγχος για 0) και αρχικές τιμές συσσωρευτών. Όλα τα υπόλοιπα — υποψήφια για ονομασία.
Magic number — είναι ένα αριθμητικό literal του οποίου η τιμή δεν είναι προφανής από τα συμφραζόμενα. Κλασικό παράδειγμα: 86400 στον κώδικα που είναι υπεύθυνος για το timeout. Ο προγραμματιστής βλέπει τον αριθμό και πρέπει να μαντέψει ότι αυτός είναι ο αριθμός των δευτερολέπτων σε μια ημέρα. Αν κάνει λάθος και βάλει 84600 — το σφάλμα θα είναι δύσκολο να εντοπιστεί, επειδή το timeout θα ενεργοποιηθεί 18 λεπτά νωρίτερα.
Γιατί είναι επικίνδυνα τα magic numbers: πρώτον, βλάπτουν την αναγνωσιμότητα. Ο αριθμός 1024 μπορεί να σημαίνει μέγεθος kilobyte, όριο σελιδοποίησης ή μέγιστο αριθμό στοιχείων. Χωρίς συμφραζόμενα — είναι απλώς ένας αριθμός. Δεύτερον, δημιουργούν διπλοτυπία: αν το 1024 χρησιμοποιείται σε πέντε σημεία, κατά την αλλαγή του ορίου σε 2048 ο προγραμματιστής πρέπει να βρει και τα πέντε και να τα αντικαταστήσει. Αν ένα σημείο παραλειφθεί — το σύστημα λειτουργεί λανθασμένα, αλλά χωρίς εμφανές σφάλμα.
// πριν — μαγεία στην καθαρή μορφή
fun calculateTimeout(base: Int): Int {
return base * 3 + 5000
}
// μετά — τιμές αντικαταστάθηκαν με σταθερές
private const val RETRY_MULTIPLIER = 3
private const val BASE_TIMEOUT_MS = 5000
fun calculateTimeout(base: Int): Int {
return base * RETRY_MULTIPLIER + BASE_TIMEOUT_MS
}
Ο τρίτος κίνδυνος — αδυναμία δοκιμής. Αν η τιμή ορίου είναι ενσωματωμένη στον κώδικα ως literal, η δοκιμή δεν μπορεί να την παρακάμψει για να ελέγξει τις οριακές συνθήκες. Μια σταθερά εξαγόμενη σε companion object ή αρχείο διαμόρφωσης καθιστά τον κώδικα δοκιμάσιμο: η δοκιμή βάζει άλλη τιμή και ελέγχει τη συμπεριφορά του συστήματος στο όριο.
Αναπτύξτε συνήθεια: κάθε φορά που γράφετε έναν αριθμό διαφορετικό από 0, 1, 100 ή 2 — σταματήστε και σκεφτείτε αν αξίζει να τον εξάγετε σε σταθερά. Αν ο αριθμός σχετίζεται με επιχειρηματική λογική (όριο, κατώφλι, timeout, μέγεθος) — εξάγετε υποχρεωτικά. Αν ο αριθμός είναι μαθηματική σταθερά (pi, e) — χρησιμοποιήστε την τυπική βιβλιοθήκη (Math.PI, Math.E).
Magic strings — literals συμβολοσειρών ενσωματωμένα στον κώδικα χωρίς εξαγωγή σε σταθερές ή πόρους. Τυπικά παραδείγματα: URL τελικών σημείων, ονόματα κλειδιών SharedPreferences, Intent Actions, bundle keys, ονόματα αρχείων και ερωτήματα SQL.
Ο κίνδυνος των μαγικών συμβολοσειρών έγκειται στην απουσία ελέγχου κατά τη μεταγλώττιση. Ένα τυπογραφικό λάθος στη συμβολοσειρά “user_prefs” δεν θα ανιχνευθεί μέχρι τον χρόνο εκτέλεσης. Αν η συμβολοσειρά χρησιμοποιείται σε δέκα σημεία και ο προγραμματιστής σε ένα σημείο έγραψε “user_pref” (χωρίς s) — η εφαρμογή δεν καταρρέει, αλλά τα δεδομένα δεν αποθηκεύονται. Ένα τέτοιο σφάλμα μπορεί να ζει στην παραγωγή για μήνες, επειδή δεν προκαλεί κατάρρευση.
Για έργα Android, οι μαγικές συμβολοσειρές πρέπει να εξάγονται σε πόρους (strings.xml, arrays.xml) ή σε σταθερές στο companion object. Για iOS — σε πόρους συμβολοσειρών (Localizable.strings) ή σταθερές enum. Για backend — σε αρχεία διαμόρφωσης (.env, application.properties). Κανένα κλειδί, URL ή διαδρομή δεν πρέπει να υπάρχει στον κώδικα ως literal συμβολοσειράς.
// πριν — μαγικές συμβολοσειρές σε όλη την κλάση
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")
// μετά — συμβολοσειρές εξήχθησαν σε enum
enum PrefKeys: String {
case authToken = "auth_token"
case currentUserId = "current_user_id"
}
prefs.set(token, forKey: PrefKeys.authToken.rawValue)
prefs.set(userId, forKey: PrefKeys.currentUserId.rawValue)
Δώστε ιδιαίτερη προσοχή σε συμβολοσειρές που επαναλαμβάνονται. Αν το ίδιο κλειδί “user_settings” εμφανίζεται σε τρία αρχεία — με πιθανότητα 99 τοις εκατό αργά ή γρήγορα θα προκύψει τυπογραφικό λάθος σε ένα από αυτά. Η εξαγωγή σε enum ή σταθερά εγγυάται ότι όλες οι αναφορές χρησιμοποιούν την ίδια τιμή.
Magic flags — λογικές παράμετροι των οποίων η τιμή δεν είναι προφανής από τα συμφραζόμενα της κλήσης. Κλασικό αντι-πρότυπο: μετάδοση true ή false σε μια μέθοδο χωρίς επεξήγηση του τι ακριβώς ενεργοποιεί ή απενεργοποιεί αυτή η σημαία.
Παράδειγμα: userDao.fetch(includeDeleted = false). Ο προγραμματιστής βλέπει false και δεν καταλαβαίνει αν αυτό σημαίνει “μην συμπεριλάβεις τα διεγραμμένα” ή “μην συμπεριλάβεις τα ενεργά”. Ένα μήνα αργότερα, το false μετατρέπεται σε true και στα αποτελέσματα αρχίζουν να εμφανίζονται διεγραμμένες εγγραφές. Το σφάλμα ανακαλύπτεται μόνο στην παραγωγή.
Λύση — αντικατάσταση λογικών σημαιών με enum ή sealed class. Αντί για παράμετρο Boolean, χρησιμοποιήστε UserFilter.includeDeleted ή UserFilter.activeOnly. Έτσι ο κώδικας τεκμηριώνει μόνος του την πρόθεση και το IDE προτείνει διαθέσιμες επιλογές κατά την αυτόματη συμπλήρωση.
Αν μια λογική σημαία μεταβιβάζεται μέσω πολλαπλών επιπέδων — αυτό είναι ένα ακόμα σήμα ότι η αφαίρεση είναι λανθασμένη. Αντί να σέρνετε τη σημαία μέσα από τρία επίπεδα κλήσεων, σκεφτείτε αν η επιλογή φιλτραρίσματος θα πρέπει να λαμβάνεται στο ανώτερο επίπεδο και να μεταβιβάζεται ως έτοιμη διαμόρφωση. Όσο λιγότερες λογικές σημαίες στον κώδικα — τόσο λιγότερη μαγεία.
Εισαγάγετε έναν κανόνα: καμία λογική παράμετρος δεν μεταβιβάζεται σε μια μέθοδο χωρίς ονομασμένο όρισμα (αν η γλώσσα υποστηρίζει named arguments). Σε Kotlin και Swift αυτή η απαίτηση εκπληρώνεται αυτόματα. Σε Java χρησιμοποιήστε Builder ή σταθερές enum αντί για true/false.
Η αναζήτηση μαγικών τιμών αυτοματοποιείται από στατικούς αναλυτές που είναι διαμορφωμένοι για τον εντοπισμό literals σε απροσδόκητα σημεία. Κάθε γλώσσα προσφέρει τα δικά της εργαλεία με παραμετροποιήσιμες εξαιρέσεις.
| Εργαλείο | Γλώσσες | Κανόνας |
|---|---|---|
| SonarQube | Java, Kotlin, Swift, Python, JS | MagicNumber, HardcodedString |
| ESLint | JavaScript, TypeScript | no-magic-numbers, no-hardcoded-strings |
| Detekt | Kotlin | MagicNumber, ComplexCondition |
| SwiftLint | Swift | magic_number (ενεργοποιημένο opt-in) |
| PMD | Java, Apex, PLSQL | MagicNumber (μπορεί να διαμορφωθεί λίστα επιτρεπόμενων) |
| PhpStorm Inspections | PHP | NumericLiteralWithContext (ενσωματωμένη επιθεώρηση) |
Η διαμόρφωση εξαιρέσεων είναι κρίσιμης σημασίας — χωρίς αυτήν ο αναλυτής θα εκδίδει προειδοποιήσεις σε κάθε αύξηση (-1, +1) και μαθηματικό μηδέν. Για SonarQube η λίστα επιτρεπόμενων αριθμών: 0, 1, -1, 2 (για διπλασιασμό), 100 (ποσοστά), 60 και 24 (χρόνος). Για όλες τις άλλες τιμές — απαιτήστε ονομασμένη σταθερά με τροποποιητή public static final (Java) ή const val (Kotlin).
Για ανάλυση σε επίπεδο CI, προσθέστε ένα βήμα με έλεγχο μαγείας ως προειδοποίηση, αλλά όχι μπλοκάρισμα του build. Η πρώτη εκτέλεση θα δείξει εκατοντάδες προειδοποιήσεις σε παλαιό κώδικα. Σταδιακά, ticket προς ticket, μεταφέρετε τον κώδικα σε σταθερές και ανεβάζετε το όριο ποιότητας. Όταν ο αριθμός των magic numbers γίνει μικρότερος από 10 — ενεργοποιήστε τον κανόνα ως σφάλμα build.
Η αναδιάρθρωση της μαγείας — μία από τις ασφαλέστερες λειτουργίες: η αντικατάσταση ενός literal με σταθερά δεν αλλάζει τη συμπεριφορά του κώδικα. Παρόλα αυτά, η προσέγγιση πρέπει να είναι συστηματική για να μην παραλειφθούν κρυφές εξαρτήσεις (για παράδειγμα, αν το ίδιο magic number χρησιμοποιείται σε μη σχετιζόμενα συμφραζόμενα αλλά τυχαία έχει την ίδια τιμή).
Διαδικασία βήμα προς βήμα: βρείτε όλες τις εμφανίσεις της μαγικής τιμής, κατανοήστε τα συμφραζόμενα κάθε μιας, χωρίστε σε διαφορετικές σταθερές (ακόμα και αν οι τιμές συμπίπτουν — τα συμφραζόμενα είναι διαφορετικά και οι σταθερές πρέπει να ονομάζονται διαφορετικά), αντικαταστήστε τα literals με σταθερές, ελέγξτε μέσω δοκιμών. Το σφάλμα στο βήμα 2 — το πιο συνηθισμένο: δύο διαφορετικές έννοιες (timeout σε χιλιοστά του δευτερολέπτου και όριο σε bytes) μπορεί αριθμητικά να συμπίπτουν (π.χ. 5000), αλλά σημασιολογικά είναι διαφορετικά μεγέθη και δεν μπορούν να ενωθούν σε μία σταθερά.
// πριν — ίδιος αριθμός σε διαφορετικά συμφραζόμενα
public class Config {
public void setupCache() {
cache.setMaxSize(5000); // 5 MB
}
public void setupTimeout() {
client.setReadTimeout(5000); // 5 δευτερόλεπτα
}
}
// μετά — διαφορετικές σταθερές για διαφορετικά συμφραζόμενα
public class Config {
private static final int CACHE_MAX_SIZE_MB = 5;
private static final int READ_TIMEOUT_SECONDS = 5;
public void setupCache() {
cache.setMaxSize(CACHE_MAX_SIZE_MB * 1024 * 1024);
}
public void setupTimeout() {
client.setReadTimeout(
READ_TIMEOUT_SECONDS * 1000
);
}
}
Για νέο κώδικα ο κανόνας είναι απλός: κάθε literal, εκτός από 0, 1, -1, true, false, null και κενή συμβολοσειρά, εξάγεται σε σταθερά. Εξαιρέσεις: μαθηματικές σταθερές (πάντα μέσω της τυπικής βιβλιοθήκης), δεδομένα δοκιμών (το literal μπορεί να μείνει στη δοκιμή, αλλά με επεξηγηματικό όνομα μεταβλητής) και οριακές τιμές για αύξηση (i + 1 σε βρόχο — φυσιολογικό).
Συχνές Ερωτήσεις
Ναι, το 100 είναι επίσης magic number αν χρησιμοποιείται χωρίς συμφραζόμενα. Αντί για 100 γράψτε MAX_PERCENT ή PROBABILITY_SCALE. Εξαίρεση: όταν το 100 είναι προφανές ποσοστό στα συμφραζόμενα (για παράδειγμα, στον τύπο υπολογισμού ποσοστού), αλλά ακόμα και σε αυτήν την περίπτωση η σταθερά βελτιώνει την αναγνωσιμότητα.
Στις δοκιμές επίσης είναι καλύτερο να χρησιμοποιείτε ονομασμένες μεταβλητές. Αντί για assertEquals(42, result) γράψτε val expected = 42; assertEquals(expected, result). Εξαίρεση: δοκιμές σε οριακές τιμές (0, null, κενή συμβολοσειρά) — μπορούν να μείνουν ως literals, επειδή είναι αναγνώσιμα στα συμφραζόμενα της δοκιμής.
Ναι, οι αριθμοί που σχετίζονται με το UI (μεγέθη, περιθώρια, διάρκεια κινούμενων σχεδίων) πρέπει να είναι σε πόρους (dimens.xml, integers.xml). Επιχειρηματικές σταθερές (timeout, όρια) — σε companion object ή αρχείο διαμόρφωσης. Το κύριο κριτήριο: αν ο αριθμός μπορεί να αλλάξει χωρίς να αλλάξει η λογική — είναι πόρος.
Εκτελέστε το SonarQube με τον κανόνα MagicNumber ή το ESLint με no-magic-numbers. Λάβετε αναφορά, ταξινομήστε κατά συχνότητα χρήσης και ξεκινήστε με αριθμούς που εμφανίζονται σε τρία ή περισσότερα σημεία. Αυτοί είναι με τη μεγαλύτερη πιθανότητα υποψήφιοι για εξαγωγή σε σταθερά.
Όχι. Επιτρεπόμενα literals: 0, 1, -1 (αύξηση/μείωση, έλεγχος κενού), true, false, null, κενή συμβολοσειρά. Όλα τα άλλα απαιτούν ονομασία. Αν ο αριθμός 0 χρησιμοποιείται όχι ως έλεγχος κενού (για παράδειγμα, 0 — είναι το ID της ρίζας κατηγορίας), τότε το 0 πρέπει επίσης να είναι σταθερά: ROOT_CATEGORY_ID = 0.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης