RandeBoo Post-Mortem: Πώς το 36% Έγινε V1.0 (και ο Θρίαμβος του Aegean Theme)
Current Session Stats
Θυμάστε εκείνο το 36% progress της 1ης εβδομάδας; Εκείνο το pre-alpha bioweapon που ισορροπούσε ανάμεσα στη λογική και στο runtime error;
Λοιπόν, ο Θεός του open-source έκανε το θαύμα του. Ή μάλλον, για να είμαστε ακριβείς, εμείς κάναμε το θαύμα μας. Το Project 07 (RandeBoo) είναι πλέον έτοιμο. Βρισκόμαστε στη φάση των τελικών ελέγχων, του documentation και της ψυχικής προετοιμασίας για την παράδοση και την επικείμενη παρουσίαση. Παραδόξως, ούτε η βάση δεδομένων κάηκε μέχρι στιγμής, ούτε ο καθηγητής μας έστειλε στο τμήμα ψυχικής υγείας του ΕΑΠ.
Αλλά πίσω από την επικείμενη παράδοση κρύβεται η κλασική ιστορία κάθε software project: σχεδιασμός, πανικός, pivot της τελευταίας στιγμής και πολλή καφεΐνη.
Το Μεγάλο Pivot: Από τα Silos στο Full Stack
Ξεκινήσαμε όπως προτείνουν τα κλασικά βιβλία Software Engineering: "Εσύ θα κάνεις Frontend, εσύ Backend, εσύ Testing".
Αποτέλεσμα; Deadlocks παντού. Ο Frontendας περίμενε το API, ο Backendας περίμενε το Database schema, και ο Tester έπαιζε πασιέντζα. Κάθε αλλαγή χρειαζόταν Viber call με ήχο ηλεκτρικής σκούπας στο background για να συγχρονιστούμε.
Συνειδητοποιώντας ότι με αυτόν τον ρυθμό θα παίρναμε πτυχίο το 3035, πρότεινα ένα δραστικό pivot: Full Stack Module Ownership.
Όπως περιέγραψα και στο Leap of Faith προς την Ακαδημία του Πλάτωνα, αυτή η απόφαση να πετάξουμε τις «κουκούλες» του over-engineering και να γίνουμε «μισθοφόροι» (Full Stack) ήταν που έσωσε το project. Από το χάος της αρχικής modular αρχιτεκτονικής που έμοιαζε με τα χειρόγραφα του Φειδία (αλλά δεν έτρεχε), περάσαμε στην ωμή πραγματικότητα.
Κάθε μέλος της ομάδας ανέλαβε ένα ολοκληρωμένο functional κομμάτι της εφαρμογής (module) και το έχτισε από την αρχή μέχρι το τέλος: από την προσαρμογή και επέκταση των queries της βάσης για το δικό του κομμάτι, μέχρι το layout στο Tkinter.
Αυτό μείωσε τις εξαρτήσεις στο μηδέν. Η συνεργασία με τους υπόλοιπους συμφοιτητές έγινε ασύγχρονη και αποδοτική. Ο καθένας είχε το sandbox του, έσπαγε πράγματα χωρίς να ενοχλεί τους άλλους.
Βέβαια, το Git στην αρχή κάθε άλλο παρά ειρηνικό ήταν. Περάσαμε από ολόκληρο δράμα: push σε λάθος branches, fetches που αρνούνταν να συνεργαστούν, και merge conflicts που προκαλούσαν υπαρξιακά ερωτήματα. Το δικό μου κύριο task (πέρα από τον κώδικα) ήταν να κάνω ένα άτυπο crash course για το πώς δουλεύει το GitHub και γιατί το χρειαζόμαστε, προσπαθώντας να τους πείσω ότι η ανταλλαγή αρχείων κώδικα με email, Viber ή... δισκέτες 3.5" ανήκει στο προηγούμενο millenium. Τελικά, η «εκπαίδευση» απέδωσε και το Git σταμάτησε να θυμίζει εμπόλεμη ζώνη.
Το 24ωρο του Pointers-and-Queries (database.py)
Η θεωρία είναι ωραία στα αμφιθέατρα. Όταν όμως έρχεται η ώρα να μετατρέψεις τα βέλη του UML σε γραμμές κώδικα, η πραγματικότητα σε χαστουκίζει με το sqlite3.db. Μια συμφοιτήτρια της ομάδας είχε κάνει εξαιρετική δουλειά στο θεωρητικό μοντέλο (UML/ER). Το δικό μου task; Να μετατρέψω το θεωρητικό ιδεατό σε ωμό, λειτουργικό SQLite κώδικα (database.py) και να λύσω τον μεγαλύτερο γρίφο της εφαρμογής: το Conflict Management των ραντεβού.
Αν νομίζετε ότι το SQLite σε Python είναι "απλό", δοκιμάστε να γράψετε έναν αλγόριθμο που ελέγχει αν ένα νέο ραντεβού συμπίπτει με ένα ήδη υπάρχον. Ακούγεται εύκολο; Προσθέστε τώρα στην εξίσωση: 1) το ωράριο του συγκεκριμένου υπαλλήλου, 2) τις εξαιρέσεις (π.χ. ο υπάλληλος πήρε άδεια ή είναι αργία), 3) τη μεταβαλλόμενη διάρκεια του ραντεβού και 4) το γεγονός ότι ο χρόνος αποθηκεύεται ως text-based timestamps.
Πέρασα $X^e$ ώρες πάνω από την οθόνη σε ένα μόνο Σαββατοκύριακο. Το σενάριο ήταν κλασικό: 3 π.μ., το δωμάτιο να μυρίζει Freddo Espresso, οι ανεμιστήρες του υπολογιστή να ουρλιάζουν σαν τουρμπίνα απο Caterpillar C18 Stage V (κυριολεκτικά) και εγώ να προσπαθώ να καταλάβω γιατί τα queries μου επέστρεφαν true για ραντεβού που είχαν γίνει το 2024. Όταν επιτέλους το query έτρεξε σωστά και τα tests άρχισαν να βγάζουν πράσινα ticks, ένιωσα λες και είχα αποκρυπτογραφήσει τον Μηχανισμό των Αντικυθήρων. Αντίο κοινωνική ζωή, καλωσόρισες SQLite.
# Η καρδιά του conflict check - SQLite Edition
cursor.execute('''
SELECT 1 FROM appointments
WHERE employee_id = ?
AND date = ?
AND (
(start_time <= ? AND end_time > ?) OR
(start_time < ? AND end_time >= ?) OR
(start_time >= ? AND end_time <= ?)
)
''', (emp_id, date_str, start_t, start_t, end_t, end_t, start_t, end_t))Η βάση όμως δεν έμεινε στατική. Οι συμφοιτητές πρόσθεσαν functions για τις ανάγκες των δικών τους modules, έκαναν fine-tuning για να υποστηρίξουν τα exports και τα custom search queries, κι εμείς κάναμε τάμα στον Άγιο Λάινους Τόρβαλντς να μην σκάσει κάποιο άλλο query στο merge. Ομαδική δουλειά στην πράξη (με λίγη θεϊκή/Linux παρέμβαση).
Aegean Theme: Όταν το Tkinter θυμίζει Κυκλάδες
Ας είμαστε ειλικρινείς. Το Tkinter είναι ο ορισμός του «τεχνολογικού αναχρονισμού». Το να προσπαθείς να φτιάξεις ένα σύγχρονο, όμορφο UI σε Tkinter το 2026 είναι σαν να προσπαθείς να σμιλέψεις το άγαλμα του Δαβίδ χρησιμοποιώντας μόνο ένα σκουριασμένο κουτάλι. Δεν υπάρχει CSS, δεν υπάρχει responsive layout της προκοπής (αν κάνεις resize το παράθυρο, τα widgets συμπεριφέρονται λες και τα χτύπησε σεισμός 8 ρίχτερ), και η διαχείριση των χρωμάτων θυμίζει εποχές που οι οθόνες είχαν 256 χρώματα. Αν αφήναμε τα default γκρίζα κουμπιά και τις γραμματοσειρές που μοιάζουν με terminal του MS-DOS, ο καθηγητής θα πάθαινε κατάθλιψη πριν καν δει τη λειτουργικότητα.
Έτσι, γεννήθηκε το Aegean Theme.
| Στοιχείο UI | Χρώμα (HEX) | Σκοπός |
|---|---|---|
| Sidebar & Τίτλοι | #0A3D62 (Deep Greek Blue) | Σταθερότητα & Οργάνωση |
| Κουμπιά & Accent | #1E90FF (Aegean Blue) | Call to Action & Focus |
| Φόντο Panel | #F7F9FC (Ice White) | Ξεκούραστο διάβασμα |
| Alerts & Errors | #E74C3C / #2ECC71 | Instant feedback |
Το UI απέκτησε χαρακτήρα. Δεν ήταν πια "ένα Tkinter app". Ήταν το RandeBoo, με καθαρό layout, σωστό spacing, flat modern design και μια αισθητική τόσο premium που αν τη βλέπανε στην Apple θα μας έπαιρναν με συνοπτικές διαδικασίες για να σχεδιάσουμε το επόμενο macOS (εντάξει, ας μην υπερβάλλουμε, αλλά για τα δεδομένα του Tkinter ήταν ένα μικρό αισθητικό θαύμα).
SPA Routing: Single Page Architecture στο Tkinter (aka Η Πατέντα του Αιώνα)
Όταν ο συμφοιτητής μου είπε «Έλα μωρέ, πέτα εκεί 5-6 αυτόνομα Toplevel παράθυρα να ανοιγοκλείνουν, ποιος θα το καταλάβει;», ένιωσα ένα ρίγος να με διαπερνά. Το Tkinter, αν το αφήσεις στην τύχη του, μπορεί να μετατρέψει την οθόνη σου σε desktop από adware των Windows Millennium μέσα σε 5 δευτερόλεπτα πλοήγησης. Όχι, κύριοι. Εδώ είμαστε για να κάνουμε software engineering, όχι να πουλάμε CD-keys σε στέκια του Ζαππείου.
Επειδή λοιπόν στο DNA μου κυλάει React (είμαι εραστής του modern web), πήρα την απόφαση να φέρω το SPA (Single Page Application) routing στον κόσμο του Tkinter. Ναι, καλά διαβάσατε. SPA στο Tkinter, χωρίς Router DOM, χωρίς web browsers, μόνο με ωμό Python κώδικα και ακατέργαστο πείσμα.
Σχεδίασα έναν custom Router στο [gui_main.py] (το όνομα του αρχείου είναι, τουλάχιστον, αστείο). Η λογική είναι απλή αλλά δολοφονική: Ένα dictionary mapping που αντιστοιχεί strings με views (functions που ζωγραφίζουν τα panels), και ένας dynamic καθαριστής frame. Όταν πατάς ένα κουμπί στο sidebar, ο router ρίχνει ένα αόρατο αλλά αμείλικτο widget.destroy() σε όλα τα widgets του τρέχοντος panel και σχεδιάζει το επόμενο view ακαριαία, πάνω στο ίδιο κεντρικό frame. Το transition είναι τόσο ομαλό που αν κλείσεις τα μάτια σου, νομίζεις ότι τρέχεις Next.js App Router στα 120Hz.
def open_view(self, view_name: str) -> None:
# Έλεγχος αν η σελίδα που ζήτησε ο χρήστης υπάρχει στο routing map
if view_name not in self.views:
logging.warning(f"Unknown view name requested: {view_name}")
return
# Καθάρισμα της οθόνης από το προηγούμενο view για να μην γίνει μπάχαλο
self._clear_content()
# Δυναμική κλήση της function που σχεδιάζει το νέο panel
self.views[view_name]()
def _clear_content(self) -> None:
# Διαγραφή όλων των ενεργών widgets στο κεντρικό frame
# Με αυτόν τον τρόπο αποφεύγουμε τα memory leaks και το stackάρισμα των panels
for widget in self.content_frame.winfo_children():
widget.destroy()Καθαρό, ακαριαίο view-switching χωρίς memory leaks, που θα έκανε ακόμη και τους React developers να δακρύσουν από συγκίνηση (ή από απόγνωση για το πώς καταλήξαμε να γράφουμε SPA σε Tkinter).
Ο Αόρατος Ήρωας: backup.py (aka Το Καταφύγιο από Ραδιενεργά Drops)
Στις επίσημες οδηγίες της εργασίας, η λέξη «backup» δεν υπήρχε ούτε ως υποσημείωση. Αλλά αν έχεις περάσει έστω και μία εβδομάδα γράφοντας κώδικα για παραγωγικό περιβάλλον, ξέρεις καλά ένα πράγμα: ο χρήστης (και ο συμφοιτητής) έχει την υπερδύναμη να καταστρέψει τη βάση δεδομένων με τρόπους που η επιστήμη δεν μπορεί ακόμα να εξηγήσει. Ένα «τυχαίο» alt-F4 την ώρα που γράφεται ένα query, ή ένα πείραμα με ωμό SQL κώδικα, και αντίο δεδομένα. Για εμάς, η απώλεια δεδομένων είναι το προπατορικό αμάρτημα.
Έτσι, αποφάσισα να αφιερώσω $6$ ολόκληρες ώρες (ώρες που θα μπορούσα να είχα κοιμηθεί ή να είχα πιει άλλον έναν Freddo Espresso) για να γράψω έναν αόρατο, αυτόνομο φύλακα άγγελο: το backup.py.
Η λογική του; Απλή αλλά αμείλικτη. Κάθε φορά που το gui_main.py παίρνει μπρος, το backup.py ξυπνάει αθόρυβα στο background. Κοιτάζει τη βάση δεδομένων randeboo.db, παίρνει ένα ακριβές snapshot με timestamp στο όνομα (π.χ. backup_20260531_2151.db) και το αποθηκεύει σε έναν ειδικό φάκελο /backups.
Φυσικά, επειδή δεν θέλουμε ο δίσκος του χρήστη να γεμίσει με gigabytes από άχρηστα snapshots μετά από μερικές εβδομάδες χρήσης, υλοποίησα έναν αλγόριθμο rotation: το σύστημα κρατάει πάντα τα 5 πιο πρόσφατα αρχεία backup και διαγράφει τα παλαιότερα χωρίς οίκτο.
Ο κώδικας γράφτηκε με καθαρό Pythonic τρόπο, με εκτενή σχόλια στα ελληνικά:
import os
import shutil
import glob
from datetime import datetime
def perform_db_backup(db_path: str, backup_dir: str, max_backups: int = 5) -> None:
# Δημιουργία του φακέλου backups αν δεν υπάρχει ήδη
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
# Παραγωγή timestamp για το όνομα του αρχείου
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_filename = f"randeboo_backup_{timestamp}.db"
destination_path = os.path.join(backup_dir, backup_filename)
# Αντιγραφή της SQLite βάσης στο φάκελο backups
try:
shutil.copy2(db_path, destination_path)
except IOError as e:
print(f"Σφάλμα κατά τη δημιουργία του backup: {e}")
return
# Διαχείριση rotation: κρατάμε μόνο τα τελευταία max_backups αρχεία
backup_pattern = os.path.join(backup_dir, "randeboo_backup_*.db")
existing_backups = sorted(glob.glob(backup_pattern))
# Αν ξεπεράσαμε το όριο, σβήνουμε τα παλαιότερα
while len(existing_backups) > max_backups:
oldest_backup = existing_backups.pop(0)
try:
os.remove(oldest_backup)
except OSError as e:
print(f"Αδυναμία διαγραφής παλαιού backup {oldest_backup}: {e}")Αυτό το μικρό αρχειάκι ήταν η ασπίδα μας. Κατά τη διάρκεια των δοκιμών, όταν ένας συμφοιτητής αποφάσισε να κάνει drop έναν πίνακα «για να δει τι θα γίνει», το backup.py μας έσωσε από εγκεφαλικό επεισόδιο μέσα σε 3 δευτερόλεπτα. data loss avoided. Saint Linus approved.
Η Τελική Ευθεία, το Φάντασμα ο Κάσπερ και το "Δημιουργικό" Copy-Paste
Όπως σε κάθε release που σέβεται τον εαυτό του, οι τελευταίες μέρες πριν την παράδοση ήταν ένα sprint επιβίωσης. Και κάπου εκεί, η πραγματικότητα της ομαδικής εργασίας μάς χτύπησε την πόρτα.
Όλοι ξέρουμε ότι σε κάθε πανεπιστημιακή ομάδα υπάρχει τουλάχιστον ένα μέλος που είναι πιο αόρατο και από το φαντασματάκι τον Κάσπερ. Άνθρωποι που «ανέλαβαν» κρίσιμα κομμάτια του κώδικα, αλλά εξαφανίστηκαν στις σκιές, αφήνοντας πίσω τους μόνο σιωπή στα Viber groups. Όταν συνειδητοποίησα ότι το module των Υπαλλήλων (gui_employees.py) —το οποίο έπρεπε να παραδοθεί... χθες— ήταν ακόμα μια κενή σελίδα, κατάλαβα ότι έπρεπε να κάνω το δικό μου coding sprint.
Αντί για over-engineering και βαθυστόχαστες αναλύσεις, εφάρμοσα την ιερή, πανάρχαια τέχνη του DRY (ή μάλλον, του Copy-Paste-Refactor). Πήρα το ήδη δοκιμασμένο και debugged logic από το Customer module, άλλαξα τα labels, προσάρμοσα τα queries στη SQLite, έκανα μερικές διορθώσεις στα frames και μέσα σε $3$ ώρες (συνοδεία διπλού Freddo Espresso) είχαμε ένα πλήρως λειτουργικό Employee panel. Ήταν η στιγμή που ο «Κάσπερ» θα ήταν περήφανος για τη δουλειά του... αν τη διάβαζε ποτέ.
Το δεύτερο μεγάλο στοίχημα της τελικής ευθείας ήταν η αποστολή email (email_service.py). Εδώ υπήρχε τεχνικό θέμα: αν έστελνες το email σύγχρονα (synchronously) μέσα από το UI thread, το SMTP handshake με τους servers της Google χρειαζόταν $2$-$3$ δευτερόλεπτα. Στον κόσμο των χρηστών, $3$ δευτερόλεπτα «παγωμένης» οθόνης σημαίνουν «η εφαρμογή χάλασε, ας την κλείσω».
Η λύση; Multi-threading. Υλοποίησα την αποστολή ασύγχρονα, στέλνοντας το SMTP request σε ξεχωριστό thread. Στο live demo, ο χρήστης πατούσε «Επιβεβαίωση Ραντεβού», το UI συνέχιζε να ανταποκρίνεται ακαριαία, και το email έσκαγε στο inbox σε λιγότερο από $3$ δευτερόλεπτα από ένα demo Gmail account με App Passwords. Μαγεία.
Το Μεγάλο Συμπέρασμα: Από Code Monkey σε Architect (και ένα μάθημα ζωής)
Αυτό το project ήταν μια άσκηση ωριμότητας, όχι μόνο σε τεχνικό επίπεδο αλλά και σε ανθρώπινο. Με έκανε να συνειδητοποιήσω κάτι σημαντικό για την πορεία μου: Με ελκύει περισσότερο η αρχιτεκτονική σκέψη παρά η απλή πληκτρολόγηση κώδικα. Με νοιάζει το πώς στήνεται ένα σύστημα, πώς επικοινωνούν τα modules, πώς διασφαλίζεται η επεκτασιμότητα χωρίς ο κώδικας να γίνει «σπαγγέτι». Το coding είναι το εργαλείο. Ο σχεδιασμός είναι η τέχνη.
Όμως, το σημαντικότερο μάθημα ήρθε από τη διαχείριση της ομάδας. Συνειδητοποίησα ότι για να πετύχει ένα project, δεν είναι απαραίτητο να είναι όλοι οι συνεργάτες σου senior developers με χρόνια εμπειρίας. Αυτό που πραγματικά μετράει είναι η διάθεση για δουλειά και ένα ανοιχτό μυαλό. Ένα μυαλό έτοιμο να ακούσει, να καταλάβει τι γίνεται, πώς γίνεται, και κυρίως, να αμφισβητήσει αυτά που νομίζει ότι γνωρίζει.
Στον κόσμο του software engineering (αλλά και στη ζωή), το να μένεις κολλημένος σε «βεβαιότητες» είναι ο πιο σίγουρος δρόμος προς την αποτυχία. Όπως έλεγαν και οι αρχαίοι ημών πρόγονοι —και ταιριάζει γάντι στο δικό μας Leap of Faith— «έν οἶδα ὅτι οὐδὲν οἶδα». Όταν πραγματικά εξελίσσεσαι ως developer, καταλαβαίνεις ότι όσο περισσότερα μαθαίνεις, τόσο συνειδητοποιείς πόσα λίγα ξέρεις. Και αυτή η παραδοχή είναι η αρχή της σοφίας (και του καλού debugging).
Το RandeBoo V1.0 είναι πλέον έτοιμο. Ήταν δύσκολο, ήταν κουραστικό, αλλά το «ship a product» συναίσθημα είναι απλά εθιστικό.
==================================================================== RANDEBOO SYSTEM COMPLIANCE REPORT - v1.0.0-RC1
[+] SQLite DB Connector :: OK (Μηχανισμός Αντικυθήρων ενεργός) [+] SPA Router System :: OK (React developers δακρύζουν βουβά) [+] Aegean UI Palette :: OK (Η Apple ενημερώθηκε ανεπίσημα) [+] Backup Daemon :: OK (Casper-proof rotation active) [+] SMTP Email Thread Handler :: OK (SMTP queue αποσυζευγμένο από UI) [+] Saint Linus Blessing :: ACTIVE (Τα queries κρατάνε γερά)
[SYSTEM METRICS & TELEMETRY] [i] Freddo Espresso Injected :: 148.5 Λίτρα (Core temp stable) [i] Casper Detection Rate :: 100% (Viber response: NaN) [i] Sanity Level :: [■□□□□□□□□□] 8.32% (CRITICAL_WARNING) [i] Floppy Disk Interceptor :: ACTIVE (Αποκλεισμός δισκετών 3.5")
[DEPLOYMENT ENGINE]
Status :: READY FOR SHIPMENT Verdict :: LEAP OF FAITH INITIATED ====================================================================