J'écris un programme en Java où, à un moment donné, je dois charger un mot de passe pour mon magasin de clés. Juste pour le plaisir, j'ai essayé de garder mon mot de passe en Java le plus court possible en procédant ainsi:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Maintenant, je sais que dans ce cas, c'est un peu idiot. J'aurais pu faire beaucoup d'autres choses, la plupart d'entre elles étant meilleures (je n'aurais pas pu utiliser une variable du tout).
Cependant, j’étais curieux de savoir s’il existait un cas où cela n’était pas si stupide. La seule autre chose à laquelle je peux penser, c'est si vous voulez réutiliser des noms de variables communs tels que count
, ou temp
, mais de bonnes conventions de dénomination et des méthodes courtes rendent peu probable que cela soit utile.
Existe-t-il un cas où l’utilisation de blocs uniquement pour réduire la portée de la variable a du sens?
Réponses:
Premièrement, en parlant aux mécanismes sous-jacents:
En portée C ++ == durée de vie b / c, les destructeurs sont appelés à la sortie de la portée. De plus, une distinction importante en C / C ++ permet de déclarer des objets locaux. Dans l’exécution de C / C ++, le compilateur allouera à l’avance un cadre de pile pour la méthode aussi grande que nécessaire, plutôt que d’allouer plus d’espace de pile en entrée à chaque étendue (qui déclare des variables). Le compilateur est donc en train de réduire ou d’aplatir les portées.
Le compilateur C / C ++ peut réutiliser l’espace de stockage de la pile pour les locaux n’entrant pas en conflit avec la durée de vie (il utilisera généralement l’analyse du code réel pour déterminer cela plutôt que la portée, car c’est plus précis que la portée!).
Je parle de C / C ++ b / c. La syntaxe de Java, c’est-à-dire les accolades et la portée, est au moins en partie dérivée de cette famille. Et aussi parce que C ++ est apparu dans les commentaires de la question.
En revanche, il n’est pas possible d’avoir des objets locaux en Java: tous les objets sont des objets tas, et tous les locals / formals sont des variables de référence ou des types primitifs (d’ailleurs, il en va de même pour la statique).
En outre, en Java, la portée et la durée de vie ne sont pas exactement assimilées: être dans / hors de la portée est essentiellement un concept de compilation qui concerne les conflits d’accessibilité et de nom; rien ne se passe vraiment dans Java à la sortie de la portée en ce qui concerne le nettoyage des variables. La récupération de place de Java détermine (le point final de la) durée de vie des objets.
De plus, le mécanisme de bytecode de Java (la sortie du compilateur Java) tend à promouvoir les variables utilisateur déclarées dans des portées limitées au niveau supérieur de la méthode, car il n’existe pas de fonctionnalité de portée au niveau du bytecode (ce traitement est similaire au traitement C / C ++ du empiler). Au mieux, le compilateur pourrait réutiliser des emplacements de variable locaux, mais (contrairement à C / C ++) uniquement si leur type est identique.
(Cependant, pour être sûr que le compilateur JIT sous-jacent au moment de l'exécution puisse réutiliser le même espace de stockage de pile pour deux variables locales typées différemment (et différentes avec une fente), avec une analyse suffisante du bytecode.)
À l’avantage du programmeur, j’aurais tendance à convenir avec d’autres qu’une méthode (privée) est une meilleure construction, même si elle n’est utilisée qu’une fois.
Pourtant, il n'y a rien de mal à l'utiliser pour résoudre des noms de conflit.
Je l'ai fait dans de rares circonstances lorsque la fabrication de méthodes individuelles est un fardeau. Par exemple, lorsque vous écrivez un interpréteur en utilisant une grande
switch
instruction dans une boucle, je pourrais (en fonction des facteurs) introduire un bloc distinct pour chaque cascase
afin que les cas individuels soient plus séparés les uns des autres, au lieu de faire de chaque cas une méthode différente (chaque méthode étant différente). qui n’est invoqué qu’une fois).(Notez qu'en tant que code par blocs, les cas individuels ont accès aux instructions "break;" et "continue;" concernant la boucle englobante, alors que les méthodes nécessiteraient le renvoi de booléens et l'utilisation de conditionnels par l'appelant pour accéder à ces flux de contrôle. déclarations.)
la source
{ int x = 42; String s="blah"; } { long y = 0; }
, lalong
variable réutilisera les emplacements des deux variablesx
ets
avec tous les compilateurs couramment utilisés (javac
etecj
).À mon avis, il serait plus clair de tirer le bloc de sa propre méthode. Si vous laissez ce bloc à l'intérieur, j'espère voir un commentaire expliquant pourquoi vous le placez là. À ce stade, il est tout simplement plus propre d’avoir l’appel de méthode
initializeKeyManager()
qui garde la variable de mot de passe limitée à la portée de la méthode et indique votre intention avec le bloc de code.la source
Ils peuvent être utiles à Rust, avec ses règles d'emprunt strictes. Et généralement, dans les langages fonctionnels, où ce bloc renvoie une valeur. Mais dans des langages comme Java? Bien que l'idée semble élégante, elle est rarement utilisée dans la pratique, aussi la confusion de cette vision réduira-t-elle la clarté potentielle de la limitation du champ d'application des variables.
Il existe un cas où je trouve cela utile - dans une
switch
déclaration! Parfois, vous voulez déclarer une variable dans uncase
:Ceci, cependant, échouera:
switch
les déclarations sont étranges - ce sont des déclarations de contrôle, mais en raison de leur chute, elles n'introduisent pas d'étendue.Donc, vous pouvez simplement utiliser des blocs pour créer les étendues vous-même:
la source
Il est généralement considéré comme une bonne pratique de garder les méthodes courtes. Réduire la portée de certaines variables peut être un signe que votre méthode est assez longue, suffisamment pour que vous pensiez que la portée des variables doit être réduite. Dans ce cas, il vaut probablement la peine de créer une méthode distincte pour cette partie du code.
Les cas qui me posent problème sont ceux où cette partie du code utilise plus d’une poignée d’arguments. Il existe des situations où vous pouvez vous retrouver avec de petites méthodes qui prennent chacune beaucoup de paramètres (ou qui nécessitent une solution de contournement pour simuler le renvoi de plusieurs valeurs). La solution à ce problème particulier consiste à créer une autre classe qui conserve les diverses variables que vous utilisez et implémente. Elle implémente toutes les étapes que vous avez à l'esprit dans ses méthodes relativement courtes: il s'agit d'un cas d'utilisation typique de l'utilisation d'un modèle Builder .
Cela dit, toutes ces directives de codage peuvent parfois être appliquées de manière approximative. Il arrive parfois que le code soit plus lisible si vous le conservez dans une méthode légèrement plus longue, au lieu de le diviser en petites sous-méthodes (ou modèles de constructeur). En règle générale, cela fonctionne pour les problèmes de taille moyenne, plutôt linéaires et où trop de fractionnements nuisent à la lisibilité (surtout si vous devez basculer entre trop de méthodes pour comprendre ce que fait le code).
Cela peut avoir un sens, mais c'est très rare.
Voici votre code:
Vous créez un
FileInputStream
, mais vous ne le fermez jamais.Une façon de résoudre ce problème consiste à utiliser une instruction try-with-resources . Il étend la
InputStream
, et vous pouvez également utiliser ce bloc pour réduire la portée d'autres variables si vous le souhaitez (dans des limites raisonnables, puisque ces lignes sont liées):(C'est aussi souvent préférable d'utiliser
getDefaultAlgorithm()
au lieu deSunX509
, d'ailleurs.)En outre, il est généralement judicieux de séparer des fragments de code en méthodes distinctes. Cela vaut rarement la peine si c'est juste pour 3 lignes (c'est le cas où la création d'une méthode séparée gêne la lisibilité).
la source
À mon humble avis, il s'agit généralement d'une solution à éviter en grande partie pour le code de production car vous n'êtes généralement pas tenté de le faire, sauf dans les fonctions sporadiques qui effectuent des tâches disparates. J'ai tendance à le faire dans certains codes de scrap utilisés pour tester des choses, mais je ne trouve aucune tentation de le faire dans un code de production où j'ai réfléchi à l'avance à ce que chaque fonction devrait faire, car elle aura naturellement une très grande portée limitée par rapport à son état local.
Je n'ai jamais vraiment vu d'exemples de blocs anonymes utilisés de cette façon (sans condition, sans
try
blocage de transaction, etc.) pour réduire le champ d'application de manière significative dans une fonction qui ne posait pas la question de savoir pourquoi cela ne pouvait pas. être divisée plus loin en fonctions plus simples à portée réduite si elle bénéficiait réellement d’un véritable point de vue SE des blocs anonymes. Il s’agit généralement d’un code éclectique qui utilise un tas de choses vaguement ou très peu liées pour lesquelles nous sommes le plus tentés d’atteindre cet objectif.Par exemple, si vous essayez de faire cela pour réutiliser une variable nommée
count
, cela suggère que vous comptez deux choses disparates. Si le nom de la variable doit être aussi court que possiblecount
, il me semble logique de le lier au contexte de la fonction, ce qui pourrait éventuellement ne compter que pour un type de chose. Vous pouvez ensuite consulter instantanément le nom de la fonction et / ou sa documentation, et voircount
immédiatement ce que cela signifie dans le contexte de ce que fait la fonction sans analyser tout le code. Je ne trouve pas souvent le bon argument pour qu'une fonction compte deux choses différentes réutilisant le même nom de variable de manière à rendre les étendues / blocs anonymes si attrayants par rapport aux alternatives. Cela ne veut pas dire que toutes les fonctions ne doivent compter qu’une seule chose. JE'L’avantage technique d’une fonction réutilisant le même nom de variable pour compter deux choses ou plus et utilisant des blocs anonymes pour limiter la portée de chaque compte individuel. Si la fonction est simple et claire, ce n’est pas la fin du monde d’avoir deux variables de dénomination nommées différemment, la première pouvant avoir un nombre de lignes de visibilité de l’application supérieur à ce qui est idéalement requis. De telles fonctions ne sont généralement pas la source d'erreurs manquant de tels blocs anonymes pour réduire encore la portée minimale de ses variables locales.Pas une suggestion pour les méthodes superflues
Cela ne veut pas dire que vous créez de force des méthodes ou simplement pour réduire la portée. C'est sans doute tout aussi mauvais ou pire, et ce que je suggère, ne devrait pas faire davantage appel à un besoin de méthodes "d'assistance" privées maladroites qu'à un besoin de portées anonymes. C'est trop penser au code actuel et à la façon de réduire la portée des variables que de penser à la façon de résoudre le problème au niveau de l'interface de manière conceptuelle, de manière à obtenir une visibilité nette et courte des états de fonction locaux naturellement, sans imbrication profonde de blocs et plus de 6 niveaux d'indentation. Je suis d'accord avec Bruno sur le fait que vous pouvez entraver la lisibilité du code en insérant avec force 3 lignes de code dans une fonction, mais cela part du principe que vous faites évoluer les fonctions que vous créez en fonction de votre implémentation existante. plutôt que de concevoir les fonctions sans s'embrouiller dans les implémentations. Si vous le faites de cette manière, j'ai trouvé peu de besoin de blocs anonymes qui ne servent à rien, sauf à réduire la portée d'une variable dans une méthode donnée, sauf si vous essayez avec zèle de réduire la portée d'une variable de quelques lignes de code inoffensif où l'introduction exotique de ces blocs anonymes contribue sans doute autant à la surcharge intellectuelle qu'à la suppression.
Essayer de réduire encore les portées minimales
Si réduire la portée des variables locales au minimum absolu en valait la peine, le code devrait être largement accepté, comme ceci:
... car cela entraîne une visibilité minimale de l'état en ne créant même pas de variables pour s'y référer en premier lieu. Je ne veux pas devenir dogmatique, mais je pense en fait que la solution pragmatique consiste à éviter les blocs anonymes autant que possible, tout comme à éviter la monstrueuse ligne de code ci-dessus, et si cela semble absolument nécessaire dans un contexte de production perspective de correction et de maintien des invariants dans une fonction, alors je pense vraiment que la façon dont vous organisez votre code en fonctions et la conception de vos interfaces mérite un nouvel examen. Naturellement, si votre méthode comporte 400 lignes et que la portée d'une variable est visible pour 300 lignes de code de plus que nécessaire, cela peut constituer un véritable problème d'ingénierie, mais ce n'est pas nécessairement un problème à résoudre avec des blocs anonymes.
Utiliser des blocs anonymes partout est exotique, pas idiomatique, et un code exotique risque d'être détesté par d'autres, si ce n'est vous-même, des années plus tard.
L'utilité pratique de la réduction de la portée
L’utilité ultime de la réduction du nombre de variables est de vous permettre d’obtenir une gestion de l’état correcte, de la conserver et de vous permettre de raisonner facilement sur ce que fait une partie donnée d’une base de code - pour pouvoir conserver des invariants conceptuels. Si la gestion de l'état local par une seule fonction est si complexe que vous devez réduire de manière décisive la portée avec un bloc anonyme dans du code qui n'est pas censé être finalisé et bon, là encore, c'est un signe pour moi que la fonction elle-même doit être réexaminée. . Si vous avez des difficultés à raisonner sur la gestion par l'état des variables dans une étendue de fonction locale, imaginez la difficulté de raisonner sur les variables privées accessibles à toutes les méthodes d'une classe entière. Nous ne pouvons pas utiliser de blocs anonymes pour réduire leur visibilité. Pour moi, il est utile de commencer par accepter que les variables tendent à avoir une portée légèrement plus large que celle dont elles auraient idéalement besoin dans de nombreuses langues, à condition que vous ne perdiez pas le contrôle de la situation au point que vous aurez du mal à maintenir les invariants. Ce n'est pas quelque chose à résoudre avec des blocs anonymes, je le vois comme accepter d'un point de vue pragmatique.
la source
Je pense que cette motivation est une raison suffisante pour introduire un bloc, oui.
Comme Casey l'a noté, le bloc est une "odeur de code" qui indique qu'il est peut-être préférable d'extraire le bloc dans une fonction. Mais vous ne pouvez pas.
John Carmark a rédigé une note sur le code incorporé en 2007. Son résumé final
Alors réfléchissez , faites un choix avec un but précis et laissez (facultatif) une preuve décrivant votre motivation pour le choix que vous avez fait.
la source
Mon opinion peut être controversée, mais oui, je pense qu'il y a certainement des situations où j'utiliserais ce type de style. Cependant, "simplement pour réduire la portée de la variable" n'est pas un argument valable, car c'est ce que vous faites déjà. Et faire quelque chose juste pour le faire n'est pas une raison valable. Notez également que cette explication n'a aucune signification si votre équipe a déjà résolu si ce type de syntaxe est conventionnelle ou non.
Je suis principalement un programmeur C #, mais j’ai entendu dire qu’en Java, le renvoi d’un mot de passe tel
char[]
quel vous permet de réduire le temps pendant lequel le mot de passe restera en mémoire. Dans cet exemple, cela ne vous aidera pas, car le ramasse-miettes est autorisé à collecter le tableau à partir du moment où il n'est pas utilisé. Par conséquent, le fait que le mot de passe quitte la portée importe peu. Je ne dis pas s'il est viable d'effacer le tableau une fois que vous en avez terminé, mais dans ce cas, si vous souhaitez le faire, il est logique d'étudier la variable:Ceci est très similaire à l' instruction try-with-resources , car elle étend la ressource et effectue une finalisation dessus une fois que vous avez terminé. Veuillez noter à nouveau que je ne plaide pas pour la gestion des mots de passe de cette manière, mais que si vous décidez de le faire, ce style est raisonnable.
La raison en est que la variable n'est plus valide. Vous l'avez créé, utilisé et invalidé son état pour ne contenir aucune information significative. Cela n'a aucun sens d'utiliser la variable après ce bloc, il est donc raisonnable de l'étendre.
Un autre exemple auquel je peux penser est lorsque vous avez deux variables qui ont un nom et une signification similaires, mais que vous travaillez avec une, puis une autre, et que vous voulez les garder séparées. J'ai écrit ce code en C #:
Vous pouvez faire valoir que je peux simplement déclarer
ILGenerator il;
en haut de la méthode, mais je ne veux pas non plus réutiliser la variable pour différents objets (approche un peu fonctionnelle). Dans ce cas, les blocs facilitent la séparation des tâches effectuées, à la fois de manière syntaxique et visuelle. Il indique également qu'après le blocage, j'en ai finiil
et rien ne devrait y accéder.Un argument contre cet exemple utilise des méthodes. Peut-être que oui, mais dans ce cas, le code n'est pas si long, et le séparer en différentes méthodes nécessiterait également de transmettre toutes les variables utilisées dans le code.
la source