Je me vois utiliser de plus en plus de types immuables lorsque les instances de la classe ne devraient pas être modifiées . Il nécessite plus de travail (voir l'exemple ci-dessous), mais facilite l'utilisation des types dans un environnement multithread.
Dans le même temps, je vois rarement des types immuables dans d'autres applications, même lorsque la mutabilité ne profiterait à personne.
Question: Pourquoi les types immuables sont-ils si rarement utilisés dans d'autres applications?
- Est-ce parce qu'il est plus long d'écrire du code pour un type immuable,
- Ou est-ce que je manque quelque chose et qu'il y a des inconvénients importants lors de l'utilisation de types immuables?
Exemple de la vie réelle
Disons que vous obtenez à Weather
partir d'une API RESTful comme ça:
public Weather FindWeather(string city)
{
// TODO: Load the JSON response from the RESTful API and translate it into an instance
// of the Weather class.
}
Ce que nous verrions généralement est (nouvelles lignes et commentaires supprimés pour raccourcir le code):
public sealed class Weather
{
public City CorrespondingCity { get; set; }
public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
public int PrecipitationRisk { get; set; }
public int Temperature { get; set; }
}
D'un autre côté, je l'écrirais de cette façon, étant donné qu'obtenir un Weather
de l'API, puis le modifier serait bizarre: changer Temperature
ou Sky
ne changerait pas la météo dans le monde réel, et changer CorrespondingCity
n'a pas de sens non plus.
public sealed class Weather
{
private readonly City correspondingCity;
private readonly SkyState sky;
private readonly int precipitationRisk;
private readonly int temperature;
public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
int temperature)
{
this.correspondingCity = correspondingCity;
this.sky = sky;
this.precipitationRisk = precipitationRisk;
this.temperature = temperature;
}
public City CorrespondingCity { get { return this.correspondingCity; } }
public SkyState Sky { get { return this.sky; } }
public int PrecipitationRisk { get { return this.precipitationRisk; } }
public int Temperature { get { return this.temperature; } }
}
la source
{get; private set;}
, et même la variable mutable devrait avoir un constructeur, car tous ces champs doivent toujours être définis et pourquoi ne pas appliquer cela? Apporter ces deux changements parfaitement raisonnables les amène à la fonctionnalité et à la parité LoC.Réponses:
Je programme en C # et Objective-C. J'aime vraiment la frappe immuable, mais dans la vraie vie, j'ai toujours été contraint de limiter son utilisation, principalement pour les types de données, pour les raisons suivantes:
StringBuilder
ouUriBuilder
en C #, ouWeatherBuilder
dans votre cas. C'est la principale raison pour laquelle je conçois de nombreuses classes ne valent pas un tel effort.En bref, l'immuabilité est bonne pour les objets qui se comportent comme des valeurs ou qui n'ont que quelques propriétés. Avant de rendre immuable quelque chose, vous devez considérer l'effort nécessaire et l'utilisabilité de la classe elle-même après l'avoir rendue immuable.
la source
Juste des types généralement immuables créés dans des langages qui ne tournent pas autour de l'immuabilité auront tendance à coûter plus de temps au développeur pour créer ainsi que potentiellement utiliser s'ils nécessitent un type d'objet "constructeur" pour exprimer les changements souhaités (cela ne signifie pas que l'ensemble le travail sera plus, mais il y a un coût initial dans ces cas). De plus, que le langage facilite ou non la création de types immuables, il aura tendance à toujours nécessiter un certain traitement et une surcharge de mémoire pour les types de données non triviaux.
Rendre les fonctions dépourvues d'effets secondaires
Si vous travaillez dans des langages qui ne tournent pas autour de l'immuabilité, je pense que l'approche pragmatique n'est pas de chercher à rendre immuable chaque type de données. Un état d'esprit potentiellement beaucoup plus productif qui vous offre plusieurs des mêmes avantages consiste à se concentrer sur la maximisation du nombre de fonctions dans votre système qui ne provoquent aucun effet secondaire .
À titre d'exemple simple, si vous avez une fonction qui provoque un effet secondaire comme celui-ci:
Ensuite, nous n'avons pas besoin d'un type de données entier immuable qui interdit aux opérateurs comme l'affectation post-initialisation de faire en sorte que cette fonction évite les effets secondaires. Nous pouvons simplement faire ceci:
Maintenant, la fonction ne joue pas avec
x
ou quoi que ce soit en dehors de sa portée, et dans ce cas trivial, nous pourrions même avoir réduit certains cycles en évitant toute surcharge associée à l'indirection / aliasing. À tout le moins, la deuxième version ne devrait pas être plus coûteuse en termes de calcul que la première.Choses qui coûtent cher à copier en entier
Bien sûr, la plupart des cas ne sont pas si simples si nous voulons éviter qu'une fonction ne provoque des effets secondaires. Un cas d'utilisation complexe dans le monde réel pourrait ressembler davantage à ceci:
À ce stade, le maillage peut nécessiter quelques centaines de mégaoctets de mémoire avec plus de cent mille polygones, encore plus de sommets et d'arêtes, de multiples textures, des cibles de morphing, etc.
transform
fonctionner sans effets secondaires, comme ceci:Et c'est dans ces cas où la copie de quelque chose dans son intégralité serait normalement une surcharge épique où j'ai trouvé utile de se transformer
Mesh
en une structure de données persistante et un type immuable avec le "générateur" analogique pour en créer des versions modifiées afin qu'il peut simplement copier peu profondément et compter des pièces qui ne sont pas uniques. Tout cela dans le but de pouvoir écrire des fonctions de maillage sans effets secondaires.Structures de données persistantes
Et dans ces cas où tout copier est si incroyablement cher, j'ai trouvé que l'effort de concevoir un immuable était
Mesh
vraiment rentable même s'il avait un coût légèrement élevé à l'avance, car cela ne simplifiait pas seulement la sécurité des threads. Il a également simplifié l'édition non destructive (permettant à l'utilisateur de superposer les opérations de maillage sans modifier sa copie d'origine), les systèmes d'annulation (désormais, le système d'annulation peut simplement stocker une copie immuable du maillage avant les modifications apportées par une opération sans faire exploser la mémoire). utilisation), et la sécurité des exceptions (maintenant, si une exception se produit dans la fonction ci-dessus, la fonction n'a pas besoin de revenir en arrière et d'annuler tous ses effets secondaires car elle n'en a pas causé au début).Je peux dire avec confiance dans ces cas que le temps nécessaire pour rendre immuables ces structures de données lourdes a permis d'économiser plus de temps qu'il n'en a coûté, car j'ai comparé les coûts de maintenance de ces nouvelles conceptions avec les anciens qui tournaient autour de la mutabilité et des fonctions provoquant des effets secondaires, et les anciennes conceptions mutables coûtaient beaucoup plus de temps et étaient beaucoup plus sujettes aux erreurs humaines, en particulier dans les domaines qui sont vraiment tentants pour les développeurs de les négliger pendant les périodes critiques, comme la sécurité d'exception.
Je pense donc que les types de données immuables sont vraiment payants dans ces cas, mais tout ne doit pas être rendu immuable afin de rendre la majorité des fonctions de votre système sans effets secondaires. Beaucoup de choses sont assez bon marché pour simplement copier en entier. De nombreuses applications du monde réel devront également provoquer des effets secondaires ici et là (tout au moins comme enregistrer un fichier), mais il existe généralement beaucoup plus de fonctions qui pourraient être dépourvues d'effets secondaires.
Le but d'avoir certains types de données immuables pour moi est de s'assurer que nous pouvons écrire le nombre maximum de fonctions pour être exempt d'effets secondaires sans encourir de surcharge épique sous la forme de copies massives de structures de données massives à gauche et à droite en totalité lorsque seules de petites portions d'entre eux doivent être modifiés. La présence de structures de données persistantes dans ces cas finit par devenir un détail d'optimisation pour nous permettre d'écrire nos fonctions sans effets secondaires sans payer un coût épique.
Frais généraux immuables
Maintenant, conceptuellement, les versions modifiables auront toujours un avantage en termes d'efficacité. Il y a toujours cette surcharge de calcul associée aux structures de données immuables. Mais je l'ai trouvé un échange digne dans les cas que j'ai décrits ci-dessus, et vous pouvez vous concentrer à rendre la surcharge suffisamment minime dans la nature. Je préfère ce type d'approche où l'exactitude devient facile et l'optimisation devient plus difficile que l'optimisation étant plus facile mais l'exactitude devenant plus difficile. Ce n'est pas aussi démoralisant d'avoir du code qui fonctionne parfaitement correctement, qui a besoin de quelques mises au point supplémentaires sur du code qui ne fonctionne pas correctement en premier lieu, quelle que soit la rapidité avec laquelle il obtient ses résultats incorrects.
la source
Le seul inconvénient auquel je peux penser est qu'en théorie, l'utilisation de données immuables peut être plus lente que les données mutables - il est plus lent de créer une nouvelle instance et de collecter la précédente que de modifier une existante.
L'autre «problème» est que vous ne pouvez pas utiliser uniquement des types immuables. En fin de compte, vous devez décrire l'état et vous devez utiliser des types mutables pour le faire - sans changer d'état, vous ne pouvez faire aucun travail.
Mais la règle générale reste d'utiliser des types immuables partout où vous le pouvez et de rendre les types mutables uniquement lorsqu'il y a vraiment une raison de le faire ...
Et pour répondre à la question " Pourquoi les types immuables sont-ils si rarement utilisés dans d'autres applications? " - Je ne pense vraiment pas qu'ils le soient ... où que vous regardiez, tout le monde recommande de rendre vos classes aussi immuables que possible ... par exemple: http://www.javapractices.com/topic/TopicAction.do?Id=29
la source
Car
objet mutable est continuellement mis à jour avec l'emplacement d'une automobile physique particulière, alors si j'ai une référence à cet objet, je peux trouver rapidement et facilement où se trouve cette automobile. Si ellesCar
étaient immuables, il serait probablement beaucoup plus difficile de trouver où se trouve actuellement.Pour modéliser n'importe quel système du monde réel où les choses peuvent changer, l'état mutable devra être codé quelque part. Il existe trois façons principales pour un objet de conserver un état mutable:
La première utilisation permet à un objet de créer facilement un instantané immuable de l'état actuel. L'utilisation de la seconde permet à un objet de créer facilement une vue en direct de l'état actuel. L'utilisation du troisième peut parfois rendre certaines actions plus efficaces dans les cas où il n'y a guère de besoin prévu d'instantanés immuables ni de vues en direct.
Au-delà du fait que la mise à jour de l'état stocké à l'aide d'une référence mutable vers un objet immuable est souvent plus lente que la mise à jour de l'état stocké à l'aide d'un objet mutable, l'utilisation d'une référence mutable obligera à renoncer à la possibilité de construire une vue en direct bon marché de l'état. Si l'on n'a pas besoin de créer une vue en direct, ce n'est pas un problème; si, cependant, il fallait créer une vue en direct, une incapacité à utiliser une référence immuable fera toutes les opérations avec la vue - à la fois en lecture et en écriture- beaucoup plus lent qu'ils ne le seraient autrement. Si le besoin d'instantanés immuables dépasse le besoin de vues en direct, l'amélioration des performances des instantanés immuables peut justifier l'atteinte des performances pour les vues en direct, mais si l'on a besoin de vues en direct et n'a pas besoin d'instantanés, l'utilisation de références immuables aux objets mutables est le moyen aller.
la source
Dans votre cas, la réponse est principalement due au fait que C # a un faible support pour Immuabilité ...
Ce serait formidable si:
tout sera immuable par défaut, sauf indication contraire (c'est-à-dire avec un mot clé 'mutable'), mélanger les types immuables et mutables est déroutant
les méthodes de mutation (
With
) seront automatiquement disponibles - bien que cela puisse déjà être fait, voir avecil y aura un moyen de dire que le résultat d'un appel de méthode spécifique (c'est-à-dire
ImmutableList<T>.Add
) ne peut pas être rejeté ou au moins produira un avertissementEt surtout si le compilateur pouvait autant que possible garantir l'immuabilité là où cela était demandé (voir https://github.com/dotnet/roslyn/issues/159 )
la source
MustUseReturnValueAttribute
attribut personnalisé qui fait exactement cela.PureAttribute
a le même effet et est encore mieux pour cela.Ignorance? Inexpérience?
Les objets immuables sont aujourd'hui largement considérés comme supérieurs, mais il s'agit d'un développement relativement récent. Les ingénieurs qui ne se sont pas tenus au courant ou qui sont simplement coincés dans «ce qu'ils savent» ne les utiliseront pas. Et il faut un peu de modifications de conception pour les utiliser efficacement. Si les applications sont anciennes ou si les ingénieurs ont peu de compétences en conception, leur utilisation peut être gênante ou gênante.
la source