window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster υπάρχει ήδη') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Αρχή "ανοικτό-κλειστό". Πρέπει ποτέ να τη χρησιμοποιήσω; - The Codest
The Codest
  • Σχετικά με εμάς
  • Υπηρεσίες
    • Ανάπτυξη λογισμικού
      • Ανάπτυξη Frontend
      • Backend Ανάπτυξη
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Μηχανικοί δεδομένων
      • Μηχανικοί cloud
      • Μηχανικοί QA
      • Άλλα
    • Συμβουλευτική
      • Έλεγχος & Συμβουλευτική
  • Βιομηχανίες
    • Fintech & Τραπεζική
    • E-commerce
    • Adtech
    • Healthtech
    • Κατασκευή
    • Εφοδιαστική
    • Αυτοκίνητο
    • IOT
  • Αξία για
    • CEO
    • CTO
    • Διευθυντής παράδοσης
  • Η ομάδα μας
  • Case Studies
  • Μάθετε πώς
    • Blog
    • Συναντήσεις
    • Διαδικτυακά σεμινάρια
    • Πόροι
Καριέρα Ελάτε σε επαφή
  • Σχετικά με εμάς
  • Υπηρεσίες
    • Ανάπτυξη λογισμικού
      • Ανάπτυξη Frontend
      • Backend Ανάπτυξη
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Μηχανικοί δεδομένων
      • Μηχανικοί cloud
      • Μηχανικοί QA
      • Άλλα
    • Συμβουλευτική
      • Έλεγχος & Συμβουλευτική
  • Αξία για
    • CEO
    • CTO
    • Διευθυντής παράδοσης
  • Η ομάδα μας
  • Case Studies
  • Μάθετε πώς
    • Blog
    • Συναντήσεις
    • Διαδικτυακά σεμινάρια
    • Πόροι
Καριέρα Ελάτε σε επαφή
Πίσω βέλος GO BACK
2019-07-02
Ανάπτυξη λογισμικού

Αρχή "ανοικτό-κλειστό". Πρέπει ποτέ να τη χρησιμοποιήσω;

Mateusz Lesniak

Οι περισσότεροι προγραμματιστές έχουν ακούσει για την αρχή ανοιχτό - κλειστό - μία από τις αρχές SOLID του θείου Bob. Ακούγεται λογικό, αλλά μπορεί να είναι ακόμα λίγο θολό μέχρι την πρώτη χρήση σε "ζωντανό" κώδικα. Η πλήρης κατάσταση της αρχής είναι: οι οντότητες λογισμικού (κλάσεις, ενότητες, συναρτήσεις κ.λπ.) πρέπει να είναι ανοικτές για επέκταση, αλλά κλειστές για τροποποίηση.

Τι σημαίνει λοιπόν πραγματικά;

Αντιμετωπίσαμε ένα πρόβλημα ανάπτυξης που μας έδειξε τι πραγματικά σημαίνει η αρχή "ανοικτό-κλειστό". Σε μια από τις διαδικτυακές μας εφαρμογές είχαμε μια φόρμα με δύο τμήματα (μεταξύ άλλων):

  • κανάλια ζήτησης
  • δυναμικά φίλτρα

Οι χρήστες μπορούν να προσθέσουν όσα φίλτρα επιθυμούν, αλλά υπάρχουν ορισμένοι κανόνες - η διαθεσιμότητα των φίλτρων εξαρτάται από τα επιλεγμένα κανάλια.

Κανάλια ζήτησης: ADΑΝΤΑΛΛΑΓΉ, ΕΠΙΚΕΦΑΛΊΔΑΠΡΟΣΦΟΡΑ, ΚΡΑΤΗΣΗ, ΑΛΛΑ Δυναμικά φίλτρα (διαστάσεις): ιστοσελίδα, διαφήμισημονάδα, geo, δημιουργικήμέγεθος, συσκευή

Αυτό το άρθρο αφορά κυρίως την αναδιαμόρφωση κώδικα, οπότε θα υπάρχουν πολλά αποσπάσματα κώδικα παρακάτω. Προσπάθησα να τα μειώσω, αλλά κάποια ποσότητα κώδικα είναι απαραίτητη για να δείξω αναδιοργάνωση κώδικα. Δεν χρειάζεται να καταλαβαίνετε κάθε μικρό μέρος του κώδικα για να κατανοήσετε την κύρια ιδέα.

Η πρώτη εφαρμογή του προβλήματος ήταν απλή:

class ResearchFormStateUpdater {
  update () {
    (...)
    this._updateDynamicFilters(),
  }

  _updateDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:disableWebsites', this._shouldDisableWebsitesFields()),
    });
  }

  _shouldDisableWebsitesFields () {
    return this._shouldDisableFields(ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS),
  }

  _shouldDisableFields (disablingDemandChannels) {
    // έχει τσεκαριστεί κάποιο από τα disablingDemandChannels ?
  }
}

ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other'],

class ResearchDynamicFilter {
  _setDynamicFilterDisableWebsitesEvent () {
    $(this._getBody()).on('dynamicFilter:disableWebsites', (event, shouldDisableWebsites) => {
      // απενεργοποίηση των φίλτρων ιστότοπων
    });
  }
}

Όπως μπορείτε να δείτε, το φίλτρο του ιστότοπου υποτίθεται ότι δεν είναι διαθέσιμο για το HEADERΠΡΟΣΦΟΡΑ, ΚΡΑΤΗΣΗ και ΑΛΛΑ κανάλια, έτσι ώστε να είναι διαθέσιμο μόνο για ADΚανάλι ΑΝΤΑΛΛΑΓΗΣ.

Το τελευταίο πράγμα που μπορείτε να πείτε για τον κώδικα είναι ότι είναι μόνιμος ή στατικός. Έτσι, έχουμε περισσότερα αιτήματα από τον πελάτη μας που κάνουν αυτές τις κλάσεις μεγαλύτερες και πιο πολύπλοκες.

Ανάπτυξη χαρακτηριστικών

  • Προσθέστε ένα άλλο κανάλι - EBDA (Το φίλτρο του ιστοτόπου δεν θα πρέπει να είναι διαθέσιμο όσο έχει επιλεγεί το EBDA):

    • επεκτείνετε το DISABLING_DEMAND_CHANNELS από το κανάλι ζήτησης EBDA
    • πολλές αλλαγές ονομάτων - στην πρώτη υλοποίηση, προσδιορίσαμε τον ιστότοπο σε ονόματα μεθόδων και σταθερών. Για παράδειγμα:

      • isWebsitesDimensionDisabled προς _areFormStateDimensionsDisabled
      • ΙΣΤΟΣΕΛΊΔΑ_ΑΠΕΝΕΡΓΟΠΟΊΗΣΗ_ΚΑΝΑΛΙΏΝ_ΖΉΤΗΣΗΣ στο DISABLING_DEMAND_CHANNELS

Συναγερμός -> όταν ένα συστατικό είναι ανοιχτό για αλλαγές, θα αλλάξουν πολλά ονόματα στο μέλλον. Δεν θα δώσουμε σημασία σε αυτό στα επόμενα βήματα.

  • Προσθέστε ένα άλλο φίλτρο για το 'Προϊόν' (Προϊόν το σύστημα διαθεσιμότητας φίλτρων είναι το ίδιο με αυτό του ιστοτόπου)

    • ResearchDynamicFilter η κλάση πρέπει να ελέγχει για μια ακόμη διάσταση κατά την απενεργοποίηση/ενεργοποίηση πεδίων
  • Ας πάμε πιο μακριά και ας προσθέσουμε κάποιο switcher πάνω από τα κανάλια -> 'Source'. Όλα τα κανάλια ζήτησης που είχαμε μέχρι τώρα βρίσκονται στην πηγή Ad Manager. Η νέα πηγή - SSP - δεν έχει κανάλια ζήτησης και το μόνο διαθέσιμο φίλτρο είναι η ιστοσελίδα.

    • Κανόνες:

      • Υπάρχουν δύο καταστάσεις της πηγής: SSP.
      • Όλα τα κανάλια ζήτησης είναι διαθέσιμα μόνο για την πηγή Ad Manager.
      • Δεν υπάρχουν κανάλια ζήτησης για την πηγή SSP
      • Το 'Website' είναι το μόνο διαθέσιμο φίλτρο για την πηγή SSP.
    • Εφαρμογή:

      • Όταν επιλέγεται η επιλογή "SSP":

        • Απενεργοποιήστε τα κανάλια ζήτησης.
        • σκανδάλη 'dynamicFilter:disableWebsitesAndProducts' <- ενεργοποιήστε και τα δύο
        • σκανδάλη 'dynamicFilter:disableNonSspOptions'
      • Όταν ελέγχεται ο Διαχειριστής διαφημίσεων:

        • σκανδάλη 'dynamicFilter:disableWebsitesAndProducts' <- έλεγχος καιρού ενεργοποιημένος ή απενεργοποιημένος
  • Προσθέστε ένα άλλο φίλτρο για την 'Πλατφόρμα'

    • Κανόνες:

      • Η πλατφόρμα είναι διαθέσιμη μόνο όταν η πηγή είναι SSP
    • Δυσκολία:

      • Τώρα έχουμε την 'Ιστοσελίδα', η οποία είναι διαθέσιμη για το κανάλι AD_EXCHANGE από το Ad Manager και για το Ssp και έχουμε την 'Πλατφόρμα', η οποία είναι διαθέσιμη για το Ssp αλλά όχι για το Ad Manager.
      • Εναλλαγή η κατάσταση του εντύπου μπορεί να γίνει πραγματικά δύσκολη και συγκεχυμένη

Εφαρμογή με νέα λειτουργικότητα:

Σας παρουσιάζω το επόμενο απόσπασμα κυρίως για να δείξω την πολυπλοκότητα του κώδικα. Μπορείτε να το αφήσετε κρυφό.

class ResearchFormStateUpdater {
  update () {
    (...)
    this._triggerCallbacks(),
  }

  _triggerCallbacks () {
    // επιλέγουμε τα callbacks ανάλογα με την πηγή
  }

  _adManagerSourceCallbacks () {
    (...)
    this._enableDemandChannels(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS),
    this._updateDefaultStateOfDynamicFilters(),
    this._updateAdManagerDynamicFilters(),
  }

  _sspSourceCallbacks () {
    (...)
    this._removeDemandChannelsActiveClassAndDisable(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS),
    this._updateDefaultStateOfDynamicFilters(),
  }

  _updateDefaultStateOfDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:enableSspFilters', this.isSourceSsp),
    });
  }

  _updateAdManagerDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:disableWebsitesAndProducts', this._areFormStateDimensionsDisabled() && !this.isSourceSsp),
    });
  }

  _shouldDisableFields (disablingDemandChannels) {
    // ελέγχεται οποιοδήποτε από τα disablingDemandChannels
  }
}

ResearchFormStateUpdater.AD_MANAGER_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other', 'ebda'],

class ResearchDynamicFilter {
  // Δεν απλοποίησα το σώμα αυτών των δύο μεθόδων για να δείξω την πολυπλοκότητα της τρέχουσας υλοποίησης

  _setDefaultDynamicFiltersToggleEvent () {
    $(this._getBody()).on('dynamicFilter:enableSspFilters', (event, shouldEnableSspOptions) => {
      this._setDefaultFiltersOptionDisabledState(shouldEnableSspOptions),

      const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val(),
      if (selectedFilterDimension === 'website') {
        this._toggleChosenFilterDisabledState(false),
      } else if (selectedFilterDimension === 'platform') {
        this._toggleChosenFilterDisabledState(!shouldEnableSspOptions),
      } else {
        this._toggleChosenFilterDisabledState(shouldEnableSspOptions),
      }
    });
  }

  _setDynamicFilterDisableWebsitesAndProductsEvent () {
    $(this._getBody()).on('dynamicFilter:disableWebsitesAndProducts', (event, shouldDisableWebsitesAndProducts) => {
      const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val(),
      if ($.inArray(selectedFilterDimension, ['website', 'product']) >= 0) {
        this._toggleChosenFilterDisabledState(shouldDisableWebsitesAndProducts),
      }
      this._setMethodSelectWebsiteAndProductOptionDisabledState(shouldDisableWebsitesAndProducts),
    });
  }

  _toggleNonSspFilters (dimensionSelect, shouldDisable) {
    $.each(ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS, (_, option) => {
      // εναλλαγή της κατάστασης του φίλτρου ανάλογα με το 'shouldDisable'
    });
  }
}

ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'],

Χρησιμοποιούμε ακόμα κάποια 'toggle' μηχανισμό. Είναι πραγματικά δύσκολο να αλλάξετε 4 μοχλούς και να φτάσετε στην αναμενόμενη κατάσταση και τώρα το DynamicFilter πρέπει να γνωρίζει, ποιες διαστάσεις δεν είναι για την πηγή ssp.

Έχουμε τον ResearchFormStateUpdater, γιατί να μην είναι αυτός υπεύθυνος;

Τελικό αίτημα

Προσθέστε ένα άλλο φίλτρο για το 'Yield partner'

Αυτή είναι η ακριβής στιγμή που αποφασίσαμε να αναδιαμορφώσουμε αυτές τις κλάσεις. Τα κανάλια και τα φίλτρα που αναλύονται είναι μόνο ένα μικρό μέρος του προβλήματος. Υπάρχουν πολλά τμήματα φόρμας εδώ και όλα τους έχουν το ίδιο πρόβλημα. Το refactor μας θα πρέπει να εξουδετερώνει την ανάγκη για αλλαγή εσωτερικών μεθόδων αυτών των κλάσεων *για* να προσθέσουμε κάποια νέα κανάλια ή διαστάσεις.

Στο επόμενο απόσπασμα, άφησα τις κύριες κλάσεις σχεδόν όπως είναι στον παραγωγικό μας κώδικα για να σας δείξω πόσο εύκολα κατανοητές είναι τώρα.

class ResearchFormStateUpdater {
  update () {
    (...)
    this._updateDynamicFilters(),
  }

  _updateDynamicFilters () {
    this._toggleAllDynamicFiltersState(this._dynamicFiltersDimensionsToBeDisabled()),
  }

  _dynamicFiltersDimensionsToBeDisabled () {
    if (this.isSourceSsp) { return ResearchFormStateUpdater.NO_SSP_FILTERS; }

    var disabledFilters = ResearchFormStateUpdater.ONLY_SSP_FILTERS,
    if (this.areDemandChannelsExceptAdxSelected) {
      disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_ADX_FILTERS),
    }
    return disabledFilters,
  }

  _toggleAllDynamicFiltersState (disabledFilters) {
    $('.dynamic-filter').each((_, filter) => {
      this._toggleDynamicFilterState(filter, disabledFilters),
    });
  }

  _toggleDynamicFilterState (dynamicFilter, disabledFilters) {
    $(dynamicFilter).trigger('dynamicFilter:toggleDynamicFilters', disabledFilters),
  }
}

ResearchFormStateUpdater.NO_SSP_FILTERS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'],

ResearchFormStateUpdater.ONLY_SSP_FILTERS = ['platform'],

ResearchFormStateUpdater.ONLY_ADX_FILTERS = ['website', 'product'],

class ResearchDynamicFilter {
  _setDynamicFiltersToggleEvent () {
    $(this._getBody()).on('dynamicFilter:toggleDynamicFilters', (event, disabledFilters) => {
      this._disableFilters(disabledFilters.split(',')),
      this._enableFilters(disabledFilters.split(','))),
    });
  }

  _disableFilters (filtersToDisable) {
    // disable filtersToDisable
  }

  _enableFilters (filtersToDisable) {
    const filtersToEnable = $(ResearchDynamicFilter.ALL_FILTERS).not(filtersToDisable).get(),
    // Ενεργοποίηση των filtersToEnable
  }
}

ResearchDynamicFilter.ALL_FILTERS = ['website', 'ad_unit', 'creative_size', 'geo', 'device', 'product', 'platform'],

Τα καταφέραμε! Τα καταφέραμε;

Τώρα το μόνο πράγμα που πρέπει να γνωρίζει το 'ResearchDynamicFilter' είναι μια λίστα με όλα τα φίλτρα - φαίνεται δίκαιο. Η υπόλοιπη λογική και ο έλεγχος έρχονται από πάνω - κάποιες ανώτερες μέθοδοι και σταθερές.

Ας δοκιμάσουμε λοιπόν τη νέα μας δομή προσθέτοντας ένα φίλτρο για το 'Yield_partner':

class ResearchFormStateUpdater {
  _dynamicFiltersDimensionsToBeDisabled () {
    (...)
    if (this.areDemandChannelsExceptEbdaSelected) {
      disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_EBDA_FILTERS),
    }
    return disabledFilters,
  }
}

ResearchFormStateUpdater.NO_SSP_FILTERS = [(...), 'yield_partner'],

ResearchFormStateUpdater.ONLY_EBDA_FILTERS = [(...), 'yield_partner'],

ResearchDynamicFilter.ALL_FILTERS = [(...), 'yield_partner'],

Όπως μπορείτε να δείτε, πρόκειται για την προσθήκη κάποιων τιμών σε σταθερές και κάποιες πρόσθετες συνθήκες.

Χάρη στην "αρχή του ανοικτού-κλειστού" είμαστε σε θέση να αλλάξουμε την επιχειρησιακή λογική της φόρμας προσθέτοντας μόνο μερικές τιμές και συνθήκες σε ένα υψηλότερο επίπεδο αφαίρεσης. Δεν χρειάζεται να μπούμε στο εσωτερικό του συστατικού και να αλλάξουμε οτιδήποτε. Αυτό το refactor επηρέασε ολόκληρη τη φόρμα και υπήρχαν περισσότερα τμήματα και όλα υπακούουν πλέον στην αρχή open-closed.

Δεν μειώσαμε την ποσότητα του κώδικα - στην πραγματικότητα, την αυξήσαμε (πριν/μετά):

  • ResearchFormStateUpdater - 211/282 γραμμές
  • ResearchDynamicFilter - 267/256 γραμμές

Πρόκειται για τη συλλογή σε σταθερές -> είναι η δημόσια διεπαφή μας τώρα, η κονσόλα μας για τον έλεγχο της διαδικασίας χωρίς δεκάδες διακόπτες.

Διαβάστε επίσης:

  • Πώς να γράψετε έναν καλό και ποιοτικό κώδικα;
  • 1TP57Ημερολόγιο. Ένα νέο έργο του Codest που βασίζεται στο Vue.js
  • Τι είναι το Ruby on Jets και πώς να δημιουργήσετε μια εφαρμογή χρησιμοποιώντας το;

Σχετικά άρθρα

Ανάπτυξη λογισμικού

Κατασκευάστε μελλοντικά ασφαλείς εφαρμογές Web: γνώσεις από την ομάδα εμπειρογνωμόνων του The Codest

Ανακαλύψτε πώς η The Codest υπερέχει στη δημιουργία κλιμακούμενων, διαδραστικών εφαρμογών ιστού με τεχνολογίες αιχμής, παρέχοντας απρόσκοπτη εμπειρία χρήστη σε όλες τις πλατφόρμες. Μάθετε πώς η τεχνογνωσία μας οδηγεί στον ψηφιακό μετασχηματισμό και την επιχειρηματική...

THECODEST
Ανάπτυξη λογισμικού

Top 10 εταιρείες ανάπτυξης λογισμικού με έδρα τη Λετονία

Μάθετε για τις κορυφαίες εταιρείες ανάπτυξης λογισμικού της Λετονίας και τις καινοτόμες λύσεις τους στο τελευταίο μας άρθρο. Ανακαλύψτε πώς αυτοί οι τεχνολογικοί ηγέτες μπορούν να βοηθήσουν στην ανύψωση της επιχείρησής σας.

thecodest
Λύσεις Enterprise & Scaleups

Βασικά στοιχεία ανάπτυξης λογισμικού Java: Α Guide to Outsourcing Successfully (Οδηγός για την επιτυχή εξωτερική ανάθεση)

Εξερευνήστε αυτόν τον βασικό οδηγό για την επιτυχή ανάπτυξη λογισμικού outsourcing Java για να αυξήσετε την αποδοτικότητα, να αποκτήσετε πρόσβαση στην τεχνογνωσία και να οδηγήσετε την επιτυχία των έργων με The Codest.

thecodest
Ανάπτυξη λογισμικού

Ο απόλυτος οδηγός για το Outsourcing στην Πολωνία

Η έξαρση της outsourcing στην Πολωνία οφείλεται στις οικονομικές, εκπαιδευτικές και τεχνολογικές εξελίξεις, που ευνοούν την ανάπτυξη της πληροφορικής και το φιλικό προς τις επιχειρήσεις κλίμα.

TheCodest
Λύσεις Enterprise & Scaleups

Ο πλήρης οδηγός εργαλείων και τεχνικών ελέγχου πληροφορικής

Οι έλεγχοι ΤΠ διασφαλίζουν ασφαλή, αποτελεσματικά και συμβατά συστήματα. Μάθετε περισσότερα για τη σημασία τους διαβάζοντας ολόκληρο το άρθρο.

The Codest
Jakub Jakubowicz CTO & Συνιδρυτής

Εγγραφείτε στη βάση γνώσεών μας και μείνετε ενήμεροι για την τεχνογνωσία από τον τομέα της πληροφορικής.

    Σχετικά με εμάς

    The Codest - Διεθνής εταιρεία ανάπτυξης λογισμικού με κέντρα τεχνολογίας στην Πολωνία.

    Ηνωμένο Βασίλειο - Έδρα

    • Γραφείο 303B, 182-184 High Street North E6 2JA
      Λονδίνο, Αγγλία

    Πολωνία - Τοπικοί κόμβοι τεχνολογίας

    • Πάρκο γραφείων Fabryczna, Aleja
      Pokoju 18, 31-564 Κρακοβία
    • Πρεσβεία του εγκεφάλου, Konstruktorska
      11, 02-673 Βαρσοβία, Πολωνία

      The Codest

    • Αρχική σελίδα
    • Σχετικά με εμάς
    • Υπηρεσίες
    • Case Studies
    • Μάθετε πώς
    • Καριέρα
    • Λεξικό

      Υπηρεσίες

    • Συμβουλευτική
    • Ανάπτυξη λογισμικού
    • Backend Ανάπτυξη
    • Ανάπτυξη Frontend
    • Staff Augmentation
    • Backend Developers
    • Μηχανικοί cloud
    • Μηχανικοί δεδομένων
    • Άλλα
    • Μηχανικοί QA

      Πόροι

    • Γεγονότα και μύθοι σχετικά με τη συνεργασία με εξωτερικό συνεργάτη ανάπτυξης λογισμικού
    • Από τις ΗΠΑ στην Ευρώπη: Γιατί οι αμερικανικές νεοσύστατες επιχειρήσεις αποφασίζουν να μετεγκατασταθούν στην Ευρώπη
    • Σύγκριση υπεράκτιων κόμβων ανάπτυξης τεχνολογίας: Ευρώπη (Πολωνία), ASEAN (Φιλιππίνες), Ευρασία (Τουρκία)
    • Ποιες είναι οι κορυφαίες προκλήσεις των CTOs και των CIOs;
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Πνευματικά δικαιώματα © 2025 από The Codest. Όλα τα δικαιώματα διατηρούνται.

    elGreek
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek