Εισαγωγή
Το SOLID είναι ένα μνημονικό ακρωνύμιο για πέντε αρχές σχεδίασης αντικειμενοστρεφούς προγραμματισμού που εισήχθησαν από τον Robert C. Martin, ο οποίος είναι ευρύτερα γνωστός ως Uncle Bob. Αυτές οι αρχές έχουν σκοπό να βοηθήσουν τους σχεδιαστές λογισμικού, τους αρχιτέκτονες, τους μηχανικούς και τους προγραμματιστές να δημιουργήσουν πιο ευέλικτο, συντηρήσιμο και επεκτάσιμο λογισμικό. Ακολουθώντας αυτές τις αρχές, μπορείτε να σχεδιάσετε κλάσεις που είναι ευκολότερο να ελεγχθούν, να αναδιαρθρωθούν (refactor), να επαναχρησιμοποιηθούν και να επεκταθούν.
Το ακρωνύμιο SOLID σημαίνει:
S – Αρχή της Μοναδικής Υπευθυνότητας
O – Αρχή του Ανοικτού/Κλειστού
L – Αρχή Υποκατάστασης της Liskov
I – Αρχή του Διαχωρισμού των Διασυνδέσεων
D – Αρχή της Αντιστροφής Εξαρτήσεων
Σε αυτό το άρθρο, θα εξηγήσουμε κάθε αρχή ξεχωριστά για να κατανοήσουμε πώς μπορεί να σας βοηθήσει να γράψετε καλύτερο κώδικα. Επιπλέον, θα προσθέσουμε αποσπάσματα κώδικα για κάθε αρχή για να σας δείξουμε πώς μπορείτε να τις εφαρμόσετε στο προγραμματιστικό σας ταξίδι, καθώς και τι πρέπει να αποφεύγετε στην αρχιτεκτονική καθαρού κώδικα (clean code). Για να παρουσιάσουμε αυτές τις έννοιες, θα χρησιμοποιήσουμε τη Kotlin programming γλώσσα που αναπτύχθηκε από την JetBrains και συνεισφέροντες ανοιχτού κώδικα.
Αρχή της Μοναδικής Υπευθυνότητας
Η Αρχή της Μοναδικής Υπευθυνότητας (SRP) είναι μια αρχή σχεδίασης λογισμικού που ορίζει ότι κάθε κλάση ή ενότητα σε ένα πρόγραμμα θα πρέπει να έχει μια μοναδική, καλά ορισμένη υπευθυνότητα. Αυτό σημαίνει ότι μια κλάση θα πρέπει να έχει μόνο έναν λόγο για να αλλάξει, και θα πρέπει να είναι υπεύθυνη για ένα μόνο μέρος της λειτουργικότητας του προγράμματος.
Δείτε το ακόλουθο απόσπασμα κώδικα ως παράδειγμα του πώς μπορεί να εφαρμοστεί αυτή η αρχή στην Kotlin:
|
1 2 3 4 5 6 7 8 9 |
data class User( val firstName: String, val lastName: String, val email: String ) |
Το data keyword στην Kotlin υποδεικνύει ότι αυτή η κλάση είναι μια κλάση δεδομένων (data class), πράγμα που σημαίνει ότι προορίζεται να κρατά δεδομένα και δεν έχει κάποια πολύπλοκη συμπεριφορά. Χρησιμοποιώντας αυτήν την κλάση δεδομένων, μπορούμε να δημιουργήσουμε μια UserService κλάση για τη διαχείριση χρηστών, όπως φαίνεται παρακάτω:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class UserService { fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } } |
Σε αυτό το παράδειγμα, η UserService κλάση έχει μία μόνο υπευθυνότητα: τη διαχείριση χρηστών σε μια βάση δεδομένων. Κάθε χρήστης αναπαρίσταται από τον κώδικα data class User που μοιραστήκαμε νωρίτερα. Όλες οι μέθοδοι στην UserService κλάση σχετίζονται με αυτήν την υπευθυνότητα και επομένως παρουσιάζουν συνοχή. Αυτό καθιστά την κλάση ευκολότερη στην κατανόηση και τη συντήρηση, καθώς είναι σαφές ότι όλες οι μέθοδοι στην κλάση σχετίζονται με μια μοναδική, καλά ορισμένη εργασία.
Μια μέθοδος που παραβιάζει την Αρχή της Μοναδικής Υπευθυνότητας (SRP) στην UserService κλάση θα ήταν μια μέθοδος που δεν σχετίζεται με την κύρια υπευθυνότητα της κλάσης, η οποία είναι η διαχείριση χρηστών σε μια βάση δεδομένων. Για παράδειγμα, εξετάστε την ακόλουθη παραλλαγή της UserService κλάσης:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class UserService { fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } fun sendEmail(user: User, subject: String, message: String) { // κώδικας για την αποστολή email στον χρήστη } } |
Σε αυτό το παράδειγμα, η sendEmail μέθοδος δεν σχετίζεται με την κύρια ευθύνη της UserService κλάσης, η οποία είναι η διαχείριση χρηστών σε μια βάση δεδομένων. Αυτή η μέθοδος είναι υπεύθυνη για την αποστολή email, κάτι που αποτελεί ξεχωριστό μέλημα από τη διαχείριση χρηστών. Ως αποτέλεσμα, αυτή η μέθοδος παραβιάζει την αρχή SRP, καθώς εισάγει έναν δεύτερο λόγο για την αλλαγή της κλάσης UserService.
Για να συμμορφωθούμε με την Αρχή της Μίας Ευθύνης, θα ήταν καλύτερο να διαχωρίσουμε τη λειτουργικότητα αποστολής email σε μια ξεχωριστή κλάση, όπως μια κλάση EmailService . Αυτό θα επέτρεπε στην κλάση UserService να επικεντρωθεί στην κύρια ευθύνη της για τη διαχείριση χρηστών, και στην κλάση EmailService να επικεντρωθεί στην ευθύνη της για την αποστολή email.
Θα πρέπει να σημειώσετε ότι η Αρχή της Μίας Ευθύνης δεν αφορά τον αριθμό των μεθόδων που έχει μια κλάση, αλλά μάλλον τη συνοχή των μεθόδων και τον σαφή διαχωρισμό των ευθυνών μέσα σε μια κλάση.
Αρχή Ανοιχτού-Κλειστού (Open-Closed Principle)
Η Αρχή Ανοιχτού-Κλειστού (Open-Closed Principle - OCP) είναι μια αρχή σχεδιασμού λογισμικού που ορίζει ότι οι οντότητες λογισμικού (όπως κλάσεις, ενότητες ή συναρτήσεις) θα πρέπει να είναι ανοιχτές για επέκταση, αλλά κλειστές για τροποποίηση. Αυτό σημαίνει ότι θα πρέπει να είναι δυνατή η προσθήκη νέας λειτουργικότητας σε μια κλάση ή ενότητα χωρίς την αλλαγή του υπάρχοντος κώδικά της.
Εξετάστε την κλάση UserService από το προηγούμενο παράδειγμα. Ας υποθέσουμε ότι θέλουμε να προσθέσουμε μια νέα δυνατότητα στην κλάση UserService που να μας επιτρέπει να αναζητούμε χρήστες με βάση τη διεύθυνση email τους. Ένας τρόπος για να γίνει αυτό θα ήταν να προσθέσουμε μια νέα μέθοδο στην κλάση UserService όπως επισημαίνεται στον παρακάτω κώδικα:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class UserService { fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } fun searchUsersByEmail(email: String): List<User> { // κώδικας για την αναζήτηση χρηστών με βάση τη διεύθυνση email } } |
Αυτή η προσέγγιση λειτουργεί, αλλά παραβιάζει την Αρχή Ανοιχτού-Κλειστού, καθώς έπρεπε να τροποποιήσουμε την υπάρχουσα κλάση UserService προκειμένου να προσθέσουμε τη νέα δυνατότητα. Μια καλύτερη προσέγγιση θα ήταν να χρησιμοποιήσουμε κληρονομικότητα ή σύνθεση για να επεκτείνουμε τη λειτουργικότητα της κλάσης UserService χωρίς να τροποποιήσουμε τον κώδικά της.
Για να το επιτύχουμε αυτό, θα μπορούσαμε να δημιουργήσουμε μια νέα κλάση με όνομα UserSearchService που επεκτείνει την κλάση UserService και προσθέτει τη λειτουργικότητα αναζήτησης μέσω email:
|
1 2 3 4 5 6 7 8 9 |
class UserSearchService(private val userService: UserService) : UserService() { fun searchUsersByEmail(email: String): List<User> { // κώδικας για την αναζήτηση χρηστών με βάση τη διεύθυνση email } } |
Σε αυτό το παράδειγμα, η κλάση UserSearchService είναι ανοιχτή για επέκταση, καθώς παρέχει πρόσθετη λειτουργικότητα πέρα από αυτήν που προσφέρεται από την UserService κλάση. Ταυτόχρονα, η UserService κλάση παραμένει κλειστή για τροποποίηση, καθώς δεν χρειάστηκε να τροποποιήσουμε τον κώδικά της για να προσθέσουμε τη δυνατότητα αναζήτησης μέσω email.
Αρχή της Υποκατάστασης Liskov
Η Αρχή της Υποκατάστασης Liskov (LSP) είναι μια αρχή σχεδιασμού λογισμικού που ορίζει ότι τα αντικείμενα μιας υπερκλάσης θα πρέπει να μπορούν να αντικατασταθούν από αντικείμενα μιας υποκλάσης χωρίς να επηρεάζεται η ορθότητα του προγράμματος. Αυτό σημαίνει ότι μια υποκλάση θα πρέπει να είναι ένα έγκυρο υποκατάστατο της υπερκλάσης της και θα πρέπει να συμπεριφέρεται με τον ίδιο τρόπο όπως η υπερκλάση όταν χρησιμοποιείται στο ίδιο πλαίσιο.
Θα συνεχίσουμε την επίδειξη χρησιμοποιώντας τις User και UserService κλάσεις από τα προηγούμενα παραδείγματα. Για να επιτρέψουμε την επέκταση της κλάσης User, χρησιμοποιούμε τη λέξη-κλειδί open στην Kotlin:
|
1 2 3 4 5 6 7 8 9 |
open class User( val firstName: String, val lastName: String, val email: String ) |
Ορίστε η αρχική UserService κλάση που χρησιμοποιεί την παραπάνω User data class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class UserService { fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } } |
Ας υποθέσουμε ότι θέλουμε να δημιουργήσουμε μια υποκλάση της User με όνομα AdminUser που αναπαριστά χρήστες με δικαιώματα διαχειριστή. Θα μπορούσαμε να το κάνουμε ως εξής:
|
1 2 3 4 5 6 7 8 9 |
class AdminUser( firstName: String, lastName: String, email: String, ) : User(firstName, lastName, email) |
Σε αυτό το παράδειγμα, η κλάση AdminUser είναι ένα έγκυρο υποκατάστατο για την κλάση User, καθώς συμπεριφέρεται με τον ίδιο τρόπο όπως η κλάση User και μπορεί να χρησιμοποιηθεί οπουδήποτε αναμένεται ένα αντικείμενο User. Για παράδειγμα, μπορούμε να χρησιμοποιήσουμε την κλάση AdminUser με την κλάση UserService ως εξής:
|
1 2 3 4 5 6 7 8 9 |
fun main() { val userService = UserService() val adminUser = AdminUser("John", "Doe", "john.doe@example.com") userService.createUser(adminUser) } |
Αυτός ο κώδικας είναι σωστός, καθώς η κλάση AdminUser είναι ένα έγκυρο υποκατάστατο για την κλάση User και μπορεί να χρησιμοποιηθεί με τον ίδιο τρόπο όπως ένα αντικείμενο User .
Είναι σημαντικό να σημειωθεί ότι η Αρχή της Υποκατάστασης Liskov αφορά κάτι περισσότερο από την απλή κληρονομικότητα. Αφορά τη διασφάλιση ότι τα αντικείμενα μιας υποκλάσης συμπεριφέρονται με τον ίδιο τρόπο όπως τα αντικείμενα της υπερκλάσης, ανεξάρτητα από τον τρόπο υλοποίησης της υποκλάσης. Για παράδειγμα, εάν η κλάση AdminUser συμπεριφερόταν διαφορετικά από την κλάση User με κάποιον τρόπο, αυτό θα παραβίαζε την Αρχή της Υποκατάστασης Liskov, καθώς δεν θα αποτελούσε έγκυρο υποκατάστατο για την κλάση User .
Αρχή του Διαχωρισμού Διασυνδέσεων
Η Αρχή του Διαχωρισμού Διασυνδέσεων (ISP) είναι μια αρχή σχεδιασμού λογισμικού που ορίζει ότι οι πελάτες δεν θα πρέπει να αναγκάζονται να εξαρτώνται από διεπαφέςπου δεν χρησιμοποιούν. Αυτό σημαίνει ότι είναι γενικά καλή ιδέα να δημιουργούνται μικρές, εστιασμένες διεπαφές που κάνουν ένα πράγμα καλά, αντί να δημιουργούνται μεγάλες διεπαφές γενικής χρήσης διεπαφές που προσπαθούν να κάνουν πολλά πράγματα.
Ορίστε ένα παράδειγμα για το πώς μπορεί να εφαρμοστεί αυτή η αρχή, ξαναγράφοντας τις κλάσεις User και UserService από τα προηγούμενα παραδείγματα:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
interface UserService { fun createUser(user: User) fun deleteUser(user: User) fun updateUser(user: User) fun getUser(id: Int): User } class DatabaseUserService : UserService { override fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } override fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } override fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } override fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } } |
Σε αυτό το παράδειγμα, η UserService interface defines four methods that are related to managing users in a database. The DatabaseUserService κλάση υλοποιεί αυτή τη διεπαφή και παρέχει συγκεκριμένες υλοποιήσεις για αυτές τις μεθόδους.
Suppose we want to add a new feature to the UserService interface that allows us to search for users by email address. One way to do this would be to add a new method to the UserService interface:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
interface UserService { fun createUser(user: User) fun deleteUser(user: User) fun updateUser(user: User) fun getUser(id: Int): User fun searchUsersByEmail(email: String): List<User> } |
Ο κώδικάς σας δεν θα εκτελεστεί, εκτός εάν υλοποιήσετε επίσης αυτή τη μέθοδο σε όλες τις κλάσεις που υλοποιούν τη UserService interface:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class DatabaseUserService : UserService { override fun createUser(user: User) { // κώδικας για τη δημιουργία χρήστη στη βάση δεδομένων } override fun deleteUser(user: User) { // κώδικας για τη διαγραφή χρήστη από τη βάση δεδομένων } override fun updateUser(user: User) { // κώδικας για την ενημέρωση χρήστη στη βάση δεδομένων } override fun getUser(id: Int): User { // κώδικας για την ανάκτηση χρήστη από τη βάση δεδομένων } override fun searchUsersByEmail(email: String): List<User> { // κώδικας για την αναζήτηση χρηστών με βάση τη διεύθυνση email } } |
Αν και αυτή η προσέγγιση λειτουργεί, παραβιάζει την Αρχή Διαχωρισμού Διεπαφών (Interface Segregation Principle), καθώς αναγκάζει την DatabaseUserService κλάση να υλοποιήσει μια μέθοδο ( searchUsersByEmail ) την οποία μπορεί να μην χρειάζεται ή να μην χρησιμοποιεί.
Μια καλύτερη προσέγγιση θα ήταν η δημιουργία μιας ξεχωριστής interface για τη λειτουργικότητα αναζήτησης email:
|
1 2 3 4 5 |
interface UserSearchService { fun searchUsersByEmail(email: String): List<User> } |
Τώρα έχουμε ξεχωριστές, μικρές και εστιασμένες διεπαφές, δηλ. UserService και UserSearchServiceπου έχουν μία μόνο ευθύνη. Μια κλάση που απαιτεί όλες τις λειτουργικότητες αυτών των διεπαφών μπορεί να τις υλοποιήσει όπως φαίνεται στο παρακάτω απόσπασμα κώδικα:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class DatabaseUserService : UserService, UserSearchService { override fun createUser(user: User) { // κώδικας για τη δημιουργία ενός χρήστη στη βάση δεδομένων } override fun deleteUser(user: User) { // κώδικας για τη διαγραφή ενός χρήστη από τη βάση δεδομένων } override fun updateUser(user: User) { // κώδικας για την ενημέρωση ενός χρήστη στη βάση δεδομένων } override fun getUser(id: Int): User { // κώδικας για την ανάκτηση ενός χρήστη από τη βάση δεδομένων } override fun searchUsersByEmail(email: String): List<User> { // κώδικας για την αναζήτηση χρηστών με βάση τη διεύθυνση email } } |
Αυτό συμμορφώνεται με την Αρχή Διαχωρισμού Διασυνδέσεων, καθώς διασφαλίζει ότι οι πελάτες (όπως η DatabaseUserService κλάση) δεν αναγκάζονται να εξαρτώνται από διεπαφές που δεν χρησιμοποιούν.
Για να κατανοήσετε καλύτερα αυτήν την έννοια, ας υποθέσουμε ότι έχουμε μια άλλη κλάση που ονομάζεται MemoryUserService που υλοποιεί τη UserService διεπαφή αλλά δεν χρειάζεται τη λειτουργικότητα αναζήτησης email, μπορούμε να γράψουμε τον κώδικα ως εξής:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class MemoryUserService : UserService { private val users = mutableListOf<User>() override fun createUser(user: User) { users.add(user) } override fun deleteUser(user: User) { users.remove(user) } override fun updateUser(user: User) { val index = users.indexOf(user) if (index >= 0) { users[index] = user } } override fun getUser(id: Int): User { return users[id] } } |
In this example, the MemoryUserService class only needs to implement the methods defined in the UserService interface, and does not need to worry about the email search functionality. This allows the MemoryUserService class to focus on its primary responsibility of managing users in memory, rather than being forced to implement unrelated functionality.
Αρχή Αντιστροφής Εξαρτήσεων
Η Αρχή Αντιστροφής Εξαρτήσεων (Dependency Inversion Principle - DIP) είναι μια αρχή σχεδιασμού λογισμικού που ορίζει ότι τα δομοστοιχεία υψηλού επιπέδου δεν πρέπει να εξαρτώνται από δομοστοιχεία χαμηλού επιπέδου, αλλά και τα δύο θα πρέπει να εξαρτώνται από αφαιρέσεις. Αυτό σημαίνει ότι είναι γενικά καλή ιδέα να σχεδιάζετε το λογισμικό σας με τέτοιο τρόπο ώστε τα στοιχεία υψηλού επιπέδου να μην είναι συνδεδεμένα με συγκεκριμένες υλοποιήσεις στοιχείων χαμηλού επιπέδου, αλλά μάλλον να εξαρτώνται από αφαιρέσεις (όπως διεπαφές ή αφηρημένες κλάσεις) που μπορούν να υλοποιηθούν με διαφορετικούς τρόπους.
Ας δούμε ένα παράδειγμα για το πώς μπορεί να εφαρμοστεί αυτή η αρχή στις κλάσεις User και UserService που χρησιμοποιήθηκαν στα προηγούμενα αποσπάσματα κώδικα:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
interface UserRepository { fun create(user: User) fun delete(user: User) fun update(user: User) fun get(id: Int): User } class UserService(private val repository: UserRepository) { fun createUser(user: User) { repository.create(user) } fun deleteUser(user: User) { repository.delete(user) } fun updateUser(user: User) { repository.update(user) } fun getUser(id: Int): User { return repository.get(id) } } |
In this example, the UserService class depends on an abstraction called the UserRepository interface, rather than depending on a specific implementation of a user repository. This allows us to implement the UserRepository interface in different ways, such as with a database or in-memory storage, without affecting the UserService class.
For example, here’s an implementation of the UserRepository interface that uses a database:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class DatabaseUserRepository: UserRepository { override fun create(user: User) { // κώδικας για τη δημιουργία ενός χρήστη στη βάση δεδομένων } override fun delete(user: User) { // κώδικας για τη διαγραφή ενός χρήστη από τη βάση δεδομένων } override fun update(user: User) { // κώδικας για την ενημέρωση ενός χρήστη στη βάση δεδομένων } override fun get(id: Int): User { // κώδικας για την ανάκτηση ενός χρήστη από τη βάση δεδομένων } } |
Here is another implementation of the UserRepository interface that uses in-memory storage:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class MemoryUserRepository : UserRepository { private val users = mutableListOf<User>() override fun create(user: User) { users.add(user) } override fun delete(user: User) { users.remove(user) } override fun update(user: User) { val index = users.indexOf(user) if (index >= 0) { users[index] = user } } override fun get(id: Int): User { return users[id] } } |
This makes the system more flexible and maintainable, as it allows us to change the repository implementation without affecting the rest of the system. It also makes it easier to test the UserService class, as we can mock the UserRepository dependency in our tests.
Conclusion
In this article, we talked about the five principles of SOLID κώδικα και κοινόχρηστων αποσπασμάτων κώδικα που ικανοποιούν κάθε αρχή. Η τήρηση αυτών των αρχών μπορεί να σας βοηθήσει να σχεδιάσετε συστήματα λογισμικού που είναι πιο ευέλικτα, συντηρήσιμα και επεκτάσιμα. Ωστόσο, είναι σημαντικό να έχετε υπ’ όψιν σας ότι αυτές οι αρχές είναι κατευθυντήριες γραμμές, παρά αυστηροί κανόνες, και εναπόκειται στον προγραμματιστή να αποφασίσει πότε και πώς θα τις εφαρμόσει στο πλαίσιο του συγκεκριμένου έργου του. Συνεχίστε τη μάθησή σας ελέγχοντας το ιστολόγιό μας για περισσότερα σε βάθος και ενημερωμένα άρθρα και οδηγούς σχετικά με το υπολογιστικό νέφος και το DevOps, σχεδιασμό και ανάπτυξη λογισμικού, τεχνολογικές τάσεις που πρέπει να προσέξετε, και πολλά άλλα.
Καλό προγραμματισμό!
Σχόλια
Δεν υπάρχουν σχόλια ακόμα. Γράψτε το πρώτο.