Je pense que le résumé succinct de la raison pour laquelle null est indésirable est que les états sans signification ne devraient pas être représentables .
Supposons que je modélise une porte. Elle peut être dans l'un des trois états suivants : ouverte, fermée mais non verrouillée, et fermée et verrouillée. Je pourrais la modéliser de la façon suivante
class Door
private bool isShut
private bool isLocked
et il est clair comment faire correspondre mes trois états à ces deux variables booléennes. Mais cela laisse un quatrième état, non désiré, disponible : isShut==false && isLocked==true
. Comme les types que j'ai choisis comme représentation admettent cet état, je dois faire un effort mental pour m'assurer que la classe ne se retrouve jamais dans cet état (peut-être en codant explicitement un invariant). En revanche, si j'utilisais un langage avec des types de données algébriques ou des énumérations vérifiées qui me permette de définir
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
alors je pourrais définir
class Door
private DoorState state
et il n'y a plus de soucis. Le système de types garantit qu'il n'y a que trois états possibles pour une instance de class Door
pour être dans. C'est à cela que servent les systèmes de types : exclure explicitement toute une classe d'erreurs au moment de la compilation.
Le problème avec null
est que chaque type de référence obtient cet état supplémentaire dans son espace qui est généralement indésirable. A string
La variable peut être n'importe quelle séquence de caractères, ou peut être cette extra folle null
qui ne correspond pas à mon domaine de problème. A Triangle
a trois Point
qui ont eux-mêmes X
y Y
mais malheureusement, les Point
ou le Triangle
peut être une valeur nulle qui n'a aucun sens pour le domaine graphique dans lequel je travaille. Etc.
Si vous avez l'intention de modéliser une valeur qui n'existe peut-être pas, vous devez choisir de le faire explicitement. Si la façon dont j'ai l'intention de modéliser les gens est que tout Person
a un FirstName
et un LastName
mais seules certaines personnes ont MiddleName
alors j'aimerais dire quelque chose comme
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
où string
est supposé être un type non nul. Il n'y a donc pas d'invariants délicats à établir et pas de problèmes inattendus. NullReferenceException
lorsqu'on essaie de calculer la longueur du nom d'une personne. Le système de types permet de s'assurer que tout code traitant de l'élément MiddleName
rend compte de la possibilité qu'il soit None
tandis que tout code traitant de l FirstName
peut supposer sans risque qu'il y a là une valeur.
Ainsi, par exemple, en utilisant le type ci-dessus, nous pourrions créer cette fonction idiote :
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
sans aucune inquiétude. En revanche, dans un langage où les références de types tels que les chaînes de caractères peuvent être nulles, il faut supposer que
class Person
private string FirstName
private string MiddleName
private string LastName
vous vous retrouvez à écrire des choses comme
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
qui explose si l'objet Personne entrant n'a pas l'invariant de tout être non-nul, ou
let TotalNumCharsInPersonsName(p:Person) =
(if p.FirstName=null then 0 else p.FirstName.Length)
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ (if p.LastName=null then 0 else p.LastName.Length)
ou peut-être
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
en supposant que p
garantit que le premier/dernier est présent mais que le milieu peut être nul, ou peut-être que vous effectuez des contrôles qui lèvent différents types d'exceptions, ou qui sait quoi. Tous ces choix d'implémentation fous et ces choses auxquelles il faut penser apparaissent parce qu'il y a cette stupide valeur représentable que vous ne voulez pas ou dont vous n'avez pas besoin.
La valeur nulle ajoute généralement une complexité inutile. La complexité est l'ennemi de tout logiciel, et vous devez vous efforcer de la réduire chaque fois que cela est possible.
(Notez bien que même ces exemples simples sont plus complexes. Même si un FirstName
ne peut être null
, a string
peut représenter ""
(la chaîne vide), qui n'est probablement pas non plus un nom de personne que nous avons l'intention de modéliser. En tant que tel, même avec des chaînes non nulles, il se peut que nous soyons toujours en train de "représenter des valeurs sans signification". Encore une fois, vous pouvez choisir de lutter contre ce problème soit par le biais d'invariants et de code conditionnel au moment de l'exécution, soit en utilisant le système de types (par exemple, pour avoir un NonEmptyString
type). Cette dernière solution est peut-être malvenue ( les "bons" types sont souvent "fermés" sur un ensemble d'opérations communes, et par exemple NonEmptyString
n'est pas fermé sur .SubString(0,0)
), mais il démontre plus de points dans l'espace de conception. En fin de compte, dans n'importe quel système de types donné, il y a une certaine complexité dont il se débarrassera très bien, et une autre complexité qui est juste intrinsèquement plus difficile à se débarrasser. La clé de ce sujet est que dans presque chaque le passage de "références nulles par défaut" à "références non nulles par défaut" est presque toujours un changement simple qui rend le système de types beaucoup plus apte à combattre la complexité et à exclure certains types d'erreurs et d'états sans signification. Il est donc assez fou que tant de langages répètent cette erreur encore et encore).
11 votes
Si vous ajoutez des balises à cette question pour la programmation fonctionnelle ou F#, vous obtiendrez certainement des réponses fantastiques.
0 votes
J'ai ajouté l'étiquette de programmation fonctionnelle puisque l'option-type vient du monde de la programmation fonctionnelle. Je préfère ne pas le marquer F#(trop spécifique). BTW quelqu'un avec des pouvoirs de taxonomie doit ajouter une étiquette peut-être-type ou option-type.
4 votes
Il n'y a pas vraiment besoin d'étiquettes aussi spécifiques, je suppose. Les balises sont principalement destinées à permettre aux gens de trouver des questions pertinentes (par exemple, "questions dont je sais beaucoup de choses et auxquelles je pourrai répondre", et "programmation fonctionnelle" est très utile à cet égard. Mais quelque chose comme "null" ou "option-type" est beaucoup moins utile. Peu de gens sont susceptibles de surveiller une balise "option-type" à la recherche de questions auxquelles ils peuvent répondre. ;)
0 votes
N'oublions pas que l'une des principales raisons de null est que l'évolution des ordinateurs est fortement liée à la théorie des ensembles. Null est l'un des ensembles les plus importants de toute la théorie des ensembles. Sans lui, des algorithmes entiers s'effondreraient. Par exemple, effectuer un tri par fusion. Cela implique de diviser une liste en deux plusieurs fois. Et si la liste compte 7 éléments ? D'abord vous la divisez en 4 et 3. Puis 2, 2, 2, et 1. Puis 1, 1, 1, 1, 1, 1, 1, 1, et.... null ! Null a une utilité, mais une utilité que vous ne voyez pas dans la pratique. Il existe plutôt dans le domaine théorique.
6 votes
@steven_desu - Je ne suis pas d'accord. Dans les langages "nullables", vous pouvez avoir une référence à une liste vide [], ainsi qu'une référence à une liste nulle. Cette question porte sur la confusion entre les deux.