En supposant que j'ai ce pseudo-code:
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
Les fonctions executeStepX
doivent être exécutées si et seulement si la précédente réussit. Dans tous les cas, la executeThisFunctionInAnyCase
fonction doit être appelée à la fin. Je suis un débutant en programmation, donc désolé pour la question très basique: existe-t-il un moyen (en C / C ++ par exemple) d'éviter que cette longue if
chaîne produise ce genre de "pyramide de code", au détriment de la lisibilité du code ?
Je sais que si nous pouvions ignorer l' executeThisFunctionInAnyCase
appel de fonction, le code pourrait être simplifié comme suit:
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
Mais la contrainte est l' executeThisFunctionInAnyCase
appel de fonction. La break
déclaration pourrait-elle être utilisée d'une manière ou d'une autre?
c++
c
if-statement
control-flow
ABCplus
la source
la source
false
être considérée comme apparentée à une situation exceptionnelle?false
retour peut être assez normal.Réponses:
Vous pouvez utiliser un
&&
(logique ET):cela répondra à vos deux exigences:
executeStep<X>()
ne doit évaluer que si le précédent a réussi (c'est ce qu'on appelle l' évaluation des courts-circuits )executeThisFunctionInAnyCase()
sera exécuté dans tous les casla source
&&
et||
) donc il n'y a aucun moyen que vous voulez les rejoindre en simpleif
déclaration sans vissage la lisibilité. Et il n'est pas toujours facile de déplacer ces conditions vers des fonctions externes, car elles peuvent dépendre de nombreuses variables locales précédemment calculées, ce qui créerait un terrible gâchis si vous essayez de passer chacune d'elles comme argument individuel.Utilisez simplement une fonction supplémentaire pour faire fonctionner votre deuxième version:
L'utilisation d'if profondément imbriqués (votre première variante) ou le désir de rompre avec "une partie d'une fonction" signifie généralement que vous avez besoin d'une fonction supplémentaire.
la source
foo
fraye un chemin à travers une séquence de conditions et d'actions liées. La fonctionbar
est proprement séparée des décisions. Si nous avons vu les détails des conditions et des actions, il se pourrait que celafoo
en fasse encore trop, mais pour l'instant c'est une bonne solution.bar
votre part devraient les passer manuellement enfoo
tant que paramètres. Si tel est le cas et si ellefoo
n'est appelée qu'une seule fois, je me tromperais vers l'utilisation de la version goto pour éviter de définir deux fonctions étroitement couplées qui finiraient par ne pas être très réutilisables.if (!executeStepA() || !executeStepB() || !executeStepC()) return
Les programmeurs C de la vieille école utilisent
goto
dans ce cas. C'est le seul usagegoto
qui est réellement encouragé par le guide de style Linux, il s'appelle l'exit de la fonction centralisée:Certaines personnes contournent l'utilisation
goto
en enveloppant le corps dans une boucle et en la rompant, mais les deux approches font effectivement la même chose. L'goto
approche est meilleure si vous avez besoin d'un autre nettoyage uniquement si elleexecuteStepA()
a réussi:Avec l'approche en boucle, vous vous retrouveriez avec deux niveaux de boucles dans ce cas.
la source
goto
et pensent immédiatement "C'est un code terrible" mais il a ses utilisations valides.goto
et je n'ai même pas besoin de penser pour voir que c'est un code terrible. J'ai dû une fois le maintenir, c'est très méchant. Le PO propose une alternative raisonnable en langage C à la fin de la question, et je l'ai incluse dans ma réponse.throw
est à bien des égards pire quegoto
parce qu'avecthrow
ce n'est même pas clair du contexte local où vous allez vous retrouver! Utilisez les mêmes considérations de conception pour le flux de contrôle de style goto que pour les exceptions.Il s'agit d'une situation courante et il existe de nombreuses façons courantes de la gérer. Voici ma tentative de réponse canonique. Veuillez commenter si j'ai raté quelque chose et je garderai ce post à jour.
Ceci est une flèche
Ce que vous discutez est connu sous le nom d' anti-modèle de flèche . Il s'agit d'une flèche car la chaîne d'if imbriqués forme des blocs de code qui se développent de plus en plus à droite puis de nouveau à gauche, formant une flèche visuelle qui "pointe" vers le côté droit du volet de l'éditeur de code.
Aplatissez la flèche avec la garde
Nous discutons ici de quelques moyens courants d'éviter la flèche . La méthode la plus courante consiste à utiliser un modèle de garde , dans lequel le code gère d'abord les flux d'exception, puis gère le flux de base, par exemple au lieu de
... vous utiliseriez ....
Lorsqu'il y a une longue série de gardes, cela aplatit considérablement le code car toutes les gardes apparaissent complètement à gauche et vos ifs ne sont pas imbriqués. De plus, vous associez visuellement la condition logique à son erreur associée, ce qui permet de savoir plus facilement ce qui se passe:
La Flèche:
Garde:
Ceci est objectivement et quantifiablement plus facile à lire car
Comment ajouter du code commun à la fin
Le problème avec le modèle de garde est qu'il repose sur ce qu'on appelle le «retour opportuniste» ou la «sortie opportuniste». En d'autres termes, cela rompt le modèle selon lequel chaque fonction doit avoir exactement un point de sortie. Il s'agit d'un problème pour deux raisons:
Ci-dessous, j'ai fourni quelques options pour contourner cette limitation soit en utilisant les fonctionnalités du langage, soit en évitant complètement le problème.
Option 1. Vous ne pouvez pas faire cela: utilisez
finally
Malheureusement, en tant que développeur c ++, vous ne pouvez pas faire cela. Mais c'est la réponse numéro un pour les langues qui contiennent un mot-clé finally, car c'est exactement à cela qu'il sert.
Option 2. Évitez le problème: restructurez vos fonctions
Vous pouvez éviter le problème en divisant le code en deux fonctions. Cette solution a l'avantage de fonctionner pour n'importe quelle langue, et en outre, elle peut réduire la complexité cyclomatique , qui est un moyen éprouvé de réduire votre taux de défauts, et améliore la spécificité de tous les tests unitaires automatisés.
Voici un exemple:
Option 3. Astuce linguistique: utilisez une fausse boucle
Une autre astuce que je vois consiste à utiliser while (true) et break, comme indiqué dans les autres réponses.
Bien que cela soit moins «honnête» que l'utilisation
goto
, il est moins susceptible d'être gâché lors de la refactorisation, car il marque clairement les limites de la portée logique. Un codeur naïf qui coupe et colle vos étiquettes ou vosgoto
relevés peut causer de gros problèmes! (Et franchement, le modèle est si commun maintenant je pense qu'il communique clairement l'intention, et n'est donc pas du tout "malhonnête").Il existe d'autres variantes de cette option. Par exemple, on pourrait utiliser à la
switch
place dewhile
. Toute construction de langage avec unbreak
mot clé fonctionnerait probablement.Option 4. Tirez parti du cycle de vie des objets
Une autre approche exploite le cycle de vie des objets. Utilisez un objet contextuel pour transporter vos paramètres (quelque chose que notre exemple naïf manque étrangement) et éliminez-le lorsque vous avez terminé.
Remarque: assurez-vous de bien comprendre le cycle de vie de l'objet de la langue de votre choix. Vous avez besoin d'une sorte de ramasse-miettes déterministe pour que cela fonctionne, c'est-à-dire que vous devez savoir quand le destructeur sera appelé. Dans certaines langues, vous devrez utiliser
Dispose
un destructeur.Option 4.1. Tirer parti du cycle de vie de l'objet (modèle wrapper)
Si vous allez utiliser une approche orientée objet, autant le faire correctement. Cette option utilise une classe pour «encapsuler» les ressources qui nécessitent un nettoyage, ainsi que ses autres opérations.
Encore une fois, assurez-vous de bien comprendre le cycle de vie de votre objet.
Option 5. Astuce linguistique: utiliser l'évaluation de court-circuit
Une autre technique consiste à profiter de l' évaluation des courts-circuits .
Cette solution tire parti du fonctionnement de l'opérateur &&. Lorsque le côté gauche de && est évalué à faux, le côté droit n'est jamais évalué.
Cette astuce est plus utile lorsque du code compact est requis et lorsque le code ne risque pas de voir beaucoup de maintenance, par exemple, vous implémentez un algorithme bien connu. Pour un codage plus général, la structure de ce code est trop fragile; même une modification mineure de la logique pourrait déclencher une réécriture totale.
la source
Fais juste
C'est si simple.
En raison de trois modifications auxquelles chacune a fondamentalement changé la question (quatre si l'on compte la révision jusqu'à la version 1), j'inclus l'exemple de code auquel je réponds:
la source
&&
liste beaucoup plus claire. Je brise généralement les conditions sur des lignes distinctes et j'ajoute une fin// explanation
à chacune ... En fin de compte, c'est beaucoup moins de code à regarder, et une fois que vous comprenez comment cela&&
fonctionne, il n'y a pas d'effort mental continu. Mon impression est que la plupart des programmeurs C ++ professionnels seraient familiers avec cela, mais comme vous le dites dans différents secteurs / projets, l'objectif et l'expérience diffèrent.Il existe en fait un moyen de différer les actions en C ++: en utilisant le destructeur d'un objet.
En supposant que vous avez accès à C ++ 11:
Et puis en utilisant cet utilitaire:
la source
executeThisFunctionInAnyCase();
s'exécutera même s'ilfoo();
lève une exception. Lors de l'écriture de code sans exception, il est recommandé de placer toutes ces fonctions de nettoyage dans un destructeur.foo()
. Et si vous le faites, attrapez-le. Problème résolu. Corrigez les bogues en les corrigeant, pas en écrivant des solutions.Defer
classe est un petit morceau de code réutilisable qui vous permet d'effectuer n'importe quel nettoyage de fin de bloc, d'une manière sûre d'exception. il est plus communément appelé garde de portée . oui, toute utilisation d'un garde-portée peut être exprimée de manière plus manuelle, tout comme n'importe quellefor
boucle peut être exprimée sous forme de bloc et dewhile
boucle, qui à son tour peut être exprimée avecif
etgoto
, qui peut être exprimée en langage assembleur si vous le souhaitez, ou pour ceux qui sont de vrais maîtres, en altérant les bits en mémoire via des rayons cosmiques dirigés par l'effet papillon de grognements courts et de chants spéciaux. mais pourquoi faire ça.Il y a une belle technique qui n'a pas besoin d'une fonction wrapper supplémentaire avec les instructions de retour (la méthode prescrite par Itjax). Il utilise une
while(0)
pseudo-boucle do . Lewhile (0)
garantit qu'il ne s'agit en fait pas d'une boucle mais qu'il n'est exécuté qu'une seule fois. Cependant, la syntaxe de boucle permet l'utilisation de l'instruction break.la source
inline
. Quoi qu'il en soit, c'est une bonne technique à connaître, car elle aide à plus que ce problème.Vous pouvez également faire ceci:
De cette façon, vous avez une taille de croissance linéaire minimale, +1 ligne par appel, et il est facilement maintenable.
EDIT : (Merci @Unda) Pas un grand fan parce que vous perdez la visibilité IMO:
la source
Est-ce que cela fonctionnerait? Je pense que c'est équivalent à votre code.
la source
ok
lorsque j'utilise la même variable comme celle-ci.En supposant que le code souhaité est tel que je le vois actuellement:
Je dirais que l'approche correcte, en ce sens qu'elle est la plus simple à lire et la plus facile à maintenir, aurait moins de niveaux d'indentation, ce qui est (actuellement) l'objectif déclaré de la question.
Cela évite tout besoin de
goto
s, d'exceptions, dewhile
boucles factices ou d'autres constructions difficiles et continue simplement le travail à portée de main.la source
return
etbreak
de sauter hors de la boucle sans avoir besoin d'introduire des variables "drapeau" supplémentaires. Dans ce cas, l'utilisation d'un goto serait tout aussi innocente - rappelez-vous que vous échangez une complexité goto supplémentaire contre une complexité variable mutable supplémentaire.newbie
, il fournit une solution plus propre sans inconvénients. Je note également qu'il ne repose pas sur le fait d'steps
avoir la même signature ou même d'être des fonctions plutôt que des blocs. Je peux voir que cela est utilisé comme un refactorisateur de premier passage même lorsqu'une approche plus sophistiquée est valide.Ce n'est peut-être pas la meilleure solution, mais vous pouvez mettre vos instructions en
do .. while (0)
boucle et utiliser desbreak
instructions à la place dereturn
.la source
do .. while (0)
pour les définitions de macro abusent également des boucles, mais il est considéré comme OK.Vous pouvez mettre toutes les
if
conditions, formatées comme vous le souhaitez dans une fonction qui leur est propre, le retour exécute laexecuteThisFunctionInAnyCase()
fonction.À partir de l'exemple de base de l'OP, les tests et l'exécution des conditions peuvent être séparés en tant que tels;
Et puis appelé comme tel;
Si des lambdas C ++ 11 sont disponibles (il n'y avait pas de balise C ++ 11 dans l'OP, mais ils peuvent toujours être une option), alors nous pouvons renoncer à la fonction séparée et envelopper cela dans un lambda.
la source
Si vous n'aimez pas
goto
et n'aimez pas lesdo { } while (0);
boucles et que vous aimez utiliser C ++, vous pouvez également utiliser un lambda temporaire pour avoir le même effet.la source
if
vous n'aimez pas goto&&
vous n'aimez pas faire {} tandis que (0)&&
vous aimez C ++ ... Désolé, vous n'avez pas pu résister, mais cette dernière condition échoue parce que la question est étiquetée c ainsi que c ++Les chaînes de IF / ELSE dans votre code ne sont pas le problème de langue, mais la conception de votre programme. Si vous êtes en mesure de reformuler ou de réécrire votre programme, je vous suggère de consulter Design Patterns ( http://sourcemaking.com/design_patterns ) pour trouver une meilleure solution.
Habituellement, lorsque vous voyez beaucoup de FI et autres dans votre code, c'est l'occasion d'implémenter le modèle de conception de stratégie ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net ) ou peut-être une combinaison d'autres modèles.
Je suis sûr qu'il existe des alternatives pour écrire une longue liste de if / else, mais je doute qu'ils changeront quoi que ce soit, sauf que la chaîne vous semblera jolie (Cependant, la beauté est dans l'œil du spectateur s'applique toujours au code aussi:-) ) . Vous devriez être préoccupé par des choses comme (dans 6 mois quand j'ai une nouvelle condition et que je ne me souviens de rien de ce code, pourrai-je l'ajouter facilement? Ou si la chaîne change, à quelle vitesse et sans erreur vais-je le mettre en œuvre)
la source
Vous faites juste ça ..
99 fois sur 100, c'est la seule façon de le faire.
N'essayez jamais, jamais, jamais de faire quelque chose de "délicat" dans le code informatique.
Au fait, je suis presque sûr que ce qui suit est la solution réelle que vous aviez en tête ...
L' instruction continue est essentielle dans la programmation algorithmique. (Autant que, l'instruction goto est critique dans la programmation algorithmique.)
Dans de nombreux langages de programmation, vous pouvez le faire:
(Remarquez tout d'abord: les blocs nus comme cet exemple sont une partie critique et importante de l'écriture d'un beau code, en particulier si vous avez affaire à une programmation "algorithmique".)
Encore une fois, c'est exactement ce que vous aviez dans votre tête, non? Et c'est la belle façon de l'écrire, donc vous avez un bon instinct.
Cependant, tragiquement, dans la version actuelle de objective-c (Mis à part - je ne sais pas pour Swift, désolé), il existe une fonctionnalité risible qui vérifie si le bloc englobant est une boucle.
Voici comment contourner cela ...
Alors n'oubliez pas que ..
faire {} while (false);
signifie simplement "faire ce bloc une fois".
c'est-à-dire qu'il n'y a absolument aucune différence entre écrire
do{}while(false);
et simplement écrire{}
.Cela fonctionne maintenant parfaitement comme vous le vouliez ... voici la sortie ...
Il est donc possible que vous voyiez l'algorithme dans votre tête. Vous devriez toujours essayer d'écrire ce que vous avez en tête. (Surtout si vous n'êtes pas sobre, car c'est à ce moment que la jolie sort! :))
Dans les projets "algorithmiques" où cela se produit souvent, dans l'objectif-c, nous avons toujours une macro comme ...
... alors vous pouvez le faire ...
Il y a deux points:
a, même s'il est stupide que objective-c vérifie le type de bloc dans lequel se trouve une instruction continue, il est troublant de "combattre cela". C'est donc une décision difficile.
b, il y a la question si vous indentez, dans l'exemple, ce bloc? Je perds le sommeil sur des questions comme ça, donc je ne peux pas vous conseiller.
J'espère que ça aide.
la source
if
, vous pouvez également utiliser des noms de fonction plus descriptifs et placer les commentaires dans les fonctions.Demandez à vos fonctions d'exécution de lever une exception si elles échouent au lieu de renvoyer false. Ensuite, votre code d'appel pourrait ressembler à ceci:
Bien sûr, je suppose que dans votre exemple d'origine, l'étape d'exécution ne retournerait que faux en cas d'erreur survenant à l'intérieur de l'étape?
la source
Beaucoup de bonnes réponses déjà, mais la plupart d'entre elles semblent faire des compromis sur une partie (certes très peu) de la flexibilité. Une approche courante qui ne nécessite pas ce compromis consiste à ajouter une variable d' état / continue . Le prix est, bien sûr, une valeur supplémentaire pour suivre:
la source
ok &= conditionX;
et pas simplementok = conditionX;
?En C ++ (la question est balisée à la fois C et C ++), si vous ne pouvez pas modifier les fonctions pour utiliser des exceptions, vous pouvez toujours utiliser le mécanisme d'exception si vous écrivez une petite fonction d'aide comme
Ensuite, votre code pourrait se lire comme suit:
Si vous êtes dans une syntaxe sophistiquée, vous pouvez le faire fonctionner via un cast explicite:
Ensuite, vous pouvez écrire votre code comme
la source
Si votre code est aussi simple que votre exemple et que votre langue prend en charge les évaluations de court-circuit, vous pouvez essayer ceci:
Si vous passez des arguments à vos fonctions et récupérez d'autres résultats afin que votre code ne puisse pas être écrit de la manière précédente, la plupart des autres réponses seraient mieux adaptées au problème.
la source
Pour C ++ 11 et au-delà, une bonne approche pourrait être d'implémenter un système de sortie de portée similaire au mécanisme de portée (sortie) de D.
Une façon possible de l'implémenter est d'utiliser des lambdas C ++ 11 et des macros d'assistance:
Cela vous permettra de revenir tôt à partir de la fonction et de vous assurer que le code de nettoyage que vous définissez est toujours exécuté à la sortie de la portée:
Les macros ne sont vraiment que de la décoration.
MakeScopeExit()
peut être utilisé directement.la source
[=]
est généralement mauvais pour un lambda de portée.[&]
: il est sûr et peu surprenant. Capture par valeur uniquement lorsque la lambda (ou les copies) pourrait survivre plus longtemps que la portée au point de déclaration ...Pourquoi personne ne donne la solution la plus simple? :RÉ
Si toutes vos fonctions ont la même signature, vous pouvez le faire de cette façon (pour le langage C):
Pour une solution C ++ propre, vous devez créer une classe d'interface qui contient une méthode d' exécution et encapsule vos étapes dans des objets.
Ensuite, la solution ci-dessus ressemblera à ceci:
la source
En supposant que vous n'avez pas besoin de variables de condition individuelles, inverser les tests et utiliser le else-falthrough comme chemin "ok" vous permettrait d'obtenir un ensemble plus vertical d'instructions if / else:
L'omission de la variable échouée rend le code un peu trop obscur IMO.
Déclarer les variables à l'intérieur est très bien, ne vous inquiétez pas de = vs ==.
C'est obscur, mais compact:
la source
Cela ressemble à une machine à états, ce qui est pratique car vous pouvez facilement l'implémenter avec un modèle d'état .
En Java, cela ressemblerait à ceci:
Une implémentation fonctionnerait comme suit:
Etc. Ensuite, vous pouvez remplacer la grande condition if par:
la source
Comme Rommik l'a mentionné, vous pouvez appliquer un modèle de conception pour cela, mais j'utiliserais le modèle Décorateur plutôt que la stratégie puisque vous souhaitez enchaîner les appels. Si le code est simple, j'irais avec l'une des réponses bien structurées pour empêcher l'imbrication. Cependant, s'il est complexe ou nécessite un chaînage dynamique, le motif Décorateur est un bon choix. Voici un diagramme de classe yUML :
Voici un exemple de programme LinqPad C #:
Le meilleur livre à lire sur les modèles de conception, à mon humble avis, est Head First Design Patterns .
la source
Plusieurs réponses ont fait allusion à un modèle que j'ai vu et utilisé plusieurs fois, en particulier dans la programmation réseau. Dans les piles de réseau, il y a souvent une longue séquence de demandes, dont chacune peut échouer et arrêter le processus.
Le modèle commun était d'utiliser
do { } while (false);
J'ai utilisé une macro pour le
while(false)
fairedo { } once;
Le motif commun était:Ce modèle était relativement facile à lire, et permettait d'utiliser des objets qui seraient correctement détruits et évitait également les retours multiples, ce qui rendait le pas et le débogage un peu plus faciles.
la source
Pour améliorer la réponse de Mathieu C ++ 11 et éviter les coûts d'exécution engendrés par l'utilisation de
std::function
, je suggère d'utiliser les éléments suivantsCette classe de modèle simple acceptera n'importe quel foncteur qui peut être appelé sans aucun paramètre, et cela sans aucune allocation de mémoire dynamique et se conforme donc mieux à l'objectif d'abstraction de C ++ sans surcharge inutile. Le modèle de fonction supplémentaire est là pour simplifier l'utilisation par déduction de paramètres de modèle (qui n'est pas disponible pour les paramètres de modèle de classe)
Exemple d'utilisation:
Tout comme la réponse de Mathieu, cette solution est totalement sûre et
executeThisFunctionInAnyCase
sera appelée dans tous les cas. Si eux-executeThisFunctionInAnyCase
mêmes lancent, les destructeurs sont marqués implicitementnoexcept
et par conséquent, un appel àstd::terminate
serait émis au lieu de provoquer une exception lors du déroulement de la pile.la source
functor
dansdeferred
le constructeur, pas besoin de forcer amove
.Il semble que vous souhaitiez passer tous vos appels à partir d'un seul bloc. Comme d'autres l'ont proposé, vous devez utiliser soit une
while
boucle et quitter en utilisantbreak
soit une nouvelle fonction avec laquelle vous pouvez quitterreturn
(peut être plus propre).Personnellement
goto
, je bannis , même pour la sortie de fonction. Ils sont plus difficiles à repérer lors du débogage.Une alternative élégante qui devrait fonctionner pour votre flux de travail consiste à créer un tableau de fonctions et à itérer sur celui-ci.
la source
Parce que vous avez également [... un bloc de code ...] entre les exécutions, je suppose que vous avez une allocation de mémoire ou des initialisations d'objet. De cette façon, vous devez vous soucier de nettoyer tout ce que vous avez déjà initialisé à la sortie, et également le nettoyer si vous rencontrez un problème et que l'une des fonctions retourne faux.
Dans ce cas, le mieux que j'ai eu dans mon expérience (quand je travaillais avec CryptoAPI) était de créer de petites classes, dans le constructeur vous initialisez vos données, dans le destructeur vous les désinitialisez. Chaque classe de fonction suivante doit être enfant de la classe de fonction précédente. Si quelque chose a mal tourné, jetez l'exception.
Et puis dans votre code il vous suffit d'appeler:
Je suppose que c'est la meilleure solution si chaque appel de ConditionX initialise quelque chose, alloue de la mémoire, etc. Mieux vaut être sûr que tout sera nettoyé.
la source
une façon intéressante est de travailler avec des exceptions.
Si vous écrivez un tel code, vous allez en quelque sorte dans la mauvaise direction. Je ne le verrai pas comme "le problème" d'avoir un tel code, mais d'avoir une "architecture" aussi désordonnée.
Astuce: discutez de ces cas avec un développeur chevronné en qui vous avez confiance ;-)
la source
Une autre approche -
do - while
boucle, même si elle a été mentionnée auparavant, il n'y avait aucun exemple qui montrerait à quoi elle ressemble:(Eh bien, il y a déjà une réponse avec
while
boucle mais lado - while
boucle ne vérifie pas de manière redondante la vérité (au début) mais à la fin xD (cela peut être ignoré, cependant)).la source