J'ai rencontré une situation où une méthode non-void manque une instruction return et le code compile toujours. Je sais que les instructions après la boucle while sont inaccessibles (code mort) et ne seraient jamais exécutées. Mais pourquoi le compilateur ne prévient-il même pas de renvoyer quelque chose? Ou pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
Si j'ajoute une instruction break (même conditionnelle) dans la boucle while, le compilateur se plaint des infâmes erreurs: Method does not return a value
dans Eclipse et Not all code paths return a value
dans Visual Studio.
public int doNotReturnAnything() {
while(true) {
if(mustReturn) break;
//do something
}
//no return statement
}
Cela est vrai des deux Java et C #.
Pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?
La règle pour les méthodes non nulles est chaque chemin de code qui retourne doit retourner une valeur , et cette règle est satisfaite dans votre programme: code zéro sur zéro les chemins qui retournent renvoient une valeur. La règle n'est pas "chaque méthode non vide doit avoir un chemin de code qui renvoie".
Cela vous permet d'écrire des méthodes de remplacement comme:
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
C'est une méthode non nulle. Il doit être une méthode non nulle afin de satisfaire l'interface. Mais il semble idiot de rendre cette implémentation illégale car elle ne renvoie rien.
Que votre méthode a un point de fin inaccessible en raison d'un goto
(rappelez-vous, un while(true)
est juste une façon plus agréable d'écrire goto
) au lieu d'un throw
(qui est une autre forme de goto
) n'est pas pertinent.
Pourquoi le compilateur ne prévient-il même pas de renvoyer quelque chose?
Parce que le compilateur n'a aucune bonne preuve que le code est incorrect. Quelqu'un a écrit while(true)
et il semble probable que la personne qui l'a fait savait ce qu'elle faisait.
Où puis-je en savoir plus sur l'analyse d'accessibilité en C #?
Voir mes articles sur le sujet, ici:
ATBG: accessibilité de facto et de jure
Et vous pourriez également envisager de lire la spécification C #.
Le compilateur Java est assez intelligent pour trouver le code inaccessible (le code après la boucle while
)
et comme son est inaccessible , il n'y a aucun intérêt à ajouter un return
là-bas (après la fin de while
)
il en va de même avec le conditionnel if
public int get() {
if(someBoolean) {
return 10;
}
else {
return 5;
}
// there is no need of say, return 11 here;
}
puisque la condition booléenne someBoolean
ne peut être évaluée que true
ou false
, il n'est pas nécessaire de fournir un return
explicitement après if-else
, car ce code est inaccessible , et Java ne s'en plaint pas.
Le compilateur sait que la boucle while
n'arrêtera jamais son exécution, donc la méthode ne se terminera jamais, donc une instruction return
n'est pas nécessaire.
Étant donné que votre boucle s'exécute sur une constante - le compilateur sait que c'est une boucle infinie - ce qui signifie que la méthode ne pourra jamais revenir, de toute façon.
Si vous utilisez une variable - le compilateur appliquera la règle:
Cela ne compilera pas:
// Define other methods and classes here
public int doNotReturnAnything() {
var x = true;
while(x == true) {
//do something
}
//no return statement - won't compile
}
La spécification Java définit un concept appelé Unreachable statements
. Vous n'êtes pas autorisé à avoir une instruction inaccessible dans votre code (c'est une erreur de compilation). Vous n'êtes même pas autorisé à avoir une instruction return après l'instruction while (true); en Java. Une instruction while(true);
rend les instructions suivantes inaccessibles par définition, vous n'avez donc pas besoin d'une instruction return
.
Notez que tandis que Halting problem est indécidable dans le cas générique, la définition de la déclaration inaccessible est plus stricte que l'arrêt. Il s'agit de décider de cas très spécifiques où un programme ne s'arrête définitivement pas. Le compilateur n'est théoriquement pas capable de détecter toutes les boucles infinies et les instructions inaccessibles, mais il doit détecter des cas spécifiques définis dans la spécification (par exemple, le cas while(true)
)
Dans la théorie des types, il y a quelque chose appelé type inférieur qui est une sous-classe de tous les autres types (!) Et est utilisé pour indiquer la non-terminaison entre autres choses. (Les exceptions peuvent compter comme un type de non-résiliation - vous ne terminez pas via le chemin normal.)
Donc, d'un point de vue théorique, ces déclarations qui ne se terminent pas peuvent être considérées comme renvoyant quelque chose de type Bottom, qui est un sous-type d'int, donc vous obtenez (en quelque sorte) votre valeur de retour après tout du point de vue du type. Et il est parfaitement normal que cela ne fasse aucun sens qu'un type puisse être une sous-classe de tout le reste, y compris int, car vous n'en renvoyez jamais réellement un.
Dans tous les cas, via une théorie de type explicite ou non, les compilateurs (rédacteurs de compilateurs) reconnaissent que demander une valeur de retour après une instruction non terminale est idiot: il n'y a aucun cas possible dans lequel vous pourriez avoir besoin de cette valeur. (Il peut être agréable que votre compilateur vous avertisse lorsqu'il sait que quelque chose ne se terminera pas, mais il semble que vous souhaitiez qu'il renvoie quelque chose. Mais il vaut mieux laisser les vérificateurs de style à la charpie, car vous avez peut-être besoin de la signature de type qui pour une autre raison (par exemple, le sous-classement) mais vous voulez vraiment la non-résiliation.)
Le compilateur est suffisamment intelligent pour découvrir que votre boucle while
est infinie.
Le compilateur ne peut donc pas penser à votre place. Il ne peut pas deviner pourquoi vous avez écrit ce code. Il en va de même pour les valeurs de retour des méthodes. Java ne se plaindra pas si vous ne faites rien avec les valeurs de retour de la méthode.
Donc, pour répondre à votre question:
Le compilateur analyse votre code et après avoir découvert qu'aucun chemin d'exécution ne mène à la fin de la fonction, il se termine par OK.
Il peut y avoir des raisons légitimes pour une boucle infinie. Par exemple, de nombreuses applications utilisent une boucle principale infinie. Un autre exemple est un serveur Web qui peut attendre indéfiniment des requêtes.
Il n'y a aucune situation dans laquelle la fonction peut atteindre sa fin sans retourner une valeur appropriée. Par conséquent, le compilateur n'a rien à redire.
Visual studio a le moteur intelligent pour détecter si vous avez tapé un type de retour, alors il devrait avoir une instruction de retour avec dans la fonction/méthode.
Comme dans PHP Votre type de retour est vrai si vous n'avez rien retourné. Le compilateur obtient 1 si rien n'est revenu.
À partir de ce
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
Le compilateur sait que l'instruction while elle-même a une nature infinie, alors ne la considérez pas. et le compilateur php deviendra automatiquement vrai si vous écrivez une condition dans l'expression de while.
Mais pas dans le cas de VS, il vous renverra une erreur dans la pile.
"Pourquoi le compilateur ne prévient-il même pas de renvoyer quelque chose? Ou pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne retournant rien?".
Ce code est également valable dans toutes les autres langues (probablement sauf Haskell!). Parce que la première hypothèse est que nous écrivons "intentionnellement" du code.
Et il y a des situations où ce code peut être totalement valide comme si vous allez l'utiliser comme un thread; ou s'il renvoyait un Task<int>
, vous pouvez effectuer une vérification d'erreur en fonction de la valeur int retournée - qui ne doit pas être renvoyée.
Votre boucle while fonctionnera pour toujours et ne sortira donc pas pendant; il continuera à s'exécuter. Par conséquent, la partie extérieure de while {} est inaccessible et il n'y a aucun intérêt à écrire return ou non. Le compilateur est suffisamment intelligent pour déterminer quelle partie est accessible et quelle partie ne l'est pas.
Exemple:
public int xyz(){
boolean x=true;
while(x==true){
// do something
}
// no return statement
}
Le code ci-dessus ne se compilera pas, car il peut arriver que la valeur de la variable x soit modifiée à l'intérieur du corps de la boucle while. Cela rend donc la partie extérieure de la boucle while accessible! Et par conséquent, le compilateur générera une erreur "aucune instruction de retour trouvée".
Le compilateur n'est pas assez intelligent (ou plutôt paresseux;)) pour comprendre que la valeur de x sera modifiée ou non. J'espère que cela efface tout.
Je peux me tromper mais certains débogueurs permettent la modification des variables. Ici, alors que x n'est pas modifié par le code et qu'il sera optimisé par JIT, on peut modifier x en false et la méthode devrait retourner quelque chose (si une telle chose est autorisée par le débogueur C #).
Les spécificités du Java pour cela (qui sont probablement très similaires au cas C #) sont liées à la façon dont le compilateur Java détermine si une méthode est en mesure de revenir.
Plus précisément, les règles sont qu'une méthode avec un type de retour ne doit pas pouvoir se terminer normalement et doit au contraire toujours se terminer brusquement (brusquement ici en indiquant via un retour ou une exception) par JLS 8.4.7 .
Si une méthode est déclarée avoir un type de retour, une erreur de compilation se produit si le corps de la méthode peut se terminer normalement. En d'autres termes, une méthode avec un type de retour ne doit renvoyer qu'en utilisant une instruction return qui fournit un retour de valeur; il n'est pas autorisé de "déposer l'extrémité de son corps" .
Le compilateur cherche à voir si terminaison normale est possible sur la base des règles définies dans JLS 14.21 Unreachable Statements car il définit également les règles pour l'achèvement normal.
Notamment, les règles pour les instructions inaccessibles font un cas spécial uniquement pour les boucles qui ont une expression constante true
définie:
Une instruction while peut se terminer normalement si au moins l'une des conditions suivantes est vraie:
L'instruction while est accessible et l'expression de condition n'est pas une expression constante (§15.28) avec la valeur true.
Il existe une instruction break accessible qui quitte l'instruction while.
Donc, si l'instruction while
peut se terminer normalement, alors une instruction de retour ci-dessous est nécessaire car le code est réputé accessible, et toute boucle while
sans accessible L'instruction break ou l'expression constante true
est considérée comme pouvant se terminer normalement.
Ces règles signifient que votre instruction while
avec une expression vraie constante et sans break
n'est jamais considérée comme se terminant normalement , et donc tout code en dessous n'est jamais considéré comme accessible . La fin de la méthode est en dessous de la boucle, et comme tout ce qui se trouve en dessous de la boucle est inaccessible, la fin de la méthode l'est aussi, et donc la méthode ne peut pas se terminer normalement (ce qui est ce que le complice cherche des).
En revanche, les instructions if
n'ont pas d'exemption spéciale concernant les expressions constantes accordées aux boucles.
Comparer:
// I have a compiler error!
public boolean testReturn()
{
final boolean condition = true;
if (condition) return true;
}
Avec:
// I compile just fine!
public boolean testReturn()
{
final boolean condition = true;
while (condition)
{
return true;
}
}
La raison de la distinction est assez intéressante et est due au désir de permettre des drapeaux de compilation conditionnels qui ne provoquent pas d'erreurs de compilation (à partir du JLS):
On peut s'attendre à ce que l'instruction if soit traitée de la manière suivante:
Une instruction if-then peut se terminer normalement si au moins l'une des conditions suivantes est vraie:
L'instruction if-then est accessible et l'expression de condition n'est pas une expression constante dont la valeur est true.
L'instruction then peut se terminer normalement.
L'instruction then est accessible si l'instruction if-then est accessible et l'expression de condition n'est pas une expression constante dont la valeur est false.
Une instruction if-then-else peut se terminer normalement si l'instruction then-peut se terminer normalement ou l'instruction else peut se terminer normalement.
L'instruction then est accessible si l'instruction if-then-else est accessible et l'expression de condition n'est pas une expression constante dont la valeur est fausse.
L'instruction else est accessible si l'instruction if-then-else est accessible et l'expression de condition n'est pas une expression constante dont la valeur est true.
Cette approche serait compatible avec le traitement d'autres structures de contrôle. Cependant, afin de permettre à l'instruction if d'être utilisée commodément à des fins de "compilation conditionnelle", les règles réelles diffèrent.
Par exemple, l'instruction suivante entraîne une erreur de compilation:
while (false) { x=3; }
car l'instructionx=3;
n'est pas accessible; mais le cas superficiellement similaire:
if (false) { x=3; }
n'entraîne pas d'erreur de compilation. Un compilateur d'optimisation peut se rendre compte que l'instructionx=3;
Ne sera jamais exécutée et peut choisir d'omettre le code de cette instruction dans le fichier de classe généré, mais l'instructionx=3;
N'est pas considérée comme "inaccessible" au sens technique spécifié ici.La justification de ce traitement différent est de permettre aux programmeurs de définir des "variables de drapeau" telles que:
static final boolean DEBUG = false;
Puis écrivez du code tel que:
if (DEBUG) { x=3; }
L'idée est qu'il devrait être possible de changer la valeur de DEBUG de false en true ou de true en false puis de compiler le code correctement sans aucune autre modification du texte du programme.
Pourquoi l'instruction de rupture conditionnelle entraîne-t-elle une erreur de compilation?
Comme indiqué dans les règles d'accessibilité de la boucle, une boucle while peut également se terminer normalement si elle contient une instruction break accessible. Étant donné que les règles d'accessibilité d'une clause alors d'une instruction if
ne prennent pas du tout en considération la condition de if
, une telle condition if
est toujours considérée comme accessible.
Si break
est accessible, le code après la boucle est à nouveau également considéré comme accessible. Puisqu'il n'y a pas de code accessible qui se traduit par une terminaison abrupte après la boucle, la méthode est alors considérée comme pouvant se terminer normalement, et le compilateur la signale donc comme une erreur.