Je vois toujours que l'abstraction est une fonctionnalité très utile que l'OO fournit pour gérer la base de code. Mais comment les grandes bases de code non OO sont-elles gérées? Ou est-ce que cela devient finalement une " grosse boule de boue "?
Mise à jour:
Il semble que tout le monde pense que «l'abstraction» n'est que de la modularisation ou de la dissimulation de données. Mais à mon humble avis, cela signifie également l'utilisation de «classes abstraites» ou «interfaces» qui est un must pour l'injection de dépendance et donc les tests. Comment les bases de code non OO gèrent-elles cela? Et aussi, à part l'abstraction, l'encapsulation aide également beaucoup à gérer de grandes bases de code car elle définit et restreint la relation entre les données et les fonctions.
Avec C, il est tout à fait possible d'écrire du code pseudo-OO. Je ne connais pas grand-chose aux autres langues non OO. Alors, est-ce LA façon de gérer de grandes bases de code C?
Réponses:
Vous semblez penser que la POO est le seul moyen de réaliser l'abstraction.
Bien que la POO soit certainement très bonne pour le faire, ce n'est en aucun cas le seul moyen. Les grands projets peuvent également être gérés grâce à une modularisation sans compromis (il suffit de regarder Perl ou Python, qui ont tous deux excellé dans ce domaine, tout comme les langages fonctionnels comme ML et Haskell), et en utilisant des mécanismes tels que des modèles (en C ++).
la source
static
modificateur d'accès attaché.Modules, fonctions (externes / internes), sous-programmes ...
comme l'a dit Konrad, la POO n'est pas le seul moyen de gérer de grandes bases de code. En fait, beaucoup de logiciels ont été écrits avant (avant C ++ *).
la source
Le principe de modularité ne se limite pas aux langages orientés objet.
la source
De manière réaliste, soit des changements peu fréquents (pensez aux calculs de retraite de la sécurité sociale) et / ou des connaissances profondément enracinées parce que les personnes qui maintiennent un tel système le font depuis un certain temps (la prise cynique est la sécurité de l'emploi).
Les meilleures solutions sont la validation reproductible, par laquelle je veux dire le test automatisé (par exemple les tests unitaires) et les tests humains qui suivent les étapes proscrites (par exemple les tests de régression) "au lieu de cliquer et voir ce qui casse".
Pour commencer à évoluer vers une sorte de test automatisé avec une base de code existante, je recommande de lire Michael Feather Working Effectively with Legacy Code , qui détaille les approches pour amener les bases de code existantes jusqu'à une sorte de cadre de test reproductible OO ou non. Cela conduit au genre d'idées auxquelles d'autres ont répondu, comme la modularisation, mais le livre décrit la bonne approche pour le faire sans casser les choses.
la source
Bien que l'injection de dépendances basée sur des interfaces ou des classes abstraites soit une très bonne façon de faire des tests, elle n'est pas nécessaire. N'oubliez pas que presque tous les langages ont un pointeur de fonction ou un eval, ce qui peut faire tout ce que vous pouvez faire avec une interface ou une classe abstraite (le problème est qu'ils peuvent faire plus , y compris beaucoup de mauvaises choses, et qu'ils ne font pas '' t en eux-mêmes fournissent des métadonnées). Un tel programme peut réellement réaliser l'injection de dépendance avec ces mécanismes.
J'ai trouvé que la rigueur avec les métadonnées était très utile. Dans les langages OO, les relations entre les bits de code sont définies (dans une certaine mesure) par la structure des classes, d'une manière suffisamment normalisée pour avoir des choses comme une API de réflexion. Dans les langages procéduraux, il peut être utile de les inventer vous-même.
J'ai également trouvé que la génération de code est beaucoup plus utile dans un langage procédural (par rapport à un langage orienté objet). Cela garantit que les métadonnées sont synchronisées avec le code (car elles sont utilisées pour le générer) et vous donne quelque chose comme les points de coupure de la programmation orientée aspect - un endroit où vous pouvez injecter du code quand vous en avez besoin. Parfois, c'est la seule façon de faire de la programmation DRY dans un tel environnement que je peux comprendre.
la source
En fait, comme vous l'avez récemment découvert , les fonctions de premier ordre sont tout ce dont vous avez besoin pour l'inversion des dépendances.
C prend en charge les fonctions de premier ordre et même les fermetures dans une certaine mesure . Et les macros C sont une fonctionnalité puissante pour la programmation générique, si elles sont manipulées avec le soin nécessaire.
Tout est là. SGLIB est un assez bon exemple sur la façon dont C peut être utilisé pour écrire du code hautement réutilisable. Et je crois qu'il y en a beaucoup plus.
la source
Même sans abstraction, la plupart des programmes sont divisés en sections. Ces sections se rapportent généralement à des tâches ou activités spécifiques et vous travaillez sur celles-ci de la même manière que vous travaillez sur les bits les plus spécifiques des programmes abstraits.
Dans les projets de petite à moyenne taille, il est parfois plus facile de le faire avec une implémentation OO puriste.
la source
L'abstraction, les classes abstraites, l'injection de dépendances, l'encapsulation, les interfaces, etc. ne sont pas le seul moyen de contrôler de grandes bases de code; c'est juste et orienté objet.
Le principal secret est d'éviter de penser OOP lors du codage non-OOP.
La modularité est la clé dans les langages non OO. En C, cela est réalisé comme David Thornley vient de le mentionner dans un commentaire:
la source
Une façon de gérer le code est de le décomposer en les types de code suivants, selon les principes de l'architecture MVC (model-view-controller).
Cette méthode d'organisation du code fonctionne bien pour les logiciels écrits dans n'importe quel langage OO ou non OO car les modèles de conception communs sont souvent communs à chacun des domaines. En outre, ces types de limites de code sont souvent les plus lâchement couplés, à l'exception des algorithmes car ils relient les formats de données des entrées au modèle, puis aux sorties.
Les évolutions du système prennent souvent la forme d'un logiciel qui gère plus de types d'entrées ou de types de sorties, mais les modèles et les vues sont les mêmes et les contrôleurs se comportent de manière très similaire. Ou un système peut au fil du temps avoir besoin de prendre en charge de plus en plus de types de sorties différents, même si les entrées, les modèles, les algorithmes sont les mêmes et que les contrôleurs et les vues sont similaires. Ou un système peut être augmenté pour ajouter de nouveaux modèles et algorithmes pour le même ensemble d'entrées, des sorties similaires et des vues similaires.
La programmation OO rend l'organisation du code difficile, car certaines classes sont profondément liées aux structures de données persistantes, et d'autres non. Si les structures de données persistantes sont intimement liées à des éléments tels que les relations en cascade 1: N ou les relations m: n, il est très difficile de décider des limites de classe jusqu'à ce que vous ayez codé une partie significative et significative de votre système avant de savoir que vous avez bien compris. . Toute classe liée aux structures de données persistantes sera difficile à faire évoluer lorsque le schéma des données persistantes change. Les classes qui gèrent les algorithmes, le formatage et l'analyse sont moins susceptibles d'être vulnérables aux modifications du schéma des structures de données persistantes. L'utilisation d'un type d'organisation de code MVC isole mieux les modifications de code les plus salissantes du code de modèle.
la source
Lorsque vous travaillez dans des langages qui manquent de structure intégrée et de fonctionnalités d'organisation (par exemple, s'il n'a pas d'espaces de noms, de packages, d'assemblages, etc.) ou lorsque ceux-ci sont insuffisants pour garder une base de code de cette taille sous contrôle, la réponse naturelle est de développer nos propres stratégies pour organiser le code.
Cette stratégie d'organisation comprend probablement des normes relatives à l'emplacement où les différents fichiers doivent être conservés, les choses qui doivent se produire avant / après certains types d'opérations, et les conventions de dénomination et autres normes de codage, ainsi que beaucoup de "c'est ainsi qu'il est configuré - ne plaisante pas avec ça! " tapez des commentaires - qui sont valables tant qu'ils expliquent pourquoi!
Parce que la stratégie finira très probablement par être adaptée aux besoins spécifiques du projet (personnes, technologies, environnement, etc.), il est difficile de donner une solution universelle pour gérer de grandes bases de code.
Par conséquent, je crois que le meilleur conseil est d'adopter la stratégie spécifique au projet et de faire de sa gestion une priorité clé: documenter la structure, pourquoi c'est ainsi, les processus pour apporter des changements, l'auditer pour s'assurer qu'elle est respectée, et surtout: changez-le quand il a besoin de changer.
Nous sommes surtout familiers avec les classes et méthodes de refactorisation, mais avec une grande base de code dans un tel langage, c'est la stratégie d'organisation elle-même (avec documentation) qui doit être refactorisée au fur et à mesure des besoins.
Le raisonnement est le même que pour la refactorisation: vous développerez un blocage mental pour travailler sur de petites parties du système si vous sentez que l'organisation globale de celui-ci est un gâchis, et finira par lui permettre de se détériorer (du moins c'est mon point de vue sur il).
Les mises en garde sont également les mêmes: utilisez des tests de régression, assurez-vous que vous pouvez facilement revenir en arrière si le refactoring va mal, et concevez de manière à faciliter le refactoring en premier lieu (ou vous ne le ferez tout simplement pas!).
Je suis d'accord que c'est beaucoup plus délicat que de refactoriser le code direct, et il est plus difficile de valider / cacher le temps aux gestionnaires / clients qui pourraient ne pas comprendre pourquoi cela doit être fait, mais ce sont également les types de projets les plus sujets à la pourriture de logiciels causés par des conceptions de haut niveau inflexibles ...
la source
Si vous vous interrogez sur la gestion d'une grande base de code, vous demandez comment garder votre base de code bien structurée à un niveau relativement grossier (bibliothèques / modules / construction de sous-systèmes / utilisation d'espaces de noms / avoir les bons documents aux bons endroits) etc.). Les principes OO, en particulier les «classes abstraites» ou les «interfaces», sont des principes pour garder votre code propre en interne, à un niveau très détaillé. Ainsi, les techniques de gestion d'une base de code volumineuse ne diffèrent pas pour le code OO ou non OO.
la source
Comment cela est géré, c'est que vous découvrez les frontières des éléments que vous utilisez. Par exemple, les éléments suivants en C ++ ont une bordure claire et toutes les dépendances en dehors de la frontière doivent être soigneusement pensées:
En combinant ces éléments et en reconnaissant leurs frontières, vous pouvez créer presque tous les styles de programmation que vous souhaitez dans c ++.
Un exemple de ceci est pour une fonction serait de reconnaître qu'il est mauvais d'appeler d'autres fonctions à partir d'une fonction, car cela provoque une dépendance, à la place, vous ne devez appeler que les fonctions membres des paramètres de la fonction d'origine.
la source
Le plus grand défi technique est le problème de l'espace de noms. Une liaison partielle peut être utilisée pour contourner ce problème. La meilleure approche consiste à concevoir en utilisant des normes de codage. Sinon, tous les symboles deviennent un gâchis.
la source
Emacs en est un bon exemple:
Les tests Emacs Lisp utilisent
skip-unless
etlet-bind
font des dispositifs de détection et de test de fonctionnalités:Tout comme SQLite. Voici son design:
sqlite3_open () → Ouvrir une connexion à une base de données SQLite nouvelle ou existante. Le constructeur de sqlite3.
sqlite3 → L'objet de connexion à la base de données. Créé par sqlite3_open () et détruit par sqlite3_close ().
sqlite3_stmt → L'objet d'instruction préparé. Créé par sqlite3_prepare () et détruit par sqlite3_finalize ().
sqlite3_prepare () → Compiler du texte SQL en octet-code qui fera le travail d'interrogation ou de mise à jour de la base de données. Le constructeur de sqlite3_stmt.
sqlite3_bind () → Stocker les données d'application dans les paramètres du SQL d'origine.
sqlite3_step () → Avance un sqlite3_stmt à la ligne de résultat suivante ou à la fin.
sqlite3_column () → Valeurs de colonne dans la ligne de résultat actuelle pour sqlite3_stmt.
sqlite3_finalize () → Destructeur pour sqlite3_stmt.
sqlite3_exec () → Fonction wrapper qui exécute sqlite3_prepare (), sqlite3_step (), sqlite3_column () et sqlite3_finalize () pour une chaîne d'une ou plusieurs instructions SQL.
sqlite3_close () → Destructeur pour sqlite3.
SQLite utilise une variété de techniques de test, notamment:
Les références
Vues conceptuelles de l'architecture d'Emacs (pdf)
L'interface du système d'exploitation SQLite ou "VFS"
Le mécanisme de table virtuelle de SQLite
Une introduction à l'interface SQLite C / C ++
Emacs-Elisp-Programming · GitHub
Tests et leur environnement - Emacs Lisp Regression Testing
Comment SQLite est testé
la source