
Développer un cadre gratuit pour les besoins des développeurs est un sujet spécifique. Si en même temps le cadre vit et se développe assez longtemps, alors les spécificités sont ajoutées. Aujourd'hui, je vais essayer de le montrer en utilisant un exemple de tentative d'extension de la fonctionnalité d'un framework "acteur" pour C ++ appelé
SObjectizer .
Le fait est que ce cadre est déjà assez ancien, il a radicalement changé plusieurs fois. Même son incarnation actuelle, SObjectizer-5, a subi de nombreux changements, graves et non. De plus, nous sommes assez sensibles à la compatibilité et introduire des changements qui cassent la compatibilité est une étape trop sérieuse pour que nous décidions simplement.
En ce moment, nous devons décider comment ajouter une nouvelle fonctionnalité à la prochaine version. Dans le processus de recherche d'une solution appropriée, deux options sont apparues. Les deux semblent assez réalisables. Mais ils sont très différents les uns des autres. Tant en termes de complexité et de complexité de mise en œuvre que dans son «apparence». C'est-à-dire ce que le développeur traitera sera différent dans chacune des options. Probablement même fondamentalement différent.
Et maintenant, en tant que développeurs du framework, nous devons faire un choix en faveur de l'une ou l'autre solution. Ou bien il faut admettre qu’aucune d’elles n’est satisfaisante et qu’il faut donc inventer autre chose. De telles décisions au cours de l'histoire de SObjectizer ont dû être prises plus d'une fois. Si quelqu'un est intéressé à se sentir à la place du développeur d'un tel cadre, vous êtes le bienvenu chez cat.
Problème d'origine
Donc, brièvement l'essence du problème d'origine. Dès le début de son existence, SObjectizer avait la caractéristique suivante: un message de temporisation n'est pas si facile à annuler. Sous la minuterie, on comprendra tout d'abord un message différé. C'est-à-dire un message qui ne doit pas être envoyé immédiatement au destinataire, mais après un certain temps. Par exemple, nous faisons send_delayed avec une pause de 1s. Cela signifie qu'en réalité le message sera envoyé par le temporisateur 1s après l'appel send_delayed.
Un message en attente peut, en principe, être annulé. Si le message est toujours en possession de la minuterie, le message après l'annulation n'ira nulle part. Il sera lancé par la minuterie et c'est tout. Mais si le temporisateur a déjà envoyé un message et qu'il se trouve maintenant dans la file d'attente des demandes de l'agent récepteur, l'annulation du temporisateur ne fonctionnera pas. Il n'existe aucun mécanisme dans SObjectizer pour supprimer un message de la file d'attente des applications.
Le problème est aggravé par au moins deux facteurs.
Premièrement, SObjectizer prend en charge la livraison en mode 1: N, c'est-à-dire si le message a été envoyé à la mbox multi-consommateurs, le message ne sera pas dans une file d'attente, mais dans plusieurs files d'attente pour N destinataires à la fois.
Deuxièmement, dans SObjectizer, le mécanisme de répartition est utilisé et les répartiteurs peuvent être très différents, y compris ceux écrits par l'utilisateur pour ses besoins spécifiques. Les files d'attente de demandes sont gérées par des répartiteurs. Et dans l'interface du répartiteur, il n'y a aucune fonctionnalité pour retirer une application qui a déjà été transférée au répartiteur. Mais même si de telles fonctionnalités étaient intégrées dans l'interface, il est loin d'être certain qu'elles pourraient être mises en œuvre efficacement dans tous les cas. Sans oublier le fait qu'une telle fonctionnalité augmenterait la complexité du développement de nouveaux répartiteurs.
En général, objectivement, si le temporisateur a déjà envoyé un message en attente au (x) destinataire (s), alors forcer SObjectizer à ne pas remettre cette instance du message est actuellement impossible.
En fait, ce problème est également pertinent pour les messages périodiques (c'est-à-dire les messages que le temporisateur doit envoyer périodiquement à des intervalles de temps prédéterminés). Mais en pratique, l'annulation de messages périodiques est beaucoup moins nécessaire que l'annulation d'un message en attente. C'est du moins le cas dans notre pratique.
Que peut-on faire maintenant?
Donc, ce problème n'est pas nouveau et pendant longtemps il y a des recommandations sur la façon de le traiter.
Identifiant unique dans le message en attente
Le moyen le plus simple est de tenir un compteur. L'agent dispose d'un compteur; lors de l'envoi d'un message en attente, la valeur actuelle du compteur est envoyée dans le message. Lorsqu'un message est annulé, le compteur de l'agent est incrémenté. A la réception du message, la valeur actuelle du compteur dans l'agent est comparée à la valeur du message. Si les valeurs ne correspondent pas, le message est rejeté:
class demo_agent : public so_5::agent_t { struct delayed_msg final { int id_; ... }; int expected_msg_id_{}; so_5::timer_id_t timer_; void on_some_event() {
Le problème avec cette méthode est que le développeur d'agent doit être intrigué par la maintenance de ces compteurs. Et si en tant que message différé nous devons envoyer le message de quelqu'un d'autre que quelqu'un d'autre a fait et dans lequel il n'y a pas de champ id_, alors nous nous trouvons dans une situation difficile.
Bien que, d'autre part, c'est la manière la plus efficace qui existe actuellement.
Utiliser une mbox unique pour les messages retardés
Une autre méthode qui fonctionne bien consiste à utiliser une boîte aux lettres unique (mbox) pour un message différé. Dans ce cas, nous créons une nouvelle mbox pour chaque message en attente, nous y abonnons et envoyons le message en attente à cette mbox. Lorsqu'un message doit être annulé, nous supprimons simplement les abonnements mbox.
class demo_agent : public so_5::agent_t { struct delayed_msg final { ...
Cette méthode peut déjà fonctionner avec les messages d'autres personnes, à l'intérieur desquels il n'y a pas d'identifiant unique. Mais cela nécessite également du travail et de l'attention de la part du développeur.
Par exemple, dans le mode de réalisation ci-dessus, il n'y a aucune protection contre le fait qu'un message en attente a déjà été envoyé plus tôt. Dans le bon sens, avant d'envoyer un nouveau message en attente, vous devez toujours effectuer des actions de on_cancel_event (), sinon l'agent aura des abonnements inutiles pour cela.
Pourquoi ce problème n'a-t-il pas été résolu auparavant?
Tout est assez simple ici: en fait, ce n'est pas un problème aussi grave qu'il y paraît. Au moins dans la vraie vie, vous n'avez pas à y faire face souvent. Habituellement, les messages en attente et périodiques ne sont pas du tout annulés (c'est pourquoi, en passant, la fonction send_delayed ne renvoie pas timer_id). Et lorsque le besoin d'annulation survient, vous pouvez utiliser l'une des méthodes décrites ci-dessus. Ou même en utiliser un autre. Par exemple, créez des agents distincts qui traiteront un message en attente. Ces agents peuvent être désinscrits lorsqu'un message en attente doit être annulé.
Ainsi, dans le contexte des autres tâches qui nous ont été confrontées, la simplification de l'annulation garantie d'un message en attente n'était pas une priorité au point de consacrer nos ressources à la résolution de ce problème.
Pourquoi le problème est-il pertinent maintenant?
Ici, tout est aussi simple. D'une part, les mains ont finalement atteint.
D'un autre côté, lorsque de nouvelles personnes qui n'avaient pas d'expérience avec lui commencent à utiliser SObjectizer, cette fonctionnalité avec l'annulation des temporisations les surprend grandement. Pas si agréablement surprenant. Et si oui, alors je voudrais minimiser les impressions négatives de la connaissance de notre outil.
De plus, nous avions nos propres tâches, nous n'avions pas besoin d'annuler constamment les messages en attente. Et les nouveaux utilisateurs ont leurs propres tâches, peut-être que tout est inversé.
Nouvelle déclaration du problème
Presque immédiatement, dès que l’on a envisagé la possibilité d’une «annulation de la temporisation garantie», j’ai pensé que la tâche pourrait être élargie. Vous pouvez essayer de résoudre le problème du rappel de l'un des messages précédemment envoyés, pas nécessairement retardé et périodique.
De temps en temps, cette opportunité est recherchée. Par exemple, imaginez que nous avons plusieurs agents en interaction de deux types: entry_point (accepte les demandes des clients) et processeur (traite les demandes):

Les agents Entry_point envoient des requêtes à l'agent processeur, qui les traite autant que possible et répond aux agents Entry_point. Mais parfois, entry_point peut constater que le traitement d'une demande envoyée précédemment n'est plus nécessaire. Par exemple, le client a envoyé une commande d'annulation ou le client est «tombé» et vous n'avez plus besoin de traiter ses demandes. Maintenant, si les messages de demande sont mis en file d'attente par l'agent de processeur, vous ne pouvez pas les rappeler. Et ce serait utile.
Par conséquent, l'approche actuelle pour résoudre le problème de «l'annulation garantie de la minuterie» est mise en œuvre avec précision en tant que prise en charge des «messages de rappel». Nous envoyons tout message d'une manière spéciale, nous obtenons une poignée sous la main, avec laquelle vous pouvez ensuite rappeler le message. Et ce n'est pas si important qu'un message régulier ou retardé réponde.
Une tentative de proposer la mise en place de "messages de rappel"
Vous devez donc introduire le concept de "message de rappel" et prendre en charge ce concept dans SObjectizer. Et donc, pour rester dans la branche 5.5. La première version de ce fil, 5.5.0, est sortie il y a près de quatre ans, en octobre 2014. Depuis lors, il n'y a eu aucun changement majeur dans 5.5. Les projets qui ont déjà basculé ou démarré immédiatement sur SObjectize-5.5 peuvent passer à de nouvelles versions dans la branche 5.5 sans aucun problème. Cette compatibilité doit être maintenue cette fois.
En général, tout est simple: il faut prendre et faire.
Comment faire clairement
Après la première approche du problème, deux choses sont devenues claires au sujet de la mise en œuvre des «messages de rappel».
Drapeau atomique et sa vérification avant le traitement du message
Premièrement, il est évident que dans le cadre de l'architecture SObjectizer-5.5 actuelle (et peut-être même plus globalement: dans le cadre des principes de SObjectizer-5 lui-même), il est impossible de supprimer des messages des files d'attente de requêtes du répartiteur, où les messages attendent que les agents récepteurs les traitent. Essayer de faire cela tuera toute l'idée de répartiteurs hétérogènes, que même l'utilisateur peut faire lui-même, selon les spécificités de sa tâche (par exemple,
celle-ci ). De plus, dans le cas de l'envoi d'un message en mode 1: N, où N sera grand, il sera coûteux de conserver une liste de pointeurs vers une instance du message envoyé dans toutes les files d'attente.
Cela signifie qu'avec le message, une sorte d'indicateur atomique doit être transmis, qui devra être analysé immédiatement après la suppression du message de la file d'attente des demandes, mais avant l'envoi du message pour traitement à l'agent récepteur. C'est-à-dire le message entre dans la file d'attente et n'est retiré de nulle part. Mais lorsque le tour arrive au message, son drapeau est vérifié. Et si le drapeau indique que le message a été retiré, le message n'est pas traité.
En conséquence, le rappel de message lui-même consiste à définir une valeur spéciale pour le drapeau atomique à l'intérieur du message.
Objet <M> Revocable_handle_t
Deuxièmement, jusqu'à présent (?) Il est évident que pour envoyer un message révocable, non pas les méthodes habituelles d'envoi de messages doivent être utilisées, mais un objet spécial sous le nom conditionnel revocable_handle_t.
Pour envoyer un message révocable, l'utilisateur doit créer une instance de revocable_handle_t, puis appeler la méthode d'envoi sur cette instance. Et si le message doit être rappelé, cela se fait à l'aide de la méthode de révocation. Quelque chose comme:
struct my_message {...}; ... so_5::revocable_handle_t<my_message> msg;
Il n'y a pas encore de détails clairs sur la mise en œuvre de revocable_handle_t, ce qui n'est pas surprenant, car le mécanisme de travail des messages de rappel n'a pas encore été sélectionné. Mais le principe de travail est que dans revocable_handle_t un lien intelligent est enregistré dans le message envoyé et dans le drapeau atomique pour celui-ci. La méthode revoke () tente de remplacer la valeur de l'indicateur. Si cela réussit, le message, après extraction de la file d'attente de commandes, ne sera plus traité.
Avec quoi il ne sera pas ami
Malheureusement, il y a deux ou trois choses avec lesquelles le rappel des messages ne peut pas être correctement lié. Tout simplement parce que le message retiré continue de rester dans les files d'attente où il est déjà arrivé.
message_limits
Une caractéristique aussi importante de SObjectizer que
message_limits est conçue pour protéger les agents contre les surcharges. Message_limits fonctionne en fonction du nombre de messages dans la file d'attente. Mis en file d'attente un message - augmenté le compteur. Sorti de la ligne - réduit.
Parce que lorsqu'un message est révoqué, il reste dans la file d'attente, puis message_limits n'affecte pas la réponse du message. Par conséquent, il peut s'avérer que la file d'attente a une limite sur le nombre de messages de type M, mais tous ont été rappelés. En fait, aucun d'entre eux ne sera traité. Mais la mise en file d'attente d'un nouveau message de type M ne fonctionnera pas, car la limite est dépassée.
La situation n'est pas bonne. Mais comment s'en sortir? Ce n'est pas clair.
chaînes de file d'attente fixes
Dans SObjectizer, un message peut être envoyé non seulement à mbox, mais aussi à mchain (c'est notre
analogue du canal CSP ). Et les chaînes peuvent avoir une taille fixe pour leurs files d'attente. Une tentative de mettre un nouveau message pour mchain avec une taille fixe en mchain complet devrait conduire à une sorte de réaction. Par exemple, en attendant la libération d'espace dans la file d'attente. Ou pour pousser le message le plus ancien.
Dans le cas d'un rappel de message, il restera dans la file d'attente mchain. Il s'avère que le message n'est plus nécessaire, mais il prend de la place dans la file d'attente mchain. Et empêche l'envoi de nouveaux messages à mchain.
La même mauvaise situation qu'avec message_limits. Et encore une fois, il n'est pas clair comment cela peut être résolu.
Ce qui n'est pas clair comment faire
Nous avons donc eu le choix entre deux (jusqu'à présent?) Options pour implémenter les messages de rappel. La première option est simple à implémenter et ne nécessite pas de modification des abats de SObjectizer. La deuxième option est beaucoup plus compliquée, mais le destinataire du message ne sait même pas qu'il s'agit de messages révocables. Nous allons brièvement examiner chacun d'eux.
Recevoir des messages révocables comme revocable_t <M>
La première solution, qui semble, d'une part, faisable et, d'autre part, assez pratique, est l'introduction d'un wrapper spécial revocable_t <M>. Lorsque l'utilisateur envoie un message révocable de type M via revocable_handle_t <M>, ce n'est pas le message M qui est envoyé, mais le message M à l'intérieur du wrapper spécial revocable_t <M>. Et, en conséquence, l'utilisateur ne recevra pas et ne traitera pas le message de type M, mais le message revocable_t <M>. Par exemple, de cette façon:
class processor : public so_5::agent_t { public: struct request { ... };
La méthode revocable_t <M> :: try_handle () vérifie la valeur du drapeau atomique et, si le message n'est pas rappelé, appelle la fonction lambda qui lui est passée. Si le message est retiré, try_handle () ne fait rien.
Avantages et inconvénients de cette approche
Le principal avantage est que ce voyage est facile à mettre en œuvre (du moins jusqu'à présent, il semble). En fait, revocable_handle_t <M> et revocable_t <M> ne seront qu'un complément subtil au SObjectizer.
Une intervention dans les internes de SObjectizer peut être nécessaire pour se faire des amis revocable_t et mutable_msg. Le fait est que dans SObjectizer il y a le concept de messages immuables (ils peuvent être envoyés à la fois en mode 1: 1 et en mode 1: N). Et il y a le concept de
messages mutables qui ne peuvent être envoyés qu'en mode 1: 1. Dans ce cas, SObjectizer traite de manière spéciale le marqueur mutable_msg <M> et effectue les vérifications correspondantes au moment de l'exécution. Dans le cas de revocable_t <mutable_msg <M>>, vous devrez apprendre à SObjectizer à traiter cette construction comme mutable_msg <M>.
Un autre avantage est que le surcoût supplémentaire (à la fois sur les métadonnées du message révocable et sur la vérification du drapeau atomique) ne sera que dans des endroits où vous ne pouvez pas vous en passer. Lorsque les messages de rappel ne sont pas utilisés, il n'y aura pas de surcharge supplémentaire du tout.
Mais le principal inconvénient est idéologique. Dans cette approche, le fait d'utiliser des messages révocables affecte à la fois l'expéditeur (en utilisant revocable_handle_t <M>) et le destinataire (en utilisant revocable_t <M>). Mais le destinataire n'a tout simplement pas besoin de savoir qu'il reçoit des messages de rappel. De plus, en tant que destinataire, vous pouvez avoir un agent tiers prêt à l'emploi qui est écrit sans revocable_t <M>.
De plus, des questions idéologiques subsistent, par exemple sur la possibilité de transmettre de tels messages. Mais, selon les premières estimations, ces problèmes sont résolus.
Recevoir des messages de rappel sous forme de messages réguliers
La seconde approche consiste à ne voir que le message de type M côté récepteur et à ne pas avoir une idée de l'existence de revocable_handle_t <M> et revocable_t <M>. C'est-à-dire si le processeur doit recevoir une demande, il ne doit voir qu'une demande, sans aucun wrapper supplémentaire.
En fait, on ne peut pas se passer de certains wrappers dans cette approche, mais ils seront cachés à l'intérieur du SObjectizer et l'utilisateur ne devrait pas les voir. Une fois l'application récupérée de la file d'attente, SObjectizer déterminera par lui-même qu'il s'agit d'un message révocable spécialement encapsulé, vérifiera l'indicateur de la pertinence du message et développera le message s'il est toujours pertinent. Ensuite, il enverra un message à l'agent pour traitement comme s'il s'agissait d'un message normal.
Avantages et inconvénients de cette approche
Le principal avantage de cette approche est évident - le destinataire du message ne sait pas avec quels messages il travaille. Cela permet à l'expéditeur du message de retirer calmement les messages de tous les agents, même ceux qui ont été écrits par d'autres développeurs.
Un autre avantage important est la capacité à s'intégrer au mécanisme de traçage de remise des messages (
ici le rôle de ce mécanisme est décrit plus en détail ). C'est-à-dire si msg_tracing est activé et que l'expéditeur retire le message, des traces de cela peuvent être trouvées dans le journal msg_tracing. Ce qui est très pratique lors du débogage.
Mais le principal inconvénient est la complexité de la mise en œuvre de cette approche. À laquelle plusieurs facteurs devront être pris en compte.
Tout d'abord, les frais généraux. Toutes sortes de choses.
Supposons que vous puissiez créer un indicateur spécial dans un message qui indique si ce message est révocable ou non. Vérifiez ensuite cet indicateur avant de commencer à traiter chaque message. En gros, un autre if est ajouté au mécanisme de remise des messages, qui fonctionnera lors du traitement de chaque (!) Message.
Je suis sûr que dans les applications réelles, la perte sur ce point sera à peine perceptible. Mais le recul des benchmarks synthétiques apparaîtra certainement. De plus, plus le repère est abstrait, moins il fait de travail réel, plus il s'enfonce. Et c'est mauvais d'un point de vue marketing, car plusieurs personnes tirent des conclusions sur le cadre en termes de repères synthétiques. Et ils le font spécifiquement: ne pas comprendre de quel type de référence il s'agit, qu'il montre essentiellement sur quel matériel il fonctionne, mais en comparant les totaux avec les performances d'un outil spécialisé, dans un autre scénario, sur un autre matériel, etc. ., etc.
En général, puisque nous créons un cadre universel, qui, en fin de compte, est jugé par des nombres abstraits dans des repères abstraits, nous ne voulons pas perdre, disons, 5% des performances du mécanisme de livraison de
tous les messages en raison de l'ajout d'une fonctionnalité qui ne prend que du temps de temps en temps et pas à tous les utilisateurs.
Par conséquent, vous devez vous assurer que lors de l'envoi du message au destinataire, SObjectizer comprend que lorsque vous extrayez le message, vous devez le gérer d'une manière spéciale. En principe, lorsqu'un message est remis à un agent, SObjectizer stocke avec le message un pointeur vers une fonction qui sera utilisée lors du traitement du message. Cela est nécessaire maintenant pour gérer les messages asynchrones et les demandes synchrones de différentes manières. En fait, voici à quoi ressemble la demande de message adressée à l'agent:
struct execution_demand_t {
Où demand_handler_pfn_t est un pointeur de fonction régulier:
typedef void (*demand_handler_pfn_t)( current_thread_id_t, execution_demand_t & );
Le même mécanisme peut également être utilisé pour traiter spécialement le message retiré. C'est-à-dire lorsque mbox envoie un message à l'agent, l'agent sait si un message asynchrone ou une demande synchrone lui est envoyé. De même, un agent peut recevoir un message de rappel asynchrone d'une manière spéciale. Et l'agent enregistre, avec le message, un pointeur sur une fonction qui sait comment il doit gérer les messages révoqués.
Tout semble aller bien, mais il y a deux gros "mais" ... :(
Premièrement, l'interface mbox existante (à savoir la classe
abstract_message_mbox_t ) ne dispose d'aucune méthode pour envoyer des messages de rappel. Cette interface doit donc être étendue. Et pour que les implémentations mbox d'autres personnes liées à abstract_message_box_t de SObjectizer-5.5 ne se cassent pas (en particulier, la série mbox est implémentée dans
so_5_extra et je ne veux tout simplement pas les casser).
Deuxièmement, les messages peuvent être envoyés non seulement aux mbox-s, derrière lesquels les agents sont cachés, mais aussi aux mchain-s. Quels sont
nos homologues des canaux CSP . Et jusqu'à présent, les applications mentaient sans pointeurs supplémentaires sur les fonctions. Pour introduire un pointeur supplémentaire dans chaque élément de la chaîne de file d'attente d'application ... Vous pouvez, bien sûr, mais cela ressemble à une solution assez coûteuse. De plus, les implémentations mchain elles-mêmes n'ont jusqu'à présent pas prévu de situation dans laquelle le message extrait doit être vérifié et éventuellement jeté.
Si vous essayez de résumer tous les problèmes décrits ci-dessus, le principal problème de cette approche est qu'il n'est pas si facile de faire son implémentation de sorte qu'elle soit bon marché pour les cas où les messages de rappel ne sont pas utilisés.
Mais qu'en est-il de l'annulation garantie des messages en attente?
Je crains que le problème d'origine ne se soit perdu dans la jungle des détails techniques. Supposons qu'il y ait des messages révocables, comment l'annulation des messages en attente / périodiques se produira-t-elle?Ici, comme on dit, des options sont possibles. Par exemple, travailler avec des messages en attente / périodiques peut faire partie de la fonctionnalité revocable_handle_t <M>: revocable_handle_t<my_mesage> msg; msg.send_delayed(target, 15s, ...); ... msg.revoke();
Ou vous pouvez créer au-dessus de revocable_handle_t <M> une classe d'assistance supplémentaire cancelable_timer_t <M>, qui fournira les méthodes send_delayed / send_periodic.Point blanc: demandes synchrones
SObjectizer-5 prend en charge non seulement l'interaction asynchrone entre les entités du programme (en envoyant des messages à mbox et mchain), mais également l'interaction synchrone via request_value / request_future. Cette interaction synchrone ne fonctionne pas uniquement pour les agents. C'est-à-dire
Vous pouvez non seulement envoyer une demande synchrone à un agent via sa mbox. Dans le cas de mchains, vous pouvez également effectuer des requêtes synchrones, par exemple, vers un autre thread de travail, sur lequel receive () ou select () a été appelé pour mchain.Ainsi, il n'est toujours pas clair s'il devrait être autorisé à utiliser des demandes synchrones en conjonction avec des messages révocables. D'une part, cela a peut-être un sens. Et cela peut ressembler, par exemple, à ceci: revocable_handle_t<my_request> msg; auto f = msg.request_future<my_reply>(target, ...); ... if(some_condition) msg.revoke(); ... f.get();
D'un autre côté, il y a encore beaucoup de messages incompréhensibles avec des messages de rappel, donc la question de l'interaction synchrone a été reportée à des temps meilleurs.Choisissez, mais soyez prudent. Mais choisissez
Il y a donc une compréhension du problème. Il existe deux options pour le résoudre. Ce qui pour le moment semble réalisable. Mais ils diffèrent considérablement dans le niveau de commodité offert à l'utilisateur, et encore plus fortement dans le coût de mise en œuvre.Vous devez choisir entre ces deux options. Ou trouver autre chose.Quelle est la difficulté de choisir?La difficulté est que SObjectizer est un framework gratuit. Il ne nous apporte pas directement d'argent. Nous le faisons, comme on dit, pour le nôtre. Par conséquent, uniquement à partir des préférences économiques, une option plus simple et plus rapide à mettre en œuvre est plus rentable.Mais, d'autre part, tout n'est pas mesuré en argent, et à long terme, un outil bien fait, dont les caractéristiques sont normalement liées les unes aux autres, est meilleur qu'un patchwork de patchs collés ensemble. La qualité est évaluée à la fois par les utilisateurs et par nous-mêmes, lorsque nous accompagnons ensuite notre développement et y ajoutons de nouvelles fonctionnalités.Ainsi, le choix va en fait entre les bénéfices à court terme et les perspectives à long terme. Certes, dans le monde moderne, les outils C ++ avec des perspectives à long terme sont en quelque sorte brumeux. Ce qui rend le choix encore plus difficile.C'est dans de telles conditions que vous devez choisir. Attention Mais choisissez.Conclusion
Dans cet article, nous avons essayé de montrer un peu le processus de conception et de mise en œuvre de nouvelles fonctionnalités dans notre framework. Un tel processus se déroule régulièrement avec nous. Plus tôt souvent parce que En 2014-2016, SObjectizer s'est développé beaucoup plus activement. Maintenant, le rythme de sortie des nouvelles versions a diminué. Ce qui est objectif, notamment parce que l'ajout de nouvelles fonctionnalités sans rien casser, cela devient plus difficile à chaque nouvelle version.J'espère que c'était intéressant de regarder dans les coulisses pour nous. Merci de votre attention!