Pourquoi utiliser des retours à la ligne au lieu de diriger avec printf?

79

J'ai entendu dire que vous devriez éviter de diriger les nouvelles lignes lors de l'utilisation printf. Alors qu'au lieu de printf("\nHello World!")vous devriez utiliserprintf("Hello World!\n")

Dans cet exemple particulier ci-dessus, cela n'a aucun sens, car le résultat serait différent, mais considérons ceci:

printf("Initializing");
init();
printf("\nProcessing");
process_data();
printf("\nExiting");

par rapport à:

printf("Initializing\n");
init();
printf("Processing\n");
process_data();
printf("Exiting");

Je ne vois aucun avantage avec les retours à la ligne, sauf que cela a l'air mieux. Y a-t-il une autre raison?

MODIFIER:

Je vais aborder les votes serrés ici et maintenant. Je ne pense pas que cela appartienne au débordement de pile, car cette question concerne principalement le design. Je dirais également que, même s’il s’agit peut-être d’opinions sur ce point, la réponse de Kilian Foth et celle de cmaster prouvent qu’une seule approche offre des avantages très objectifs.

Klutt
la source
5
Cette question se situe à la frontière entre les "problèmes de code" (ce qui n'est pas un sujet) et la "conception de logiciel conceptuel" (ce qui est un sujet). Il se peut qu'il soit fermé, mais ne le prenez pas trop fort. Je pense néanmoins que l'ajout d'exemples de code concrets était le bon choix.
Kilian Foth
46
La dernière ligne fusionnerait avec l'invite de commande sous Linux sans une nouvelle ligne.
GrandmasterB
4
Si cela "a l'air mieux" et qu'il n'y a pas d'inconvénient, c'est une raison suffisante pour le faire, IMO. Écrire un bon code n’est pas différent d’écrire un bon roman ou un bon article technique - le diable est toujours dans les détails.
alephzero
5
Est-ce que init()et process_data()imprimer quelque chose eux-mêmes? À quoi penseriez-vous que le résultat ressemble s'ils le faisaient?
Bergi
9
\nest un terminateur de ligne , pas un séparateur de ligne . Ceci est démontré par le fait que les fichiers texte, sous UNIX, finissent presque toujours par \n.
Jonathon Reinhart

Réponses:

222

Un nombre suffisant d'E / S de terminal sont mises en mémoire tampon en ligne . Ainsi, en mettant fin à un message avec \ n, vous pouvez être certain qu'il sera affiché à temps. Avec un \ n en tête, le message peut ou non être affiché en même temps. Souvent, cela signifie que chaque étape affiche le message de progression de l' étape précédente , ce qui entraîne une confusion sans fin et une perte de temps lorsque vous essayez de comprendre le comportement d'un programme.

Kilian Foth
la source
20
Ceci est particulièrement important lorsque vous utilisez printf pour déboguer un programme qui se bloque. Mettre la nouvelle ligne à la fin d'un printf signifie que la sortie standard de la console est vidée à chaque printf. (Notez que lorsque stdout est redirigé vers un fichier, les bibliothèques std utilisent généralement la mise en cache au lieu de la mise en mémoire de lignes, ce qui rend le débogage de printf assez difficile, même avec une nouvelle ligne à la fin.)
Erik Eidt
25
@ErikEidt Notez que vous devriez utiliser à la fprintf(STDERR, …)place, qui n'est généralement pas du tout tamponnée pour la sortie de diagnostic.
Déduplicateur
4
@DuDuplicator L'écriture de messages de diagnostic dans le flux d'erreur présente également des inconvénients: de nombreux scripts supposent qu'un programme a échoué si quelque chose est écrit dans le flux d'erreur.
Voo
54
@Voo: Je dirais que tout programme supposant une écriture dans stderr indique qu'un échec est en soi incorrect. Le code de sortie du processus est ce qui indique s'il a échoué ou non. S'il s'agissait d'un échec, la sortie stderr en expliquera la raison . Si le processus s'est terminé avec succès (code de sortie zéro), la sortie de stderr devrait être considérée comme une sortie d'information pour un utilisateur humain, sans sémantique particulière analysable par la machine (il peut contenir des avertissements lisibles par l'homme, par exemple), tandis que stdout est la sortie réelle de le programme, éventuellement adapté pour un traitement ultérieur.
Daniel Pryden
23
@Voo: Quels programmes décrivez-vous? Je ne connais pas de progiciel largement utilisé qui se comporte comme vous le décrivez. Je sais que certains programmes le font, mais ce n’est pas comme si j’avais juste inventé la convention que je décris plus haut: c’est ainsi que fonctionne la grande majorité des programmes dans un environnement Unix ou de type Unix, et à ma connaissance, la grande majorité des programmes ont toujours. Je ne recommanderais certainement pas à un programme d’éviter d’écrire sur stderr simplement parce que certains scripts ne le gèrent pas bien.
Daniel Pryden
73

Sur les systèmes POSIX (essentiellement n'importe quel système Linux, BSD, quel que soit le système basé sur le code source ouvert que vous puissiez trouver), une ligne est définie comme une chaîne de caractères qui se termine par une nouvelle ligne \n. Ceci est l'hypothèse de base , tous les outils standard de ligne de commande se fondent sur, y compris (mais sans s'y limiter) wc, grep, sed, awket vim. C’est aussi la raison pour laquelle un éditeur (comme vim) ajoute toujours un a \nà la fin d’un fichier, et pourquoi les normes antérieures de C imposaient que les en-têtes se terminent par un \ncaractère.

Btw: Avoir des \nlignes terminées rend le traitement du texte beaucoup plus facile: vous savez que vous avez une ligne complète lorsque vous avez ce terminateur. Et vous savez à coup sûr que vous devez examiner plus de caractères si vous n'avez pas encore rencontré ce terminateur.

Bien sûr, cela se trouve du côté entrée des programmes, mais la sortie du programme est très souvent utilisée à nouveau comme entrée de programme. Votre sortie doit donc respecter la convention afin de permettre une entrée transparente dans d’autres programmes.

cmaster
la source
25
Ceci est l' un des plus vieux débats en génie logiciel: est - il préférable d'utiliser les nouvelles lignes (ou, dans un langage de programmation, un autre marqueur « fin de l' instruction » , comme un point - virgule) en ligne terminateurs ou ligne séparateurs ? Les deux approches ont leurs avantages et leurs inconvénients. Le monde Windows s’est surtout basé sur l’idée que la séquence de nouvelle ligne (qui est typiquement CR LF) est un séparateur de ligne et que la dernière ligne d’un fichier n’a donc pas besoin de se terminer. Dans le monde Unix, cependant, une séquence de nouvelle ligne (LF) est un terminateur de ligne , et de nombreux programmes sont construits autour de cette hypothèse.
Daniel Pryden
33
POSIX définit même une ligne comme "une séquence de zéro ou plusieurs caractères non-nouvelle ligne plus un caractère de fin de ligne nouvelle ".
pipe
6
Etant donné que, comme @pipe le dit, c'est dans la spécification POSIX, on peut probablement l'appeler de jure plutôt que de facto comme le suggère la réponse?
Baldrickk
4
@ Baldrickk droit. J'ai mis à jour ma réponse pour qu'elle soit plus affirmative, maintenant.
cmaster
C crée également cette convention pour les fichiers source: un fichier source non vide qui ne se termine pas par une nouvelle ligne produit un comportement indéfini.
R ..
31

En plus de ce que d'autres ont mentionné, j'ai l'impression qu'il existe une raison beaucoup plus simple: c'est la norme. Chaque fois que quelque chose est imprimé sur STDOUT, cela suppose presque toujours qu'il est déjà sur une nouvelle ligne et qu'il n'est donc pas nécessaire d'en démarrer une nouvelle. Cela suppose également que la prochaine ligne à écrire agira de la même manière, elle se termine donc utilement par une nouvelle ligne.

Si vous produisez des lignes de début de ligne entrelacées avec des lignes de fin de ligne standard, "cela ressemblera à ceci:

Trailing-newline-line
Trailing-newline-line

Leading-newline-line
Leading-newline-line
Leading-newline-lineTrailing-newline-line
Trailing-newline-line

Leading-newline-lineTrailing-newline-line
Trailing-newline-line
Trailing-newline-line

... ce qui n'est probablement pas ce que vous voulez.

Si vous utilisez uniquement des nouvelles lignes dans votre code et que vous l'exécutez dans un environnement de développement intégré, il se peut que cela se passe bien. Dès que vous l'exécutez dans un terminal ou introduisez le code d'une autre personne qui écrit dans STDOUT à côté de votre code, vous verrez une sortie indésirable comme ci-dessus.

Le gars avec le chapeau
la source
2
Quelque chose de similaire se produit avec les programmes qui sont interrompus dans un shell interactif: si une ligne partielle est imprimée (sa nouvelle ligne est manquante), le shell ne comprend pas la colonne sur laquelle se trouve le curseur, ce qui rend difficile la modification de la ligne de commande suivante. À moins d’ajouter une nouvelle ligne à votre liste $PS1, celle-ci serait irritante si vous suiviez des programmes classiques.
Toby Speight
17

Étant donné que les réponses fort élogieuses ont déjà fourni d'excellentes raisons techniques pour lesquelles les nouvelles lignes doivent être préférées, je l'aborderai sous un autre angle.

À mon avis, les éléments suivants rendent un programme plus lisible:

  1. un rapport signal sur bruit élevé (simple, mais pas plus simple)
  2. les idées importantes viennent en premier

De ce qui précède, nous pouvons affirmer que les nouvelles lignes de fuite sont meilleures. Les nouvelles lignes formatent le "bruit" par rapport au message. Le message doit ressortir et doit donc apparaître en premier (la coloration syntaxique peut aussi aider).

Alex Vong
la source
19
Oui, "ok\n"c'est bien mieux que "\nok"...
cmaster
@cmaster: Cela me rappelle d'avoir lu des informations sur MacOS à l'aide des API de chaîne Pascal en C, qui nécessitaient de préfixer tous les littéraux de chaîne avec un code d'échappement magique tel que "\pFoobar".
Grawity
16

L'utilisation des nouvelles lignes de fin simplifie les modifications ultérieures.

En tant qu'exemple (très trivial) basé sur le code de l'OP, supposons que vous deviez générer une sortie avant le message "Initializing" et que cette sortie provient d'une autre partie logique du code, située dans un fichier source différent.

Lorsque vous exécutez le premier test et que "Initializing" est ajouté à la fin d'une ligne d'une autre sortie, vous devez rechercher dans le code pour trouver où il a été imprimé, puis espérer que "Initializing" soit remplacé par "\ nInitializing". "ne bousille pas le format de quelque chose d'autre, dans des circonstances différentes.

Maintenant, réfléchissez à la manière dont vous allez gérer le fait que votre nouvelle sortie est en fait facultative. Par conséquent, le passage à "\ nInitializing" génère parfois une ligne blanche indésirable au début de la sortie ...

Définissez-vous un indicateur global ( horreur de choc? !!! ) indiquant s'il existe une sortie précédente et testez-le pour imprimer "Initializing" avec un "\ n" en tête, ou indiquez-vous le "\ n" en même temps votre sortie précédente et laissez les futurs lecteurs de code se demander pourquoi cette "initialisation" n'a pas un "\ n" majuscule, contrairement à tous les autres messages de sortie?

Si vous produisez régulièrement des retours à la ligne de fin, au moment où vous savez que vous avez atteint la fin de la ligne à terminer, vous évitez tous ces problèmes. Notez que cela peut nécessiter une instruction distincte ("\ n") à la fin d'une logique produisant une ligne pièce par pièce, mais le point est que vous exportez la nouvelle ligne à la première place dans le code, là où vous savez que vous devez faites-le, pas ailleurs.

Alephzero
la source
1
Si chaque élément de sortie indépendant est censé apparaître sur sa propre ligne, les nouvelles lignes suivies peuvent fonctionner correctement. Si plusieurs éléments doivent être regroupés dans la mesure du possible, les choses se compliquent. S'il est pratique d'alimenter toutes les sorties par le biais d'une routine commune, une opération d'insertion d'un effacement jusqu'à la fin de ligne si le dernier caractère était un CR, rien si le dernier caractère était une nouvelle ligne et une nouvelle ligne si le dernier caractère peut être utile si les programmes doivent faire autre chose que produire une séquence de lignes indépendantes.
Supercat
7

Pourquoi utiliser des retours à la ligne au lieu de diriger avec printf?

Correspondre étroitement à la spécification C.

La bibliothèque C définit une ligne comme se terminant par un caractère de nouvelle ligne '\n' .

Un flux de texte est une séquence ordonnée de caractères composée de lignes , chaque ligne étant composée de zéro ou plusieurs caractères plus un caractère de fin de ligne. Que la dernière ligne nécessite un caractère de nouvelle ligne se terminant est défini par l'implémentation. C11 §7.21.2 2

Le code qui écrit les données sous forme de lignes correspondra alors à ce concept de la bibliothèque.

printf("Initializing"); // Write part of a line
printf("\nProcessing"); // Finish prior line & write part of a line
printf("\nExiting");    // Finish prior line & write an implementation-defined last line

printf("Initializing\n");//Write a line 
printf("Processing\n");  //Write a line
printf("Exiting");       //Write an implementation-defined last line

Re: last line nécessite un caractère de fin de ligne . Je recommanderais de toujours écrire une finale '\n'en sortie et de tolérer son absence en entrée.


Vérification orthographique

Mon correcteur orthographique se plaint. Peut-être que vous aussi.

  v---------v Not a valid word
"\nProcessing"

 v--------v OK
"Processing\n");
chux
la source
Je me suis amélioré une fois ispell.elpour mieux faire face à cela. J'admets que c'était plus souvent \tle problème, et il pourrait être évité simplement en divisant la chaîne en plusieurs jetons, mais ce n'était qu'un effet secondaire d'un travail plus général "ignorer", permettant de sauter de manière sélective des parties non textuelles de HTML. ou des corps en plusieurs parties MIME et des parties de code non commentées. Je voulais toujours l'étendre au changement de langue où il y a des métadonnées appropriées (par exemple <p lang="de_AT">ou Content-Language: gd), mais je n'ai jamais eu de Round Tuit. Et le responsable a carrément rejeté mon patch. :-(
Toby Speight
@TobySpeight Voici un toit rond . Au plaisir d'essayer votre re-amélioré ispell.el.
Chux
4

Les nouvelles lignes majeures facilitent souvent l'écriture du code lorsqu'il existe des conditions, par exemple:

printf("Initializing");
if (jobName != null)
    printf(": %s", jobName);
init();
printf("\nProcessing");

(Mais comme cela a été noté par ailleurs, vous devrez peut-être vider le tampon de sortie avant d'effectuer toute étape qui prend beaucoup de temps CPU.)

Par conséquent, un bon argument peut être présenté pour les deux façons de le faire, bien que personnellement je n'aime pas printf () et utiliserais une classe personnalisée pour construire la sortie.

Ian
la source
1
Pouvez-vous expliquer pourquoi cette version est plus facile à écrire qu’une version avec des retours à la ligne? Dans cet exemple, cela ne m'est pas apparent. Au lieu de cela, je pouvais voir des problèmes avec l'ajout de la sortie suivante à la même ligne que "\nProcessing".
Raimund Krämer
Comme Raimund, je peux également voir les problèmes découlant de ce travail. Vous devez tenir compte des impressions environnantes lorsque vous appelez printf. Et si vous vouliez conditionner la totalité de la ligne "Initializing"? Vous devez inclure la ligne "Processus" dans cette condition pour savoir si vous devez préfixer avec une nouvelle ligne ou non. S'il y a une autre impression à venir et que vous devez conditionner la ligne "Traitement", vous devez également inclure la prochaine impression dans cette condition pour savoir si vous devez préfixer par une nouvelle nouvelle ligne, et ainsi de suite pour chaque impression.
JoL
2
Je suis d'accord avec le principe, mais l'exemple n'est pas bon. Un exemple plus pertinent serait le code qui est supposé générer un certain nombre d'éléments par ligne. Si la sortie est censée commencer par un en-tête qui se termine par une nouvelle ligne, et que chaque ligne est supposée commencer par un en-tête, il peut être plus facile de dire, par exemple, if ((addr & 0x0F)==0) printf("\n%08X:", addr);et d'ajouter sans condition une nouvelle ligne à la sortie à la fin, plutôt que d'utiliser code séparé pour l'en-tête de chaque ligne et la fin de ligne.
Supercat
1

Les nouvelles lignes ne fonctionnent pas bien avec les autres fonctions de la bibliothèque, notamment puts()et perrordans la bibliothèque standard, mais également avec toute autre bibliothèque que vous êtes susceptible d'utiliser.

Si vous souhaitez imprimer une ligne pré-écrite (une constante ou une déjà formatée - par exemple avec sprintf()), puts()le choix est naturel (et efficace). Cependant, il n'y a aucun moyen puts()de terminer la ligne précédente et d'écrire une ligne non terminée - elle écrit toujours le terminateur de ligne.

Toby Speight
la source