¿Por qué utilizar nuevas líneas finales en lugar de liderar con printf?

79

Escuché que debes evitar las nuevas líneas al usar printf. Entonces, en lugar de printf("\nHello World!")usarprintf("Hello World!\n")

En este ejemplo particular anterior no tiene sentido, ya que la salida sería diferente, pero considere esto:

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

comparado con:

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

No puedo ver ningún beneficio con las nuevas líneas finales, excepto que se ve mejor. ¿Hay alguna otra razon?

EDITAR:

Me referiré a los votos cerrados aquí y ahora. No creo que esto pertenezca al desbordamiento de la pila, porque esta pregunta es principalmente sobre diseño. También me gustaría decir que aunque puede ser opiniones a este asunto, la respuesta de Kilian Foth y la respuesta de cmaster demuestra que efectivamente existen beneficios muy objetivo con un enfoque.

klutt
fuente
55
Esta pregunta está en la frontera entre "problemas con el código" (que está fuera del tema) y "diseño de software conceptual" (que está en el tema). Puede cerrarse, pero no lo tome demasiado duro. Sin embargo, creo que agregar ejemplos de código concreto fue la elección correcta.
Kilian Foth
46
La última línea se fusionaría con el símbolo del sistema en Linux sin una nueva línea final.
GrandmasterB
44
Si "se ve mejor" y no tiene inconvenientes, esa es una buena razón para hacerlo, en mi opinión. Escribir un buen código no es diferente a escribir una buena novela o un buen documento técnico: el diablo siempre está en los detalles.
alephzero
55
¿Hacer init()e process_data()imprimir algo ellos mismos? ¿Cómo esperarías que se vería el resultado si lo hicieran?
Bergi
99
\nes un terminador de línea , no un separador de línea . Esto se evidencia por el hecho de que los archivos de texto, en UNIX, casi siempre terminan en \n.
Jonathon Reinhart

Respuestas:

222

Una buena cantidad de E / S de terminal está almacenada en línea , por lo que al finalizar un mensaje con \ n puede estar seguro de que se mostrará de manera oportuna. Con un \ n inicial, el mensaje puede mostrarse o no a la vez. A menudo, esto significaría que cada paso muestra el mensaje de progreso del paso anterior , lo que causa un sinfín de confusión y pérdida de tiempo cuando intenta comprender el comportamiento de un programa.

Kilian Foth
fuente
20
Esto es particularmente importante cuando se utiliza printf para depurar un programa que falla. Poner la nueva línea al final de una printf significa que stdout a la consola se vacía en cada printf. (Tenga en cuenta que cuando la salida estándar se redirige a un archivo, las librerías std generalmente hará bloque de memoria intermedia en lugar de búfer de línea, por lo que hace printf depuración de un accidente bastante difícil incluso con nueva línea al final.)
Erik Eidt
25
@ErikEidt Tenga en cuenta que, en su fprintf(STDERR, …)lugar, debe usarlo , que generalmente no está protegido en absoluto para la salida de diagnóstico.
Deduplicador el
44
@Deduplicator Escribir mensajes de diagnóstico en la secuencia de error también tiene sus desventajas: muchas secuencias de comandos suponen que un programa ha fallado si algo se escribe en la secuencia de error.
Voo
54
@Voo: Yo diría que cualquier programa que asume escrituras en stderr indica que un error es incorrecto. El código de salida del proceso es lo que indica si falló o no. Si fue un error, la salida stderr explicará por qué . Si el proceso salió con éxito (código de salida cero), la salida stderr debe considerarse una salida informativa para un usuario humano, sin semántica analizable por máquina en particular (por ejemplo, puede contener advertencias legibles por humanos), mientras que stdout es la salida real de El programa, posiblemente adecuado para su posterior procesamiento.
Daniel Pryden
23
@Voo: ¿Qué programas estás describiendo? No conozco ningún paquete de software ampliamente utilizado que se comporte como usted describe. Sé que hay programas que lo hacen, pero no es como si acabara de inventar la convención que describí anteriormente: así es como funciona la gran mayoría de los programas en un entorno similar a Unix o Unix, y que yo sepa, la forma en que La gran mayoría de los programas siempre lo han hecho. Ciertamente no recomendaría ningún programa para evitar escribir en stderr simplemente porque algunos scripts no lo manejan bien.
Daniel Pryden
73

En los sistemas POSIX (básicamente cualquier Linux, BSD, cualquier sistema basado en código abierto que pueda encontrar), una línea se define como una cadena de caracteres que termina en una nueva línea \n. Esta es la premisa básica de todas las herramientas de línea de comandos estándar se basan en, incluyendo (pero no limitado a) wc, grep, sed, awk, y vim. Esta es también la razón por la cual algunos editores (como vim) siempre agregan un \nal final de un archivo, y por qué los estándares anteriores de C requerían que los encabezados terminaran con un \ncarácter.

Por cierto: tener \nlíneas terminadas hace que el procesamiento de texto sea mucho más fácil: sabes con certeza que tienes una línea completa cuando tienes ese terminador. Y sabes con certeza que necesitas mirar más personajes si aún no has encontrado ese terminador.

Por supuesto, esto está en el lado de entrada de los programas, pero la salida del programa se usa muy a menudo como entrada de programa nuevamente. Por lo tanto, su salida debe cumplir con la convención en aras de permitir la entrada sin problemas en otros programas.

cmaster
fuente
25
Este es uno de los debates más antiguos en ingeniería de software: ¿es mejor usar nuevas líneas (o, en un lenguaje de programación, otro marcador de "fin de declaración" como un punto y coma) como terminadores de línea o separadores de línea ? Ambos enfoques tienen sus pros y sus contras. El mundo de Windows se ha basado principalmente en la idea de que la secuencia de nueva línea (que generalmente es CR LF allí) es un separador de línea y, por lo tanto, la última línea de un archivo no necesita terminar con ella. Sin embargo, en el mundo Unix, una secuencia de nueva línea (LF) es un terminador de línea , y muchos programas se basan en esta suposición.
Daniel Pryden
33
POSIX incluso define una línea como "Una secuencia de cero o más caracteres que no son de nueva línea más un carácter de nueva línea que termina ".
tubería
66
Dado que, como dice @pipe, está en la especificación POSIX, probablemente podamos llamarlo de jure en lugar de facto como sugiere la respuesta.
Baldrickk el
44
@Baldrickk Derecha. He actualizado mi respuesta para ser más afirmativo, ahora.
cmaster
C también hace esta convención para los archivos fuente: un archivo fuente no vacío que no termina con una nueva línea produce un comportamiento indefinido.
R ..
31

Además de lo que otros han mencionado, siento que hay una razón mucho más simple: es el estándar. Cada vez que algo se imprime en STDOUT, casi siempre supone que ya está en una nueva línea y, por lo tanto, no necesita comenzar una nueva. También supone que la siguiente línea que se escribirá actuará de la misma manera, por lo que termina útilmente al comenzar una nueva línea.

Si imprime líneas de línea nueva y entrelazadas con líneas estándar de línea nueva, "terminará así:

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

... que presumiblemente no es lo que quieres.

Si usa solo nuevas líneas iniciales en su código y solo lo ejecuta en un IDE, puede resultar correcto. Tan pronto como lo ejecute en una terminal o introduzca el código de otras personas que escribirá en STDOUT junto con su código, verá una salida no deseada como la anterior.

El chico con el sombrero
fuente
2
Algo similar sucede con los programas que se interrumpen en un shell interactivo: si se imprime una línea parcial (falta su nueva línea), el shell se confunde acerca de en qué columna está el cursor, lo que dificulta editar la siguiente línea de comando. A menos que agregue una nueva línea principal a su $PS1, lo que sería irritante después de los programas convencionales.
Toby Speight
17

Dado que las respuestas altamente votadas ya han dado excelentes razones técnicas por las cuales debería preferirse seguir las nuevas líneas, lo abordaré desde otro ángulo.

En mi opinión, lo siguiente hace que un programa sea más legible:

  1. Una alta relación señal / ruido (también conocida como simple pero no más simple)
  2. las ideas importantes son lo primero

De los puntos anteriores, podemos argumentar que las nuevas líneas finales son mejores. Las nuevas líneas están formateando "ruido" en comparación con el mensaje, el mensaje debe sobresalir y, por lo tanto, debe ser lo primero (el resaltado de sintaxis también puede ayudar).

Alex Vong
fuente
19
Sí, "ok\n"es mucho mejor que "\nok"...
cmaster 19/1118
@cmaster: me recuerda haber leído sobre MacOS usando las API de Pascal-string en C, lo que requería el prefijo de todos los literales de cadena con un código de escape mágico como "\pFoobar".
Grawity
16

El uso de líneas nuevas al final simplifica las modificaciones posteriores.

Como un ejemplo (muy trivial) basado en el código del OP, suponga que necesita producir alguna salida antes del mensaje "Inicializando", y esa salida proviene de una parte lógica diferente del código, en un archivo fuente diferente.

Cuando ejecuta la primera prueba y encuentra que "Inicializar" ahora se agrega al final de una línea de alguna otra salida, debe buscar a través del código para encontrar dónde se imprimió, y luego esperar cambiar "Inicializando" a "\ nInicialización "no arruina el formato de otra cosa, en diferentes circunstancias.

Ahora considere cómo manejar el hecho de que su nueva salida es realmente opcional, por lo que su cambio a "\ nInicialización" a veces produce una línea en blanco no deseada al comienzo de la salida ...

¿Establece un indicador global ( shock horror ?? !!! ) que indica si hubo algún resultado anterior y lo prueba para imprimir "Inicializando" con un "\ n" inicial opcional, o genera el "\ n" junto con su salida anterior y deje futuros lectores de códigos preguntándose por qué esta "Inicialización" no tiene un "\ n" inicial como todos los demás mensajes de salida.

Si constantemente genera nuevas líneas finales, en el punto en el que sabe que ha llegado al final de la línea que necesita ser terminada, evita todos esos problemas. Tenga en cuenta que eso puede requerir una declaración de put ("\ n") al final de alguna lógica que genera una línea pieza por pieza, pero el punto es que usted genera la nueva línea en el primer lugar del código donde sabe que necesita hazlo, no en otro lugar.

alephzero
fuente
1
Si se supone que cada elemento de salida independiente debe aparecer en su propia línea, las nuevas líneas finales pueden funcionar bien. Sin embargo, si se deben consolidar varios elementos cuando sea práctico, las cosas se vuelven más complicadas. Si es práctico alimentar toda la salida a través de una rutina común, una operación para insertar un claro al final de la línea si el último carácter era un CR, nada si el último carácter era una nueva línea y una nueva línea si el último carácter fue algo más, puede ser útil si los programas necesitan hacer algo más que generar una secuencia de líneas independientes.
supercat
7

¿Por qué utilizar nuevas líneas finales en lugar de liderar con printf?

Coincidir estrechamente con la especificación C.

La biblioteca C define una línea como que termina con un carácter de nueva línea '\n' .

Una secuencia de texto es una secuencia ordenada de caracteres compuestos en líneas , cada línea que consta de cero o más caracteres más un carácter de nueva línea que termina. Si la última línea requiere un carácter de nueva línea de terminación está definida por la implementación. C11 §7.21.2 2

El código que escribe datos como líneas coincidirá con el concepto de la biblioteca.

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: la última línea requiere un carácter de nueva línea de terminación . Recomiendo siempre escribir un final '\n'en la salida y tolerar su ausencia en la entrada.


Corrección ortográfica

Mi corrector ortográfico se queja. Quizás tu también lo haga.

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

 v--------v OK
"Processing\n");
chux
fuente
Una vez mejoré ispell.elpara hacer frente mejor a eso. Admito que \tese era el problema con mayor frecuencia , y podría evitarse simplemente dividiendo la cadena en varios tokens, pero era solo un efecto secundario del trabajo de "ignorar" más general, omitir selectivamente partes de HTML que no son de texto. o cuerpos multiparte MIME y partes de código que no hacen comentarios. Siempre quise extenderlo a cambiar de idioma donde hay metadatos apropiados (por ejemplo, <p lang="de_AT">o Content-Language: gd), pero nunca obtuve un Round Tuit. Y el responsable rechazó mi parche directamente. :-(
Toby Speight
@TobySpeight Aquí hay un toit redondo . Esperamos probar su re-mejorado ispell.el.
chux el
4

Las nuevas líneas principales a menudo pueden facilitar la escritura del código cuando hay condicionales, por ejemplo,

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

(Pero como se ha señalado en otra parte, es posible que deba vaciar el búfer de salida antes de realizar cualquier paso que requiera mucho tiempo de CPU).

Por lo tanto, se puede hacer un buen caso para ambas formas de hacerlo, sin embargo, personalmente no me gusta printf () y usaría una clase personalizada para construir la salida.

Ian
fuente
1
¿Podría explicar por qué esta versión es más fácil de escribir que una con líneas nuevas? En este ejemplo no me resulta evidente. En cambio, pude ver que surgen problemas con la próxima salida que se agrega a la misma línea que "\nProcessing".
Raimund Krämer
Al igual que Raimund, también puedo ver problemas derivados de trabajar así. Debe tener en cuenta las huellas circundantes al llamar printf. ¿Qué sucede si desea condicionalizar toda la línea "Inicializando"? Tendría que incluir la línea "Procesando" en esa condición para saber si debe prefijar con una nueva línea o no. Si hay otra impresión por delante y necesita condicionalizar la línea "Procesando", también necesitaría incluir la siguiente impresión en esa condición para saber si debe prefijar con otra nueva línea, y así sucesivamente para cada impresión.
JoL
2
Estoy de acuerdo con el principio, pero el ejemplo no es bueno. Un ejemplo más relevante sería con el código que se supone que genera una cierta cantidad de elementos por línea. Si se supone que la salida comienza con un encabezado que termina con una nueva línea, y se supone que cada línea comienza con un encabezado, puede ser más fácil decir, por ejemplo, if ((addr & 0x0F)==0) printf("\n%08X:", addr);y agregar incondicionalmente una nueva línea a la salida al final, que usar código separado para el encabezado de cada línea y la nueva línea final.
supercat
1

Las nuevas líneas líderes no funcionan bien con otras funciones de la biblioteca, en particular puts()y perroren la Biblioteca estándar, sino también con cualquier otra biblioteca que pueda usar.

Si desea imprimir una línea preescrita (ya sea una constante o una ya formateada, por ejemplo, con sprintf()), entonces puts()es la opción natural (y eficiente). Sin embargo, no hay forma de puts()terminar la línea anterior y escribir una línea sin terminar; siempre escribe el terminador de línea.

Toby Speight
fuente